前言
之前非系统性的学习导致知识封面总有缺漏,打算从 vue3 开始重新系统的学习一下 vue 和 js 的相关知识。记录以前没有掌握好的地方。
正文
vite
不多说,vue3 的全新打包工具。项目启动的速度比 webpack 快太多了。具体的安装和使用官网有很详细的介绍。 vite
setup 语法糖
Vue3 官方提供了 script setup 语法糖
只需在 script 标签中添加 setup,组件只需引入不用注册,属性和方法也不用返回,也不用写 setup 函数,也不用写 export default ,甚至是自定义指令也可以在我们的 template 中自动获得。
节省了不少无用的代码,不然每次添加新的数据都需要再新增 return。
<script setup></script>
子父组件传值
因为没有了 setup 函数,那么 props,emit,attrs 怎么获取呢 setup script 语法糖提供了新的 API 来供我们使用 defineProps 用来接收父组件传来的 props,用于父向子传值。 defineEmits 用来声明触发的事件,用于子向父传值。
//子组件
<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>
点击 发送消息给父组件 点击 改变传给子组件的值
插槽
一共有三种插槽。
- 默认插槽
- 具名插槽
- 作用域插槽
在开发中,我们会经常封装一个个可复用的组件: 前面我们会通过 props 传递给组件一些数据,让组件来进行展示但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的 div、span 等等这些元素;比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片;我们应该让使用者可以决定某一块区域到底存放什么内容和元素。
默认插槽
//子组件
<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>
效果图:
具名插槽
顾名思义就是有具体名字的插槽。
//子组件
<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>
效果图:
作用域插槽
就是子组件可给父组件传递数据,以作用域插槽的形式。
//子组件
<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>
效果图:
vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 个人的理解就是当你需要跨页面访问一些数据就可以用到 vuex,比如网站完成登录后就把登录的用户信息保存到 vuex 中,这样在任何需要用到用户信息的页面都可以访问。 vue3 之后的 vuex 引入方式有所变化,具体可以参照官网文档。vuex 官网
1.安装 vuex
npm install vuex@next --save
//or
yarn add vuex@next --save
2.配置
//打开main.js,添加这两行
import store from './store';
createApp(App).use(store);
在你的 src 目录新建一个 store 文件夹,再新建一个 index.js 文件。 index.js 的内容如下
//index.js
import { createStore } from 'vuex';
export default createStore({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {},
});
3.state 使用
state: {
username: "jjh"
},
然后你在需要用到该数据的地方引入 vuex,就可以调用它了
<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>
效果图:
4.mutations 使用
当我们需要修改 state 的状态,及修改它的值时就需要用到 mutations。方式是用==store.commit('mution 中函数名', '需要传递的参数' )== 进行传递。可以调用函数来触发。 在 index.js 的 mutations 添加
//index.js
mutations: {
changeName (state, newName) {
state.username = newName
}
},
页面代码
<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>
效果图: 点击按钮进行修改。
5.aciton 使用
异步操作在 action 中进行,再传递到 mutation。 即“相应视图—>修改 State”拆分成两部分,视图触发 Action,Action 再触发 Mutation。 action 基本使用如下: 在 index.js 的 action 里添加
actions: {
changeName_action (context, newName) {
context.commit("changeName", newName) //这是mutations的函数名
}
},
页面代码通过==store.dispatch('action 中函数名', '需要传递的参数' )==
<template>
<div class="msg">
<h1>来自VUEX的数据-{{ store.state.username }}</h1>
<v-btn @click="vuexClick" color="primary">VUEX</v-btn>
<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>
效果图: 点击按钮后修改了数据。
6.getters 使用
类似于组件的计算属性。通过==store.getters.方法名== 进行调用。 以官网的案例来说,在 index.js 添加
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: (state) => {
return state.todos.filter(todo => todo.done)
}
}
页面代码
<template>
<div class="msg">
<h1>来自VUEX的数据-{{ store.state.username }}</h1>
<v-btn @click="vuexMutations" color="primary">vuexMutations</v-btn>
<v-btn @click="vuexAciotn" color="primary">vuexAciotn</v-btn>
<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>
效果图: 输出了 done 为 true 的数据。
7. Module 使用
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。 用法在 index.js 中:
//index.js
const mouduleA = {
state: {
name: "mouduleA"
}
}
const mouduleB = {
state: {
name: "mouduleB"
}
}
const store = createStore(
/**
** 之前重复的部分
**/
modules: {
a: moduleA,
b: moduleB
}
})
页面代码部分: 通过==store.state.去的 module 的名字== 来访问。
//案列
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>
<v-btn @click="vuexAciotn" color="primary">vuexAciotn</v-btn>
<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>
效果图:
8.vuex 的持久化
让在 vuex 中管理的状态数据同时存储在本地。可免去自己存储的环节。在开发的过程中,像用户信息(名字,头像,token)需要 vuex 中存储且需要本地存储,不然会出现刷新页面数据就消失的情况。 在 vue 中可以通过vuex-persistedstate插件帮助我们实现。
- 安装
npm i vuex-persistedstate -s
//or
yarn add vuex-persistedstate -s
//推荐yarn,速度更快而且出错的概率更低。不推荐cnpm安装依赖因为经常出现奇奇怪怪的bug。
- 配置 在你的 index.js 中
//导入
import createPersistedState from 'vuex-persistedstate';
export default createStore({
/**
** 之前重复的部分
**/
//新增以下部分
plugins: [
createPersistedState({
storage: window.sessionStorage,
}),
],
});
这样就能把数据保存在本地 localStorage 中。注意关闭浏览器或者页面后就会消失。
vue-router
官网 vue-router 帮助 vue 项目实现路由跳转的插件。
1.安装
//针对vue3的版本
npm install vue-router@4 -s
//or
yarn add vue-router@4 -s
2.配置
在 main.js 下引入 vue-router
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router);
在 src 下新建 router 文件夹,再新建 index.js 文件。 index.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 在最顶上。
//方法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'),
},
];
在浏览器输出当前的路由即可实现跳转。 效果图:
带参数的动态路由匹配
路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来。因此,我们可以通过更新 User 的模板来呈现当前的用户 ID:
//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')
}
页面
//单个参数
<template>
<div>
<h1>{{ $route.params.name }}</h1>
</div>
</template>
//多个参数
<template>
<div>
<h1>{{ $route.params.id }}</h1>
<h1>{{ $route.params.name }}</h1>
</div>
</template>
效果图:
导航守卫
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。可以用作拦截器。
全局前置守卫
你可以使用 router.beforeEach 注册一个全局前置守卫:
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
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' });
else next();
});
具体查看 路由守卫
vite 的跨域配置
在 vite 构建项目的 vite.config.js 中添加
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 部分
const formRef = ref();
const chage = () => {
//需要先.value得到,之后的操作和vue2是一样的。
//例如给它添加而外的属性
formRef.value[index].$el.style.display = 'none';
};
//html 部分
<div ref="formRef"></div>;