Vue3 学习记录Vue3 学习记录Vue3 学习记录Vue3 学习记录Vue3 学习记录Vue3 学习记录

前言

之前非系统性的学习导致知识封面总有缺漏,打算从 vue3 开始重新系统的学习一下 vue 和 js 的相关知识。记录以前没有掌握好的地方。

正文

vite

不多说,vue3 的全新打包工具。项目启动的速度比 webpack 快太多了。具体的安装和使用官网有很详细的介绍。 vite

setup 语法糖

Vue3 官方提供了 script setup 语法糖

只需在 script 标签中添加 setup,组件只需引入不用注册,属性和方法也不用返回,也不用写 setup 函数,也不用写 export default ,甚至是自定义指令也可以在我们的 template 中自动获得。

节省了不少无用的代码,不然每次添加新的数据都需要再新增 return。

js
<script setup></script>

子父组件传值

因为没有了 setup 函数,那么 props,emit,attrs 怎么获取呢 setup script 语法糖提供了新的 API 来供我们使用 defineProps 用来接收父组件传来的 props,用于父向子传值。 defineEmits 用来声明触发的事件,用于子向父传值。

js
//子组件
<template>
  <h2>我是一个子组件</h2>
  <p>{{ props.title }}</p>
  <v-btn @click="sonClick">发消息给父组件</v-btn>
</template>

<script setup>
import { reactive, ref, defineProps, defineEmits } from 'vue';
const emit = defineEmits(["change"]);
const props = defineProps({
  title: String
})
const sonClick = () => {
  emit('change', "来自子组件的消息")
}
</script>

//父组件
<template>
  <div>
    <h1>{{ pageData.title }}</h1>
    <h1>{{ pageData.sonMsg }}</h1>
    <v-btn @click="fatherChange">改变传给子组件的值</v-btn>
    <Child :title="pageData.message" @change="accept">
    </Child>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from '../Child/Child.vue';
const pageData = reactive({
  title: "我是父组件",
  message: "来自父组件的消息",
  sonMsg: ""
})
const fatherChange = () => {
  pageData.message = "来自父组件的最新消息"
  console.log(pageData.message)
}
const accept = (msg) => {
  pageData.sonMsg = msg
}
</script>

image.png 点击 发送消息给父组件image.png 点击 改变传给子组件的值image.png

插槽

一共有三种插槽。

  1. 默认插槽
  2. 具名插槽
  3. 作用域插槽

在开发中,我们会经常封装一个个可复用的组件: 前面我们会通过 props 传递给组件一些数据,让组件来进行展示但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的 div、span 等等这些元素;比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片;我们应该让使用者可以决定某一块区域到底存放什么内容和元素。

默认插槽

js
//子组件
<template>
  <h2>我是一个子组件</h2>
  <p>{{ props.title }}</p>
  <v-btn @click="sonClick">发消息给父组件</v-btn>
  <!--这是插槽的位置 -->
  <slot></slot>
</template>

<script setup>
import { reactive, ref, defineProps, defineEmits } from 'vue';
const emit = defineEmits(["change"]);
const props = defineProps({
  title: String
})
const sonClick = () => {
  emit('change', "来自子组件的消息")
}
</script>



//父组件
<template>
  <div class="homecard">
    <h1>{{ pageData.title }}</h1>
    <h1>{{ pageData.sonMsg }}</h1>
    <v-btn @click="fatherChange">改变传给子组件的值</v-btn>
    <Child :title="pageData.message" @change="accept">
      <h2>这是插槽</h2>
    </Child>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from '../Child/Child.vue';
const pageData = reactive({
  title: "我是父组件",
  message: "来自父组件的消息",
  sonMsg: ""

})
const fatherChange = () => {
  pageData.message = "来自父组件的最新消息"
  console.log(pageData.message)
}
const accept = (msg) => {
  pageData.sonMsg = msg
}
</script>
<style scoped>
.homecard {
  position: relative;
  left: 40%;
  top: 20%;

}
</style>

效果图: image.png

具名插槽

顾名思义就是有具体名字的插槽。

js
//子组件
<template>
  <h2>我是一个子组件</h2>
  <p>{{ props.title }}</p>
  <v-btn @click="sonClick">发消息给父组件</v-btn>
  <!--这是插槽的位置 -->
  <slot name="home"></slot>
  <slot name="home2"></slot>
</template>

<script setup>
import { reactive, ref, defineProps, defineEmits } from 'vue';
const emit = defineEmits(["change"]);
const props = defineProps({
  title: String
})
const sonClick = () => {
  emit('change', "来自子组件的消息")
}
</script>

//父组件
<template>
  <div class="homecard">
    <h1>{{ pageData.title }}</h1>
    <h1>{{ pageData.sonMsg }}</h1>
    <v-btn @click="fatherChange">改变传给子组件的值</v-btn>
    <Child :title="pageData.message" @change="accept">
      <template #home>
        <h2>这是插槽1号</h2>
      </template>
      <template #home2>
        <h2>这是插槽2号</h2>
      </template>
    </Child>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from '../Child/Child.vue';
const pageData = reactive({
  title: "我是父组件",
  message: "来自父组件的消息",
  sonMsg: ""

})
const fatherChange = () => {
  pageData.message = "来自父组件的最新消息"
  console.log(pageData.message)
}
const accept = (msg) => {
  pageData.sonMsg = msg
}
</script>
<style scoped>
.homecard {
  position: relative;
  left: 40%;
  top: 20%;

}
</style>

效果图: image.png

作用域插槽

就是子组件可给父组件传递数据,以作用域插槽的形式。

js
//子组件
<template>
  <h2>我是一个子组件</h2>
  <p>{{ props.title }}</p>
  <v-btn @click="sonClick">发消息给父组件</v-btn>
  <!--这是插槽的位置 -->
  <slot name="home" :data="message"></slot>
  <slot name="home2"></slot>
</template>

<script setup>
import { reactive, ref, defineProps, defineEmits } from 'vue';
const message = ref("作用域插槽")
const emit = defineEmits(["change"]);
const props = defineProps({
  title: String
})
const sonClick = () => {
  emit('change', "来自子组件的消息")
}
</script>



//父组件
<template>
  <div class="homecard">
    <h1>{{ pageData.title }}</h1>
    <h1>{{ pageData.sonMsg }}</h1>
    <v-btn @click="fatherChange">改变传给子组件的值</v-btn>
    <Child :title="pageData.message" @change="accept">
      <template #home="{ data }">
        <h2>这是插槽1号</h2>
        <h2>{{ data }}</h2>
      </template>
      <template #home2>
        <h2>这是插槽2号</h2>
      </template>
    </Child>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from '../Child/Child.vue';
const pageData = reactive({
  title: "我是父组件",
  message: "来自父组件的消息",
  sonMsg: ""

})
const fatherChange = () => {
  pageData.message = "来自父组件的最新消息"
  console.log(pageData.message)
}
const accept = (msg) => {
  pageData.sonMsg = msg
}
</script>
<style scoped>
.homecard {
  position: relative;
  left: 40%;
  top: 20%;

}
</style>

效果图: image.png

vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 个人的理解就是当你需要跨页面访问一些数据就可以用到 vuex,比如网站完成登录后就把登录的用户信息保存到 vuex 中,这样在任何需要用到用户信息的页面都可以访问。 vue3 之后的 vuex 引入方式有所变化,具体可以参照官网文档。vuex 官网

1.安装 vuex

sh
npm install vuex@next --save
//or
yarn add vuex@next --save

2.配置

js
//打开main.js,添加这两行
import store from './store';
createApp(App).use(store);

在你的 src 目录新建一个 store 文件夹,再新建一个 index.js 文件。 image.png index.js 的内容如下

js
//index.js
import { createStore } from 'vuex';
export default createStore({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});

3.state 使用

js
 state: {
   username: "jjh"
 },

然后你在需要用到该数据的地方引入 vuex,就可以调用它了

js
<template>
  <div class="msg">
    <h1>来自VUEX的数据-{{ store.state.username }}</h1>
  </div>
</template>
<script setup>
import store from "../../store";
</script>
<style scoped>
.msg {
  position: relative;
  left: 20%;
  top: 20%;
}
</style>

效果图: image.png

4.mutations 使用

当我们需要修改 state 的状态,及修改它的值时就需要用到 mutations。方式是用==store.commit('mution 中函数名', '需要传递的参数' )== 进行传递。可以调用函数来触发。 在 index.js 的 mutations 添加

js
  //index.js
  mutations: {
    changeName (state, newName) {
      state.username = newName
    }
  },

页面代码

js
<template>
  <div class="msg">
    <h1>来自VUEX的数据-{{ store.state.username }}</h1>
    <v-btn @click="vuexClick" color="primary">VUEX</v-btn>
  </div>
</template>
<script setup>
import store from "../../store";
const vuexClick = () => {
  store.commit("changeName", "roy")
}
</script>
<style scoped>
.msg {
  position: relative;
  left: 20%;
  top: 20%;
}
</style>

效果图: image.png 点击按钮进行修改。 image.png

5.aciton 使用

异步操作在 action 中进行,再传递到 mutation。 即“相应视图—>修改 State”拆分成两部分,视图触发 Action,Action 再触发 Mutation。 action 基本使用如下: 在 index.js 的 action 里添加

js
  actions: {
    changeName_action (context, newName) {
      context.commit("changeName", newName) //这是mutations的函数名
    }
  },

页面代码通过==store.dispatch('action 中函数名', '需要传递的参数' )==

js
<template>
  <div class="msg">
    <h1>来自VUEX的数据-{{ store.state.username }}</h1>
    <v-btn @click="vuexClick" color="primary">VUEX</v-btn>
    &nbsp;
    <v-btn @click="vuexAciotn" color="primary">vuexAciotn</v-btn>
  </div>
</template>
<script setup>
import store from "../../store";
const vuexClick = () => {
  store.commit("changeName", "roy")
}
const vuexAciotn = () => {
  store.dispatch("changeName_action", "roy")
}
</script>
<style scoped>
.msg {
  position: relative;
  left: 20%;
  top: 20%;
}
</style>

效果图: image.png 点击按钮后修改了数据。 image.png

6.getters 使用

类似于组件的计算属性。通过==store.getters.方法名== 进行调用。 以官网的案例来说,在 index.js 添加

js
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: (state) => {
      return state.todos.filter(todo => todo.done)
    }
  }

页面代码

js
<template>
  <div class="msg">
    <h1>来自VUEX的数据-{{ store.state.username }}</h1>
    <v-btn @click="vuexMutations" color="primary">vuexMutations</v-btn>
    &nbsp;
    <v-btn @click="vuexAciotn" color="primary">vuexAciotn</v-btn>
    &nbsp;
    <v-btn @click="vuexGetters" color="primary">vuexGetters</v-btn>
  </div>
</template>
<script setup>
import store from "../../store";
const vuexMutations = () => {
  store.commit("changeName", "roy")
}
const vuexAciotn = () => {
  store.dispatch("changeName_action", "roy")
}
const vuexGetters = () => {
  console.log(store.getters.doneTodos)
}
</script>
<style scoped>
.msg {
  position: relative;
  left: 20%;
  top: 20%;
}
</style>

效果图: image.png 输出了 done 为 true 的数据。

7. Module 使用

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。 用法在 index.js 中:

js
//index.js
const mouduleA = {
  state: {
    name: "mouduleA"
  }
}
const mouduleB = {
  state: {
    name: "mouduleB"
  }
}
const store = createStore(
  /**
   ** 之前重复的部分
   **/
  modules: {
    a: moduleA,
    b: moduleB
  }
})

页面代码部分: 通过==store.state.去的 module 的名字== 来访问。

js
//案列
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
//代码
<template>
  <div class="msg">
    <h1>来自VUEX的数据-{{ store.state.username }}</h1>
    <h1>来自ModuleA的数据-{{ store.state.a.name }}</h1>
    <h1>来自ModuleB的数据-{{ store.state.b.name }}</h1>
    <v-btn @click="vuexMutations" color="primary">vuexMutations</v-btn>
    &nbsp;
    <v-btn @click="vuexAciotn" color="primary">vuexAciotn</v-btn>
    &nbsp;
    <v-btn @click="vuexGetters" color="primary">vuexGetters</v-btn>
  </div>
</template>
<script setup>
import store from "../../store";
const vuexMutations = () => {
  store.commit("changeName", "roy")
}
const vuexAciotn = () => {
  store.dispatch("changeName_action", "roy")
}
const vuexGetters = () => {
  console.log(store.getters.doneTodos)
}
</script>
<style scoped>
.msg {
  position: relative;
  left: 20%;
  top: 20%;
}
</style>

效果图: image.png

8.vuex 的持久化

让在 vuex 中管理的状态数据同时存储在本地。可免去自己存储的环节。在开发的过程中,像用户信息(名字,头像,token)需要 vuex 中存储且需要本地存储,不然会出现刷新页面数据就消失的情况。 在 vue 中可以通过vuex-persistedstate插件帮助我们实现。

  1. 安装
sh
npm i vuex-persistedstate -s
//or
yarn add vuex-persistedstate -s
//推荐yarn,速度更快而且出错的概率更低。不推荐cnpm安装依赖因为经常出现奇奇怪怪的bug。
  1. 配置 在你的 index.js 中
js
//导入
import createPersistedState from 'vuex-persistedstate';
export default createStore({
  /**
   ** 之前重复的部分
   **/
  //新增以下部分
  plugins: [
    createPersistedState({
      storage: window.sessionStorage,
    }),
  ],
});

这样就能把数据保存在本地 localStorage 中。注意关闭浏览器或者页面后就会消失。

vue-router

官网 vue-router 帮助 vue 项目实现路由跳转的插件。

1.安装

js
//针对vue3的版本
npm install vue-router@4 -s
//or
yarn add vue-router@4 -s

2.配置

在 main.js 下引入 vue-router

js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router);

在 src 下新建 router 文件夹,再新建 index.js 文件。 image.png index.js 的内容。

js
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  //放入路由的信息
];
const router = createRouter({
  history: createWebHistory(), //选择history模式
  routes,
});

export default router;

3.使用

基础用法

在 index.js 引入组件,并在 routes 数组里配置好信息,引入有两种方法,个人觉得方法 2 要美观一些。方法 1 会写一堆 import 在最顶上。

js
//方法1
import Home from '../views/Home/Home.vue'; //引入组件
const routes = [
  {
    path: '/home',
    name: 'home',
    component: Home,
  },

  {
    path: '/home',
    name: 'home',
    //方法2
    component: () => import('../views/Home/Home.vue'),
  },
];

在浏览器输出当前的路由即可实现跳转。 效果图: image.png

带参数的动态路由匹配

路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来。因此,我们可以通过更新 User 的模板来呈现当前的用户 ID:

js
//index.js
   //单个参数
  {
    path: '/user/:name',
    name: 'user',
    component: () => import('../views/User/User.vue')
  }
  //支持匹配多个参数
  {
    path: '/user/:id/:name',
    name: 'user',
    component: () => import('../views/User/User.vue')
  }

页面

js
//单个参数
<template>
  <div>
    <h1>{{ $route.params.name }}</h1>
  </div>
</template>

//多个参数
<template>
  <div>
    <h1>{{ $route.params.id }}</h1>
    <h1>{{ $route.params.name }}</h1>
  </div>
</template>

效果图: image.pngimage.png

导航守卫

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。可以用作拦截器。

全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫:

js
const router = createRouter({ ... })

 router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }

  // ...
  // 返回 false 以取消导航
  return false
 })

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

每个守卫方法接收两个参数:

  • to: 即将要进入的目标 用一种标准化的方式
  • from: 当前导航正要离开的路由 用一种标准化的方式

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或 name: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。
可选的第三个参数 next
js
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' });
  else next();
});

具体查看 路由守卫

vite 的跨域配置

在 vite 构建项目的 vite.config.js 中添加

language
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vuetify from '@vuetify/vite-plugin'

const path = require('path')

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
    vuetify({
      autoImport: true,
    }),
  ],
  define: { 'process.env': {} },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  server: {
    port: 4000,//启动端口
    proxy: {
      '/api': { //对/api开头的请求才进行跨域
        target: 'http://localhost:57909',  //服务器地址
        changeOrigin: true,  // 开启跨域
 //如果你的后台请求本来就有'/api',这一句就需要删除
 //比如你的后台请求是http://localhost:8080/api/Admin/GetAllClassVOList,已经包含'/api',就不需要添加这一句
        rewrite: (path) => path.replace(/^\/api/, '')  //删除请求的'/api'
      }
    }
  },
})

建议搭配 axios 一起使用。

axios

一个进行网络请求的工具。易用、简洁且高效的 http 库。 安装

vue3 的$refs

碰到需要直接操作 dom 的时候会用到。

js
//js 部分
const formRef = ref();

const chage = () => {
  //需要先.value得到,之后的操作和vue2是一样的。
  //例如给它添加而外的属性
  formRef.value[index].$el.style.display = 'none';
};

//html 部分
<div ref="formRef"></div>;
扫码关注我的公众号
关于代码的问题都欢迎交流