Vue3
文章目录
简介
从Vue2进阶到Vue3的学习笔记,记录Vue3于Vue2的不同点,新增特性等
其中,最大的改变就是组合式API(简单来说就是写JavaScript逻辑的语法发生了些改变),当然你也可以继续使用Vue2的写法,因为Vue3向下兼容它们;但我认为这是一种趋势,因此最好还是掌握组合式API的语法写法
Vue2 写法的改变
关于 Vue2 语法写法在 Vue3 中最大的改变就是:Vue2 可以使用
this,得到 Vue 实例对象,虽然在 Vue3 中也依然支持 Vue2 的语法写法( 用该写法可以获取this);但若使用 Vue3 写法,setup在beforeCreate和create前执行,因此 Vue 实例对象还没有被创建,无法获取到this;所以在 Vue3 写法中,大部分的改动围绕着曾经使用this编写的代码,现在如何编写;
那么我们首先从一个问题开始,为什么使用 Vue3 呢?
- Vue2 使用的是optionsAPI(选项式API),Vue3 开始使用 compositionAPI(组合式API),代码组织更方便了, 逻辑复用更方便了 非常利于维护
- 数据响应式 原理重新实现 ( ES6 的
proxy替代了 ES5 的Object.defineProperty),大大优化了响应式监听的性能,原来检测对象属性的变化, 需要一个个对属性递归监听, proxy 可以直接对整个对象劫持 template模板可以有多个根元素(可以不用使用一个根元素包裹整个组件了)- 源码用
typescript重写, 有更好的 类型推导 (类型检测更为严格, 更稳定)
总结:
vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便
SetUp函数
是 Vue3 最常用也是最重要的API,使用组合式API必须使用setup函数!setup 函数是一个新的组件选项,作为组件中 compositionAPI 的起点
它解决的问题:
解决组件内容庞大后,理解和维护组件变得困难的问题。即 vue2 中 data、computed、methods、watch 等内容非常多以后,同一业务逻辑的 data 中的数据和 methods 中的方法在 vue 文件中 “相隔甚远” ,看代码时,经常需要根据 data 中的数据去搜索找到对应的 methods 方法,上下跳跃查看代码,非常不方便。而在 setup 中,则可以把 data 中的数据和 methods 方法写在相临的位置,方便查看和维护
它的简单使用:
<!-- 使用方法1: -->
<script>
export default {
setup() {
/* setup函数体 */
return {
/* setup函数返回的变量或对象 */
}
}
}
</script>
<!-- 使用方法2(推荐): -->
<script setup>
/* 定义的所有内容都是setup的返回变量或对象,它们的定义以及内容都在标签中 */
</script>
那么它是大概是怎么用的呢?
<script setup>
let name = 'bokey'
let nameChange = () => {
name = 'xxx'
}
</script>
<!-- 会被编译成 ↓ -->
<script>
data() {
return {
name: 'bokey', // 但需要注意:该name值没有响应式(vue不会监听该值变化)!
}
},
methods: {
nameChange() {
name = 'xxx'
},
}
</script>
注意事项:
- 它没有
this指针,因为从生命周期角度来看,setup会在beforeCreate钩子函数之前执行 - 在
template模版中需要使用的数据和函数,需要在setup返回
相信到这里,你已经理解了setup函数的作用,但这还不具备响应式,也就是说 Vue3 不会监听定义内容是否变化;为它们添加响应式就是下面要讲的
响应式数据及读取(ref / reactive)
<template>
<div id="root">
<!-- data数据创建 -->
<h1>非对象数据:我是{{ name }}</h1>
<h1>对象数据:我是{{ Bokey.name }},我的性别为:{{ Bokey.sex }}</h1>
<!-- method方法创建 -->
<button @click="changeName">一键改名</button>
</div>
</template>
<script setup>
import { ref , reactive } from 'vue'
// 创建非对象数据
const name = ref("Bokey")
// 创建对象数据
const Bokey = reactive({
name: 'bokey',
sex: 'male',
})
// 方法创建,于数据的读取
const changeName = () => {
name.value = 'LZH'
Bokey.name = 'LZH'
}
</script>
结果:

点击后:

计算属性
<template>
<div id="root">
姓:<input type="text" v-model="Bokey.firstName" /><br /><br />
名:<input type="text" v-model="Bokey.lastName" /><br /><br />
全名:<span>{{ fullName }}</span><br>
全名:<span>{{ fullNameGetAndSet }}</span>
<hr>
<button @click="changeFullName">修改fullName</button>
<button @click="changeFullNameGetAndSet">修改fullNameGetAndSet</button>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const Bokey = reactive({
firstName: '张',
lastName: '三',
})
// 计算属性(简写形式,只有getter时)
let fullName = computed(() => {
return Bokey.firstName + Bokey.lastName
})
let changeFullName = () => { // 修改计算属性fullName的点击事件回调函数
fullName.value = '修改了1-修改了2'
}
// 需要getter和setter时
let fullNameGetAndSet = computed({
get() {
return Bokey.firstName + '-' + Bokey.lastName
},
set(value) {
const arr = value.split('-')
Bokey.firstName = arr[0] // 修改firstName
Bokey.lastName = arr[1] // 修改lastName
}
})
let changeFullNameGetAndSet = () => { // 修改计算属性fullNameGetAndSet的点击事件回调函数
fullNameGetAndSet.value = '修改了1-修改了2'
}
</script>
结果:

修改姓:

修改名:

点击修改fullName按钮:

点击修改fullNameGetAndSet按钮:

监视属性
<template>
<div id="root">
<h2>今天天气很{{ info ? '炎热' : '寒冷' }}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
let info = ref(true)
let changeWeather = function () {
info.value = !info.value
}
// 监听info属性
watch(info, (newValue, oldValue) => {
console.log('isHot被修改:' + oldValue + "->" + newValue);
}, {
immediate: true, // 初始化也调用监听的回调函数
})
</script>
结果:

点击切换天气:

深度监视
<template>
<div id="root">
<h3>a的值为:{{ numbers.a }}</h3>
<button @click="numbers.a++">点我a++</button>
<h3>b的值为:{{ numbers.b }}</h3>
<button @click="numbers.b++">点我b++</button>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
let numbers = reactive({
a: 1,
b: 1
})
// 监视numbers
watch(numbers, (newValue, oldValue) => {
console.log('numbers被修改:a: ' + oldValue.a + "->" + newValue.a + ' | b: ' + oldValue.b + '->' + newValue.b);
}, {
deep: true, // 深度监视(注意:在vue3中这里的deep是false也无法关闭深度监视,历史遗留问题)
})
</script>
结果:(先点一次a++,再点一次b++)

可以看到Vue3的历史遗留问题:当监听的对象为Proxy代理对象(也就是对象)时,oldValue没有办法正确获取,都是最新值
【注意】:直接侦听一个对象时有 2 点需要注意:
- 无法正确的获取 oldValue,因为:新值变了同时旧值也跟着变了——改变一个对象里属性的值时,新值和旧值指向的是同一块内存区,所以无法拿到旧值,也可以理解为是浅拷贝的问题
- 默认开启深度监听(deep: true),且将 deep 置为 false 无效
因此,建议直接监听对象里的属性,而不是监听一整个对象
下面给出解决方法:
watch([() => numbers.a,() => numbers.b] , ([aNewValue, aOldValue],[bNewValue,bOldValue]) => {
console.log('numbers被修改:a: ' + aOldValue + "->" + aNewValue + ' | b:' + bOldValue + '->' + bNewValue);
}, {
immediate: true
})
结果:

自定义指令
全局定义
在main.js中定义,但需要把createApp(App).mount('#app')拆开,先创建实例,定义全局自定义指令,再绑定到App组件上
定义形式:
/* vue实例 */.directive(/* 指令名称 */,/* 指令配置 */)
实例(指令名称为v-focusOverall):
import { createApp } from 'vue'
import App from './App.vue'
// createApp(App).mount('#app') 需要拆开!
let app = createApp(App)
// 在app实例上定义全局自定义指令
app.directive("focusOverall",{
created() { // 在绑定元素的 attribute 或事件监听器被应用之前调用
console.log("Create - Overall")
},
beforeMount() { // 在元素被插入到 DOM 前调用
console.log("BeforeMount - Overall");
},
mounted() { // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用,大部分自定义指令都写在这里
console.log("Mounted - Overall");
},
beforeUpdate() { // 绑定元素的父组件更新前调用
console.log("BeforeUpdate - Overall");
},
updated() { // 在绑定元素的父组件及他自己的所有子节点都更新后调用
console.log("Update - Overall");
},
beforeUnmount() { // 绑定元素的父组件卸载前调用
console.log("BeforeUnmount - Overall");
},
unmounted() { // 绑定元素的父组件卸载后调用
console.log("Unmounted - Overall");
},
})
// 绑定app
app.mount("#app")
组件使用:
<template>
<div id="root">
<!-- ↓ 使用指令 -->
<div v-focusOverall v-if="showOrNot" >{{ msg }}</div>
<button @click="msg = '测试元素被改变了'">更新测试元素</button>
<button @click="showOrNot = !showOrNot;">卸载元素</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
let msg = ref('测试元素')
let showOrNot = ref(true)
</script>
结果:

点击更新元素:

点击卸载元素:

局部定义
形式:
注意:此处的指令名必须以
v字母开头并以首字母大写驼峰形式来定义,Vue3才会把它们当作自定义指令处理
const /* 指令名 */ = {
/* 指令配置 */
}
例子(指令名:v-focus):
<template>
<div id="root">
<div v-focus v-if="showOrNot" >{{ msg }}</div>
<button @click="msg = '测试元素被改变了'">更新测试元素</button>
<button @click="showOrNot = !showOrNot;">卸载元素</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
let msg = ref('测试元素')
let showOrNot = ref(true)
const vFocus = {
created() { // 在绑定元素的 attribute 或事件监听器被应用之前调用
console.log("Create")
},
beforeMount() { // 在元素被插入到 DOM 前调用
console.log("BeforeMount");
},
mounted() { // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用,大部分自定义指令都写在这里
console.log("Mounted");
},
beforeUpdate() { // 绑定元素的父组件更新前调用
console.log("BeforeUpdate");
},
updated() { // 在绑定元素的父组件及他自己的所有子节点都更新后调用
console.log("Update");
},
beforeUnmount() { // 绑定元素的父组件卸载前调用
console.log("BeforeUnmount");
},
unmounted() { // 绑定元素的父组件卸载后调用
console.log("Unmounted");
},
}
</script>
结果:

点击更新元素:

点击卸载元素:

自定义指令的参数表列
修改v-focus的内容如下
<template>
<div id="root">
<div v-focus:[arg]="msg" v-if="showOrNot" >{{ msg }}</div>
<button @click="msg = '测试元素被改变了'">更新测试元素</button>
<button @click="showOrNot = !showOrNot;">卸载元素</button>
</div>
</template>
<script setup>
import { ref , reactive} from 'vue'
let msg = ref('测试元素')
let arg = reactive({
arg_one: 1,
arg_two: 2,
})
let showOrNot = ref(true)
const vFocus = {
updated(el,binding,vnode,oldVnode) {
console.log("Update的参数表列");
console.log("el - 指令所绑定元素的DOM",el);
console.log("bind - 自定义指令接收的参数",binding);
console.log("vnode - Vue编译生成的虚拟节点",vnode);
console.log("oldVnode - 更新前的虚拟节点",oldVnode);
},
}
</script>
触发更新:

注意:
- 除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行
生命周期

总结相应对比:
vue2 -------> vue3
beforeCreate --------> setup(()=>{})
created --------> setup(()=>{})
beforeMount --------> onBeforeMount(()=>{})
mounted --------> onMounted(()=>{})
beforeUpdate --------> onBeforeUpdate(()=>{})
updated --------> onUpdated(()=>{})
beforeDestroy --------> onBeforeUnmount(()=>{})
destroyed --------> onUnmounted(()=>{})
activated --------> onActivated(()=>{})
deactivated --------> onDeactivated(()=>{})
errorCaptured --------> onErrorCaptured(()=>{})
/*
其中:
onActivated:被keep-alive缓存的组件激活时调用
onDeactivated:被keep-alive缓存的组件停用时调用
onErrorCaptured:当捕获一个来自子孙组件的错误时被调用,有三个参数:错误对象、发生错误的组件实例、一个包含错误来源信息的字符串;此钩子会返回false来阻止改错误继续向上传播
额外还增加了两个用于调试的钩子:
1.onRenderTracked
2.onRenderTriggered
它们会告诉你哪个操作跟踪了组件以及该操作的目标对象和键,会检测什么依赖造成了组件的重新渲染
三个参数:key(更新的键), target(操作的目标), type(操作类型)
*/
使用方法:
<template>
<div id="root">
<div>n的值:{{ n }}</div>
<button @click="n++">更新组件n值</button>
</div>
</template>
<script setup>
import { onBeforeMount , onMounted , onBeforeUpdate , onUpdated , onBeforeUnmount , onUnmounted , ref } from 'vue'
let n = ref(1)
onBeforeMount(() => {
console.log("onBeforeMount");
})
onMounted(() => {
console.log("onMounted");
})
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
})
onUpdated(() => {
console.log("onUpdate");
})
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
})
onUnmounted(() => {
console.log("onUnmounted");
})
</script>
结果:

点击更新:

获取标签的DOM(ref标记)
依然使用ref来标记标签,只是不再用this.$refs.xxx来得到标签DOM,而是通过let xxx = ref(null),来得到标签的DOM
注意:只有在该标签被挂载后,才能得到DOM,否则为null
<template>
<div id="root">
<div ref="div">n的值:{{ n }}</div>
<button @click="getDivRef" ref="btn">获取div更新n值</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
let n = ref(1)
let div = ref(null)
const getDivRef = () => {
console.log("ref实例",div);
console.log("DOM",div.value);
div.value.innerHTML = "使用DOM修改了"
}
</script>
结果:

点击按钮:

附:
上面的DOM明明先输出,后修改,为什么输出的是修改后的内容呢,我认为输出的是DOM的最新状态,而不是输出时DOM的状态
props(父组件与子组件通信):
props支持父子间通信,两种形式:
可以接收父组件给的数据,也可以接收父组件给的函数;
- 数据名称必须和父组件的数据名称保持一致
- 限制形式也可以不限制进行接收,按需求
// 1. 直接接收形式
const props = defineProps(['数据名称','数据名称',/*...*/])
// 2. 限制形式
const props = defineProps({
/* 数据名称 */ : {
type: /* 数据类型 */,
default: /* 该数据默认值 */,
require: /* 是否必须 */,
},
/* 数据名称 */ : /* 数据类型 */,
/* 数据名称 */ : {},
})
// 父组件的传递形式,在子组件的标签中传递数据或者函数(提供给子组件让它发送信息给父组件)
</* 子组件名称 */ /* 数据名称 */="数据内容" :/* 数据名称 */="数据变量(对象)" />
父组件给子组件传递数据
App组件:
<template>
<div id="root">
<appSon name="Bokey" :age="20" sex="男"></appSon>
</div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
</script>
appSon组件:
<template>
<h2>姓名:{{ props.name }}</h2>
<h2>年龄:{{ myAge }}</h2> <!-- 注意:这里是MyBokey组件的属性MyAge -->
<h2>性别:{{ props.sex }}</h2>
<button @click="updateAge">点我加一岁</button>
</template>
<script setup>
import { defineProps , ref} from 'vue'
// 通过defineProps获得父组件的通信内容(参数数据)
const props = defineProps({
name:String,
age:Number,
sex:String,
})
// 若子组件中需要修改内容,需要内化为子组件自己的数据;因为props的数据为只读数据,不可以修改!
let myAge = ref(props.age)
const updateAge = () => {
myAge.value++
}
</script>
结果:

点击按钮:

子组件给父组件传递数据
App组件:
<template>
<div id="root" style="padding: 20px;border: 1px red solid;">
<appSon :getSonData="getSonData"></appSon>
<div>儿子的数据:{{ sonData }}</div>
</div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
import { ref } from "vue"
let sonData = ref(null)
const getSonData = (data) => {
sonData.value = data
}
</script>
appSon组件:
<template>
<div style="padding: 10px;border: 1px black solid;">
<button @click="props.getSonData(myMsg)">点我发送数据给父组件</button>
</div>
</template>
<script setup>
import { defineProps, ref } from 'vue'
const props = defineProps(['getSonData'])
let myMsg = ref("我的重要信息,收好~")
</script>
结果:

点击按钮:

自定义事件
自定义事件一般用于子组件给父组件数据,形式:
// 1. 父组件提供函数给子组件
</* 子组件名称 */ @/* 自定义事件名 */="提供的函数名"/>
// 2. 子组件触发事件
import { defineEmits } from 'vue'
$emit('自定义事件名',/* 参数表列 */)
App组件:
<template>
<div id="root" style="padding: 20px;border: 1px red solid;">
<div>你好!{{ sonName }}</div>
<appSon @callFather="getSonName"></appSon>
</div>
</template>
<script setup>
import appSon from "./components/appSon.vue";
import { ref } from "vue"
let sonName = ref(null)
let getSonName = (data) => {
sonName.value = data
}
</script>
appSon组件:
<template>
<div style="padding: 10px;border: 1px black solid;">
<button @click="$emit('callFather',myName)">点我发送信息给父组件</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue'
let myName = "Bokey"
let $emit = defineEmits(['callFather'])
</script>
结果:

点击按钮:

附:
Vue3默认不支持全局事件总线,也就是说Vue3 没有和Vue2 $on 和 $emit 一样的的全局事件系统。
这是因为在 Vue 3 中,官方推荐使用 Composition API 以及更灵活的函数式组件,同时提倡使用更明确的通信方式
不过依然可以使用一些库来实现全局事件的管理(eg:mitt)
全局变量 / 方法挂载
在main.js中:
const app = createApp(App)
app.config.globalProperties.$/* 方法名 */ = function(/* 参数表列 */){ /* 函数体 */ }
app.config.globalProperties./* 变量名 */ = /* 变量值 */
app.mount("#app")
例:
mian.js:
const app = createApp(App)
app.config.globalProperties.$say = function(msg){ console.log('点击按钮了,信息:' + msg) }
app.config.globalProperties.globalText = "hi"
app.mount("#app")
app.vue:
<template>
<div id="root">
<!-- 在结构中,可以通过函数名或变量名直接访问全局,可不用代理 -->
<button @click="$say(globalText)">say hi</button>
</div>
</template>
<script setup>
// 获取getCurrentInstance得到vue实例代理,才能得到全局变量或函数
import { getCurrentInstance , onMounted} from 'vue';
const { proxy } = getCurrentInstance()
onMounted(() => {
console.log("vue的实例代理",proxy);
console.log(proxy.$say);
console.log(proxy.globalText);
})
</script>
结果(点击按钮):

插件使用
插件的使用都在main.js中引入插件文件就可以,如下
main.js:
import /* 插件名称 */ from "插件入口文件所在位置"
createApp(App).use(/* 插件名称 */).mount("#app")
// 使用多个插件:createApp(App).use(/* 插件名称 */).use(/* 插件名称 */)./* 其他插件... */.mount("#app")
VueX
防止你忘了,把VueX的工作流程放下面
State:VueX的数据存储模块,用来存储所有共享信息的状态
Actions:负责发送异步请求,请求后端数据,是VueX的行为模块
Mutations:负责加工Actions请求到的数据,是VueX的加工模块
Getters:负责进行数据展示的加工处理,是VueX的计算属性,具有缓存功能

基本使用流程:
(1)大致使用步骤:
1.在src/store/index.js文件中初始化Vuex的数据
2.组件中获取 VueX 的Store:import { useStore } from 'vuex';const store = useStore()
3.组件中使用 "store.state.数据名称"(组件JS中) 或 "{{store.state.数据名称}}"(组件HTML结构中) 读取/使用Vuex中的数据
4.组件中使用 "store.getters.数据名称"(组件JS中) 或 "{{store.getters.数据名称}}"(组件HTML结构中) 读取/使用Vuex中的计算属性
5.组件逻辑中使用 "store.dispatch('actions方法名',传递数据)" 或 "store.commit('mutations方法名,传递数据')" 修改Vuex中的数据
6.在src/store/index.js文件中定义相关的actions方法,mutations方法,getters属性
(2)src/store/index.js文件中actions和mutations的函数写法:
const actions = {
// 该方法使用 store.dispatch(函数名,数据参数) 调用
/*函数名*/(context,value) { // context是上下文对象,value是传递的参数(数据),value可以有也可以没有
// 可以在这里面发送异步网络请求
// 函数体中可以使用context.state获取当前Vuex中的数据
// 函数体中可以使用context.commit(函数名,数据参数)或者context.dispatch(函数名,数据参数) 推进Vuex进程
}
}
const mutations = {
// 该方法使用 store.commit(函数名,数据参数) 或者 context.commit(函数名,数据参数) 调用
/*函数名*/(state,value) { // state是Vuex的数据,value是传递的参数,value可以有也可以没有
// 函数体中利用actions部分得到的数据来处理加工或修改Vuex的数据
}
}
(3)附加一些事项:
1.在Vuex的actions中的方法我们习惯使用小写开头,以便区分
2.在Vuex的mutations中的方法我们习惯使用大写开头,以便区分
3.在Vue组件的逻辑结构(JS代码)中,我们可以直接使用"store.commit()"来调用Vuex的mutations部分方法(若不需要使用Vuex的actions部分的方法请求后端数据或不需要对数据进行判断校验等操作)
最后附上基本的src/store/index.js文件内容结构:
// 引入vuex的store工厂
import { createStore } from 'vuex'
export default createStore({
// 准备state-用于存储数据
state: { },
// 准备mutations-用于操作数据(state)
mutations: { },
// 准备actions--用于响应组件中的动作
actions: { },
// 准备getter--用于处理数据便于展示
getters: { },
// 使用modules引用模块和命名空间
modules: { }
})
例(Bokey和LZH组件共享Vuex中的age数据):
src/store/index.js文件( VueX 的store配置文件):
// 引入vuex的store工厂
import { createStore } from 'vuex'
export default createStore({
// 准备state-用于存储数据
state: {
age:20,
},
// 准备mutations-用于操作数据(state)
mutations: {
ADD_AGE(state,value){ // 年龄加n
state.age += value
},
REAL_AGE(state,value){ // 修改为真实年龄
state.age = value
},
ADD_AGE_ODD(state){ // 偶数才加1
state.age++
}
},
// 准备actions--用于响应组件中的动作
actions: {
realAge(context){ // 修改为真实年龄
// 模拟请求服务器,返回的值为20
var age = 20
context.commit('REAL_AGE',age) // 传递给mutations部分操作数据
},
AddAgeOdd(context){ // 偶数才加1
if (context.state.age % 2) return // 判断是否为奇数
context.commit('ADD_AGE_ODD') // 为偶数就传递给mutations部分操作数据
}
},
getters: {
ten_age(state){ // 计算属性:年龄的十倍
return state.age*10
}
},
// 使用modules引用模块和命名空间
modules: { }
})
Bokey组件:
<template>
<div class="Bokey">
<h1>我是Bokey</h1>
<h3>我的年龄:{{ store.state.age }}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="ageAdd">点我年龄+n</button>
<button @click="realAge">点我请求服务器,修改年龄为真实年龄</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let n = ref(1)
let ageAdd = () => {
store.commit('ADD_AGE',n.value)
}
let realAge = () => {
store.dispatch('realAge')
}
</script>
<style>
.Bokey {
background-color: skyblue;
}
</style>
LZH组件:
<template>
<div class="LZH">
<h1>我是LZH</h1>
<h3>我的年龄:{{ store.state.age }}</h3>
<h3>我年龄的10倍是:{{ store.getters.ten_age }}</h3> <!-- 计算属性 -->
<button @click="AddAgeOdd">点我只有年龄为偶数时+1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
const store = useStore()
let AddAgeOdd = () => {
store.dispatch('AddAgeOdd')
}
</script>
<style>
.LZH {
background-color: pink;
}
</style>
结果:

VueX模块化
模块化方式:
(1)在`src/store`目录下创建并编写模块文件(习惯名为"xxxOptions"),暴露并开启命名空间,文件中写基本结构:
export default {
namespaced: true, //开启命名空间
actions: { /* actions对话方法... */ }, // 该模块对应的action部分
mutations: { /* mutations对话方法... */ }, // 该模块对应的mutations部分
state: { /* state数据... */ }, // 该模块对应的state数据
getters: { /* getter数据... */ } // 该模块对应的getter数据
}
(2)在`src/store/index.js`文件中引入并使用模块:
import { createStore } from 'vuex'
import /* 模块引用名称 */ from './xxxOptions'
export default createStore({
modules: { // 使用modules引用模块和命名空间
/* 模块名 */:/* 模块引用名称 */
}
})
(3)组件中读取(命别名)state或getters数据的方式改变:
首先都需要获取store:
import { useStore } from 'vuex';
const store = useStore()
1.state数据的读取(命别名)的方式:
store.state./* 模块名 */./* 数据名 */
2.getters数据的读取(命别名)的方式:
store.getters['模块名/数据名']
(4)组件中配置对话的方式改变:
首先都需要获取store:
import { useStore } from 'vuex';
const store = useStore()
1.Actions对话调用方式:
store.dispatch('模块名/Actions对话函数名',/* 参数表列 */)
2.Mutation对话调用方式:
store.commit('模块名/Mutations对话函数名',/* 参数表列 */)
例:
store/ageOptions.js:
// Vuex年龄数据模块
export default {
namespaced: true, //开启命名空间
actions: {
realAge(context) {
var age = 20
context.commit('REAL_AGE', age)
}
},
mutations: {
ADD_AGE(state, value) {
state.age += value
},
REAL_AGE(state, value) {
state.age = value
},
ADD_AGE_ODD(state) {
state.age++
}
},
state: {
age: 20
},
getters: {
ten_age(state) {
return state.age * 10
}
}
}
store/personOptions.js
// Vuex人员模块
export default {
namespaced: true, //开启命名空间
actions: {
add(context, value) {
value = {
id: context.state.personList.length + 1,
name: value
}
context.commit('ADD', value)
}
},
mutations: {
ADD(state, value) {
state.personList.push(value)
}
},
state: {
personList: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
},
getters: {}
}
store/index.js:
import { createStore } from 'vuex'
// 引入各个模块
import ageOptions from './ageOptions'
import personOptions from './personOptions'
export default createStore({
// 使用modules引用模块和命名空间
modules: {
ageAbout: ageOptions,
personOptions,
}
})
Bokey.vue:
<template>
<div class="Bokey">
<h1>我是Bokey</h1>
<!-- 读取模块化的state和gtter -->
<h3>我的年龄:{{ store.state.ageAbout.age }}</h3>
<h3>年龄的十倍:{{ store.getters['ageAbout/ten_age'] }}</h3>
<h3 style="color:red">人员列表中的人数:{{ store.state.personOptions.personList.length }}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="ageAdd">点我年龄+n</button>
<button @click="realAge">点我请求服务器,修改年龄为真实年龄</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let n = ref(1)
// 调用模块化的mutations和actions
let ageAdd = () => {
store.commit('ageAbout/ADD_AGE', n.value)
}
let realAge = () => {
store.dispatch('ageAbout/realAge')
}
</script>
<style>
.Bokey {
background-color: skyblue;
}
</style>
LZH.vue:
<template>
<div class="LZH">
<h3>人员列表:</h3>
<ul>
<!-- 读取模块化的state -->
<li v-for="item in store.state.personOptions.personList" :key="item.id">
id:{{ item.id }},姓名:{{ item.name }}
</li>
</ul>
<!-- 读取模块化的state -->
<h3 style="color:red">Bokey的年龄为:{{ store.state.ageAbout.age }}</h3>
<input type="text" placeholder="请输入人员姓名" v-model="name" />
<button @click="add">点我添加该人</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex';
const store = useStore()
let name = ref(null)
// 调用模块化的mutations和actions
const add = () => {
store.dispatch('personOptions/add',name.value)
}
</script>
<style>
.LZH {
background-color: pink;
}
</style>
结果:

Vue Router
基本使用:
src/router/index.js:
import { createRouter,createWebHashHistory } from "vue-router";
// 定义路由规则
const routes = [
{
path: '...', // 路由路径
component: () => import('...') // 该路径下匹配的路由组件
},
]
// 创建路由对象及暴露
export default createRouter({
history:createWebHashHistory(), // 注意这里是方法!
routes
})
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 使用router
createApp(App).use(router).mount("#app")
然后就阔以在项目中使用啦~
下面是具体小栗子:
创建三个页面/src/page/xxxPage.vue(HomePage.vue,LoginPage.vue,RegisterPage.vue)
<!-- userHome中的结构 -->
<template>
<h2>I am Home Page</h2>
</template>
<!-- userLogin中的结构 -->
<template>
<h2>I am Login Page</h2>
</template>
<!-- userRegister中的结构 -->
<template>
<h2>I am Register Page</h2>
</template>
编写路由规则
const routes = [
{
path: '/',
component: () => import('../pages/HomePage')
},
{
path: '/login',
component: () => import('../pages/LoginPage')
},
{
path: '/register',
component: () => import('../pages/RegisterPage')
}
]
App.vue:
<template>
<div id="root">
<router-link active-class="chose" class="item" to="/" exact>首页</router-link>
<router-link active-class="chose" class="item" to="/login">登录</router-link>
<router-link active-class="chose" class="item" to="/register">注册</router-link>
<router-view></router-view>
</div>
</template>
结果(点击后就会显示不同内容):

编程式路由导航
由于 Vue3 没有
this,所以我们无法通过this.$router得到路由对象,下面给出了 Vue3 得到路由对象的方法;其实和 VueX 类似,就是通过一个工厂方法拿到该插件对象
App.vue:
<template>
<div id="root">
<router-link active-class="chose" to="/" exact>首页</router-link>
<router-link active-class="chose" to="/login">登录</router-link>
<router-link active-class="chose" to="/register">注册</router-link>
<button @click="gotoByPath('/')">点我跳转到首页</button>
<button @click="gotoByPath('/login')">点我跳转到登录</button>
<router-view></router-view>
</div>
</template>
<script setup>
import { useRouter } from "vue-router"
const router = useRouter()
let gotoByPath = (path) => {
router.push({
path: path,
})
}
</script>
结果(和普通跳转一样的跳转效果):

本文是Vue3从进阶到学习的笔记,介绍了Vue3相比Vue2的改变,如采用组合式API、数据响应式原理重实现等,还详细讲解了SetUp函数、响应式数据、计算属性、监视属性、自定义指令等内容,以及VueX、Vue Router等插件的使用。

2284

被折叠的 条评论
为什么被折叠?



