新特性
主要内容
- Vue3介绍
- setup函数
- Vue组件数据
- 定制化开发计算属性和监听器
- Vue定制化开发组件之间通信
一、Vue3介绍
Vue3优点:
- 最火框架,定制化开发它是国内最火的前端框架之一,
- 性能提升,定制化开发运行速度事vue2.x的1.5倍左右
- 体积更小,定制化开发按需编译体积比vue2.x要更小
- ==类型推断,定制化开发更好的支持Ts(typescript)==定制化开发这个也是趋势
- 高级给予,定制化开发暴露了更底层的API定制化开发和提供更先进的内置组件
- ★组合API (composition api) ,定制化开发能够更好的组织逻辑,封装逻辑,复用逻辑
基本使用
vite是什么:
- 定制化开发它是一个更加轻量(定制化开发热更新速度快,定制化开发打包构建速度快)的vue定制化开发项目脚手架工具。
- 相对于vue-cli定制化开发它默认安装的插件非常少,定制化开发随着开发过程依赖增多,定制化开发需要自己额外配置。
- 所以: 定制化开发在单纯学习vue3定制化开发语法会使用它,定制化开发后面做项目的时候我们还是使用vue-cli
- 什么是vite?—— 定制化开发新一代前端构建工具。
- 优势如下:
- 定制化开发开发环境中,定制化开发无需打包操作,定制化开发可快速的冷启动。
- 定制化开发轻量快速的热重载(HMR)。
- 定制化开发真正的按需编译,定制化开发不再等待整个应用编译完成。
vite基本使用:
-
创建项目
npm init vite-app 项目名称
或者yarn create vite-app 项目名称
-
安装依赖
npm i
或者yarn
-
启动项目
npm run dev
或者yarn dev
npm init vite@latest## 创建工程npm init vite-app <project-name>## 定制化开发进入工程目录cd <project-name>## 安装依赖npm install## 运行npm run dev
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
创建vue项目
- 定制化开发安装最新版本的Vue-cli工具,定制化开发最新版本工具已经提供Vue3-preview
npm install -g @vue/cli# ORyarn global add @vue/cli
- 1
- 2
- 3
定制化开发根实例初始化
在2.x中,通过new Vue()
定制化开发的方法来初始化
import Vue from 'vue'new Vue({ router, store, render: h => h(App)}).$mount('#app')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在3.x中Vue定制化开发不再是一个构造函数,通过createApp
定制化开发方法初始化
createApp(App).use(store).use(router).mount('#app')
- 1
组合式 API 定制化开发征求意见稿
选项API和组合API
- 代码风格:定制化开发一个功能逻辑的代码组织在一起(包含数据,函数…)
- 优点:定制化开发功能逻辑复杂繁多情况下,定制化开发各个功能逻辑代码组织再一起,定制化开发便于阅读和维护
- 缺点:定制化开发需要有良好的代码组织定制化开发能力和拆分逻辑能力
- 补充:为了能让大家较好的过渡到vue3.0的版本来,
也支持vue2.x选项API写法
更好的逻辑复用与代码组织
我们都因 Vue 简单易学而爱不释手,它让构建中小型应用程序变得轻而易举。但是随着 Vue 的影响力日益扩大,许多用户也开始使用 Vue 构建更大型的项目。这些项目通常是由多个开发人员组成团队,在很长一段时间内不断迭代和维护的。多年来,我们目睹了其中一些项目遇到了 Vue 当前 API 所带来的编程模型的限制。这些问题可归纳为两类:
- 随着功能的增长,复杂组件的代码变得越来越难以阅读和理解。这种情况在开发人员阅读他人编写的代码时尤为常见。根本原因是 Vue 现有的 API 迫使我们通过选项组织代码,但是有的时候通过逻辑关系组织代码更有意义。
- 目前缺少一种简洁且低成本的机制来提取和重用多个组件之间的逻辑。 RFC 中提出的 API 为组件代码的组织提供了更大的灵活性。现在我们不需要总是通过选项来组织代码,而是可以将代码组织为处理特定功能的函数。这些 API 还使得在组件之间甚至组件之外逻辑的提取和重用变得更加简单。我们会在设计细节这一节展示达成的效果。
更好的类型推导
二、setup函数
2.1 setup函数介绍
setup
是一个新的组件选项,作为组件中使用组合API的起点。- 从组件生命周期来看,它的执行在组件实例创建之前
vue2.x的beforeCreate
执行。 - 这就意味着在
setup
函数中this
还不是组件实例,this
此时是undefined
- 在模版中需要使用的数据和函数,需要在
setup
返回。
<template> <div class="container"> <h1 @click="say()">{{msg}}</h1> </div></template><script>export default { setup () { console.log('setup执行了') console.log(this) // 定义数据和函数 const msg = 'hi vue3' const say = () => { console.log(msg) } return { msg , say} }, beforeCreate() { console.log('beforeCreate执行了') console.log(this) }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
总结: setup
组件初始化之前执行,它返回的数据和函数可在模版使用。
2.2 组合API-生命周期
回顾.x生命周期钩子函数:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
认识vue3.0生命周期钩子函数(7)
setup
创建实例前onBeforeMount
挂载DOM前onMounted
挂载DOM后onBeforeUpdate
更新组件前onUpdated
更新组件后onBeforeUnmount
卸载销毁前onUnmounted
卸载销毁后
代码演示
<template> <div class="container"> container </div></template><script>import { onBeforeMount, onMounted } from 'vue'export default { setup () { onBeforeMount(()=>{ console.log('DOM渲染前',document.querySelector('.container')) }) onMounted(()=>{ console.log('DOM渲染后1',document.querySelector('.container')) }) onMounted(()=>{ console.log('DOM渲染后2',document.querySelector('.container')) }) },}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
注意:
组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同。
生命周期可以写多遍,可以把两个功能写在两个不同的生命周期里。去实现不同的逻辑
三、Vue组件数据
介绍
ref或者reactive替代data中的变量
在2.x中通过组件data的方法来定义一些当前组件的数据
data() { return { name: 'iwen', list: [], }}
- 1
- 2
- 3
- 4
- 5
- 6
在3.x中通过ref或者reactive创建响应式对象
import { ref,reactive } from "vue"export default { name: 'HelloWorld', setup(){ const name = ref("iwen") const state = reactive({ list:[] }) return{ name, list } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
定义响应式数据:
- reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。 {}, []
- 通常是用来定义响应式对象数据
<template> <div> <p>{{obj.name}}</p> <p><button @click="updateName">修改名字</button></p> </div></template><script>import { reactive } from 'vue'export default { setup () { // 普通数据 // const obj={ // name:'zs', // age:18 // } const obj=reactive({ name:'zs,我是响应式的', age:18 }) const updateName=()=>{ console.log('updateName....'); obj.name='lisi' } return {obj,updateName} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
toRef函数
toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且跟原来是数据的值是关联的。
如上例中,只想访问响应式对象obj中name属性,而不需要name.
通过对象解构出来的数据,不是响应式的。需要使用toRef
<template> <div> <p>{{ name }}</p> <p><button @click="updateName">修改</button></p> </div></template><script>import { reactive, toRef } from "@vue/reactivity";export default { setup() { // 定义响应式数据 const obj = reactive({ name: "zs", age: 18, }); // 模块中只使用name, // 注意:从响应式数据中解构出来的数据不再是响应式的!!! // let {name}=obj; let name = toRef(obj, "name"); const updateName = () => { console.log("updateName、、、、"); // toRef转换响应式数据包装成对象,value存放值的位置 name.value = "我是修改之后的lisi 啊"; }; return { name, updateName }; },};</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据。
toRefs是函数
-
toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
<template> <div> <p>{{ name }}</p> <p>{{ age }}</p> <p><button @click="updateName">修改</button></p> </div></template><script>import { reactive, toRef, toRefs } from "@vue/reactivity";export default { setup() { // 定义响应式数据 const obj = reactive({ name: "zs", age: 18, }); const obj2={...obj}; console.log(obj2); const obj3=toRefs(obj) console.log(obj3); const updateName = () => { console.log("updateName、、、、"); obj3.name.value='改了哦、、、' // 也可以改原来的数据,因为【值是关联的】 // obj.name='也可以改原来的数据' }; return { ...obj3, updateName }; },};</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据。
可以理解的是,用户会纠结用 ref 还是 reactive。而首先你要知道的是,这两者你都必须要了解,才能够高效地使用组合式 API。只用其中一个很可能会使你的工作无谓地复杂化,或反复地造轮子。 使用 ref 和 reactive 的区别,可以通过如何撰写标准的 JavaScript 逻辑来比较
// 风格 1: 将变量分离let x = 0let y = 0function updatePosition(e) { x = e.pageX y = e.pageY}// --- 与下面的相比较 ---// 风格 2: 单个对象const pos = { x: 0, y: 0,}function updatePosition(e) { pos.x = e.pageX pos.y = e.pageY}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 如果使用 ref,我们实际上就是将风格 (1) 转换为使用 ref (为了让基础类型值具有响应性) 的更细致的写法。
- 使用 reactive 和风格 (2) 一致。我们只需要通过 reactive 创建这个对象。
总结一下,一共有两种变量风格:
- 就像你在普通 JavaScript 中区别声明基础类型变量与对象变量时一样区别使用 ref 和 reactive。我们推荐你在此风格下结合 IDE 使用类型系统。
- 所有的地方都用 reactive,然后记得在组合函数返回响应式对象时使用 toRefs。这降低了一些关于 ref 的心智负担,但并不意味着你不需要熟悉这个概念。
组合API-ref函数
定义响应式数据:
- ref函数,常用于简单数据类型定义为响应式数据
- 再修改值,获取值的时候,需要
.value
- 在模板中使用ref申明的响应式数据,可以省略
.value
- 再修改值,获取值的时候,需要
代码演示
<template> <div class="container"> <div>{{name}}</div> <div>{{age}}</div> <button @click="updateName">修改数据</button> </div></template><script>import { ref } from 'vue'export default { name: 'App', setup () { // 1. name数据 const name = ref('ls') console.log(name) const updateName = () => { name.value = 'zs' } // 2. age数据 const age = ref(10) // ref常用定义简单数据类型的响应式数据 // 其实也可以定义复杂数据类型的响应式数据 // 对于数据未之的情况下 ref 是最适用的 // const data = ref(null) // setTimeout(()=>{ // data.value = res.data // },1000) return {name, age, updateName} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
模板在解析的时候,会判断是不是ref形式创建的数据,直接取出value。所以在模板中使用的时候,省略 .value
使用场景:
- 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
- 其他情况使用ref
3.2 组合API-reactive函数
定义响应式数据:
- reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。
演示代码:
<template> <div class="container"> <div>{{obj.name}}</div> <div>{{obj.age}}</div> <button @click="updateName">修改数据</button> </div></template><script>import { reactive } from 'vue'export default { name: 'App', setup () { // 普通数据 // const obj = { // name: 'ls', // age: 18 // } const obj = reactive({ name: 'ls', age: 18 }) // 修改名字 const updateName = () => { console.log('updateName') obj.name = 'zs' } return { obj ,updateName} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
3.3 知识运用案例
基本步骤:
- 记录鼠标坐标
- 定义一个响应式数据对象,包含x和y属性。 ==>响应式数据对象
- 在组件渲染完毕后,监听document的鼠标移动事件 =>生命周期,挂载之后
- 指定move函数为事件对应方法,在函数中修改坐标
- 在setup返回数据,模版中使用
- 累加1功能
- 定义一个简单数据类型的响应式数据 ===》ref(0)
- 定义一个修改数字的方法
- 在setup返回数据和函数,模板中使用
落的代码:
<template> <div class="container"> <div>坐标</div> <div>x: {{x}}</div> <div>y: {{y}}</div> <hr> <div>{{count}} <button @click="add">累加1</button></div> </div></template><script>import { onMounted, onUnmounted, reactive , ref, toRefs} from 'vue'const useMouse = () => { // 1. 记录鼠标坐标 // 1.1 申明一个响应式数据,他是一个对象,包含x y const mouse = reactive({ x: 0, y: 0 }) // 1.3 修改响应式数据 const move = (e) => { mouse.x = e.pageX mouse.y = e.pageY } // 1.2 等dom渲染完毕。去监听事件 onMounted(()=>{ document.addEventListener('mousemove', move) }) // 1.4 组件销毁,删除事件 onUnmounted(()=>{ document.removeEventListener('mousemove', move) }) return mouse}export default { name: 'App', setup () { const mouse = useMouse() // 2. 数字累加 const count = ref(0) const add = () => { count.value ++ //一定记得.value } //单纯 ...mouse 解构是不可以的,需要 toRefs,再解构 return { ...toRefs(mouse), count, add } }}</script><style scoped lang="less"></style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
封装:
const useMouse = () => { // 声明响应式数据 const mouse = reactive({ x: 0, y: 0, }); // 修改响应式数据 const move = (e) => { // console.log(e.pageX); // console.log(e.pageY); mouse.x = e.pageX; mouse.y = e.pageY; }; // 等dom渲染完毕。去监听事件 onMounted(() => { document.addEventListener("mousemove", move); }); // 组件卸载,删除事件 onUnmounted(() => { document.removeEventListener("mousemove", move); }); // 一定要return 出去!!!! return mouse;};export default { setup() { const mouse = useMouse(); // 定义响应式数据 let count = ref(0); const add = () => { count.value++; //一定记得.value }; // return { mouse }; //单纯 ...mouse 解构是不可以的,需要 toRefs,再解构 return { ...toRefs(mouse), count, add }; },};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
补充:ref属性:
Vue2 :
ref: 获取DOM元素;获取组件事件
Vue3: ref
回顾:
-
Vue3 新特性:
-
Vue2 选项API (data,methods,computd,。。。。。)
-
Vue3 组合API(方法)
-
创建根节点:Vue 2: new Vue({}) ;Vue3 :createApp(App).use(router).use(vuex)
-
Vue3 Setup():起点 beforeCreate() created() ===>this
-
Vue 2 : data,computd 定义的数据都是响应式
-
Vue3 定义响应式数据: ref():基本数据类型 reactive() :复杂数据类型
- const res= ref(null)
- toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且跟原来是数据的值是关联的。
- toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
-
ref :获取DOM 元素,组件
-
属性:
<h2 ref='myH2'> </h2>
- 1
- 2
- 3
const myH2=ref(null);onMounted(()=>{ console.log(myH2)})reutrn{myH2}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
-
Vue2 生命周期全部都支持
-
Vue3 on onBeforeMount onUnmounted Vue3的生命周期比Vue2都要早
-
数据代理方式:
-
Vue2: Object.defineproperty(obj,‘name’) ==> 会对 data, computed所有属性进行遍历代理
getter 依赖收集 [] ,setter
-
Vue3 Proxy 13种 ===》MDN
-
四、计算属性和监听器
4.1 计算属性
在2.x中
computed: { storeData () { return this.$store.state.storeData },}
- 1
- 2
- 3
- 4
- 5
在3.x中
import {computed} from 'vue'setup(){ const storeData = computed(() => store.state.storeData) return { storeData }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
高级点的用法:
const newMoney = computed({ // 访问, get() { // 该函数的返回值,就是计算属性的值 return money.value * 6; }, // 修改它的依赖的值 set(val) { money.value = val / 6; } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
4.2 watch监听器
在2.x版本的时候代码如下
export default { data() { return { counter: 0 } }, watch: { counter(newValue, oldValue) { console.log('The new counter value is: ' + this.counter) } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在3.x版本的时候代码如下
import { ref, watch } from 'vue'const counter = ref(0)watch(counter, (newValue, oldValue) => { console.log('The new counter value is: ' + counter.value)})
- 1
- 2
- 3
- 4
- 5
- 6
watch函数
定义计算属性:
watch函数,是用来定义侦听器的
监听ref定义的响应式数据
监听多个响应式数据数据
监听reactive定义的响应式数据
监听reactive定义的响应式数据,某一个属性
深度监听
默认执行
<template> <div class="container"> <div> <p>count的值:{{count}}</p> <button @click="add">改数据</button> </div> <hr> <div> <p>{{obj.name}}</p> <p>{{obj.age}}</p> <p>{{obj.brand.name}}</p> <button @click="updateName">改名字</button> <button @click="updateBrandName">改品牌名字</button> </div> </div></template><script>import { reactive, ref, watch } from 'vue'export default { name: 'App', setup () { const count = ref(0) const add = () => { count.value++ } // 当你需要监听数据的变化就可以使用watch // 1. 监听一个ref数据 // watch(count, (newVal,oldVal)=>{ // console.log(newVal,oldVal) // }) const obj = reactive({ name: 'ls', age: 10, brand: { id: 1, name: '宝马' } }) const updateName = () => { obj.name = 'zs' } const updateBrandName = () => { obj.brand.name = '奔驰' } // 2. 监听一个reactive数据 watch(obj, ()=>{ console.log('数据改变了') }) watch(()=>obj.brand, ()=>{ console.log('brand数据改变了') },{ deep: true, immediate: true }) return {count, add, obj, updateName, updateBrandName} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
五、Vue组件之间通信
5.1 组合API-父子通讯
父传子:
<template> <div class="container"> <h1>父组件</h1> <p>{{money}}</p> <hr> <Son :money="money" /> </div></template><script>import { ref } from 'vue'import Son from './Son.vue'export default { name: 'App', components: { Son }, // 父组件的数据传递给子组件 setup () { const money = ref(100) return { money } }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
<template> <div class="container"> <h1>子组件</h1> <p>{{money}}</p> </div></template><script>import { onMounted } from 'vue'export default { name: 'Son', // 子组件接收父组件数据使用props即可 props: { money: { type: Number, default: 0 } }, setup (props) { // 获取父组件数据money console.log(props.money) }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
子传父
<template> <div class="container"> <h1>父组件</h1> <p>{{money}}</p> <hr>+ <Son :money="money" @change-money="updateMoney" /> </div></template><script>import { ref } from 'vue'import Son from './Son.vue'export default { name: 'App', components: { Son }, // 父组件的数据传递给子组件 setup () { const money = ref(100)+ const updateMoney = (newMoney) => {+ money.value = newMoney+ }+ return { money , updateMoney} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
<template> <div class="container"> <h1>子组件</h1> <p>{{money}}</p>+ <button @click="changeMoney">花50元</button> </div></template><script>import { onMounted } from 'vue'export default { name: 'Son', // 子组件接收父组件数据使用props即可 props: { money: { type: Number, default: 0 } }, // props 父组件数据 // emit 从上下文context中解构出emit,触发自定义事件的函数+ setup (props, {emit}) { // 获取父组件数据money console.log(props.money) // 向父组件传值+ const changeMoney = () => {+ emit('change-money', 50)+ }+ return {changeMoney} }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
5.2 Provide / Inject
这里要值得一说的是Provide / Inject的应用,他们在3.x中得到了增强
- provide() 和 inject() 可以实现嵌套组件之间的数据传递。 ==》深层传递
- 这两个函数只能在 setup() 函数中使用。
- 父级组件中使用 provide() 函数向下传递数据。
- 子级组件中使用 inject() 获取上层传递过来的数据。
- 不限层级 父组件:
<template> <div> <provideAndInject /> </div></template> <script>import { provide } from "@vue/composition-api"; // 父组件引入 provideimport provideAndInject from "./components/provideAndInject"; // 引入子组件 export default { name: "app", components: { provideAndInject }, setup() { // provide('数据名称', 要传递的数据) provide("customVal", "我是父组件向子组件传递的值"); }};</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
子组件
<template> <div> <h3>{{ customVal }}</h3> </div></template> <script>// 子组件导入 injectimport { inject } from "@vue/composition-api"; export default { setup() { //调用 inject 函数,通过指定的数据名称,获取到父级共享的数据 const customVal = inject("customVal"); return { customVal }; }};</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
5.3 Vuex引用
在2.x版本对store对象的引用是通过this对象
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
在3.x版本对store引用变为createStore方法
import { createApp } from 'vue'import { createStore } from 'vuex'// Create a new store instance.const store = createStore({ state () { return { count: 0 } }, mutations: { increment (state) { state.count++ } }})const app = createApp({ /* your root component */ })// Install the store instance as a pluginapp.use(store)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
在组件中引用store对象
import { useStore } from "vuex"setup(){ const store = useStore(); const storeData = computed(() =>store.state.counter) return{ storeData }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
值得注意的是,我们在使用vuex大多会引用扩展运算符,如果使用扩展运算符根据目前官网vuex文档,仍然需要在methods中进行处理
代码组织能力
在2.x版本中,data和methods代码过多的时候,会不容易维护,过多的属性和过多的方法无论是阅读还是维护都给开发者造成了很大的困扰。 在3.x版本中,我们可以更好组织代码,例子如下:
我们分离一个数据处理文件helloworld.js
import { ref,reactive } from "vue"export function article(){ const currentArt = ref("测试数据"); const artLists = reactive({ name:"标题", content:"内容" }) function changeArt(){ currentArt.value = "新测试数据" } return{ currentArt, artLists, changeArt }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我们可以在分离一个网络请求文件处理NetWork.js
import { ref,reactive } from "vue"import axios from "axios"export function getBanner(){ const netState = reactive({ banner:{} }) function http(url){ axios.get(url).then(res =>{ netState.banner = res.data }) } return { netState, http }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我们可以在主业务文件中如下引入:
import { article } from "./HelloWorld";import { getBanner } from "./NetWork";export default { setup(props, ctx) { const { netState, http } = getBanner(); onMounted(() => { http("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php") }); const { currentArt, artLists, changeArt } = article(); return { currentArt, artLists, changeArt, netState }; },};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
状态管理
Vuex是一个很棒的状态管理库,它简单易懂,且与Vue集成良好。为什么不直接使用Vuex?因为即将发布的Vue3公开了底层的响应式系统,并引入了构建应用程序的新方法。新的响应式系统功能强大,可用于共享状态管理。
你需要一个共享状态吗
在某些情况下,多个组件之间数据共享困难,以至于需要集中的状态管理。情况包括:
- 多个组件使用相同数据
- 多个根模块需要单独接入数据
- 深层嵌套的组件需要与其它组件进行数据通讯
如果上述情况都不存在,那么很容易做决定:你不需要它。 如果存在上述情况呢?最直接的答案就是使用Vuex,这是一个经过实战检验的解决方案,而且效果不错。 如果不想添加另一个依赖项或者不想做过多的配置?那么现在就可以与Composition API一起,用新的Vue3内置的方法解决这些问题。
新的解决方案
共享状态必须符合两个标准:
- 响应式:当状态改变时,使用它们的组件也相应更新
- 可用性:可以在任何组件中访问
响应式
Vue3对外暴露了其响应式系统的众多函数,你可以使用reactive
函数创建一个reactive
变量(也可以使用ref
函数)
import { reactive } from 'vue';export const state = reactive({ counter: 0 });
- 1
- 2
- 3
从响应式函数返回的对象是一个Proxy
对象,它可以监听其属性的更改。当在组件模板中使用时,每当响应值发生变化时,组件就会重新渲染:
<template> <div>{{ state.counter }}</div> <button type="button" @click="state.counter++">Increment</button></template><script> import { reactive } from 'vue'; export default { setup() { const state = reactive({ counter: 0 }); return { state }; } };</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可用性
上面的示例非常适合单个组件,但多组件共享状态就不适用了。为了克服这个问题,你可以使用Vue3的provide
与inject
:
import { reactive, provide, inject } from 'vue';export const stateSymbol = Symbol('state');export const createState = reactive({ counter: 0 });export const setCounter = (num) =>{ createState.counter = num}export const useState = () => inject(stateSymbol);export const provideState = () => provide( stateSymbol, createState);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
当你将Symbol
作为键值传递给provide
时,该值将通过inject
方法使任何组件可用。键在提供和检索时使用相同的Symbol
名称。
如上图,如果你在最上层的组件上提供值,那么它将在所有组件中都可用,或者你也可以在应用的主文件上调用provide
import { createApp } from 'vue';import App from './App.vue';import { stateSymbol, createState } from './store';createApp(App).provide(stateSymbol, createState).mount('#app');<template> <div class="about"> <h1>This is an about page</h1> <p>{{ state.counter }}</p> <button @click="changeCounter">修改</button> </div></template><script>import { useState,setCounter } from "../store"export default{ setup(){ function changeCounter(){ setCounter(100) } return{ state:useState(), changeCounter } }}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
通过使用Vue3的响应式系统和依赖注入机制,我们已经从局部状态管理变为全局状态管理,本方案可以在小型应用中取代Vuex。 但是如果大型项目或者有使用Vuex的特性的情况下,这种方案就不合适了,例如:你不知道谁做的状态更改,因为状态可以直接更改,不受任何限制。 所以基于此,大型项目仍然需要使用Vuex。
5.5 无法使用EventBus
在2.x中通过EventBus的方法来实现组件通信
全局事件总线机制:基于自定义事件
var EventBus = new Vue()Vue.prototype.$EventBus = EventBus...this.$EventBus.$on() this.$EventBus.$emit()
- 1
- 2
- 3
- 4
- 5
在3.x中移除了$on, $off等方法(参考rfc),而是推荐使用mitt方案来代替
import mitt from 'mitt'const emitter = mitt()// listen to an eventemitter.on('foo', e => console.log('foo', e) )// fire an eventemitter.emit('foo', { a: 'b' })
- 1
- 2
- 3
- 4
- 5
- 6
-
安装:
npm i mitt -S
- 1
-
utils/mitter.js
mitt库默认导出一个函数,充当事件总线对象==》EventBus
import mitt from 'mitt'const emitter = mitt()export default emitter
- 1
- 2
- 3
-
需要的组件导入
import emitter from 'utils/mitter.js' //触发 emitter.emit('事件名','参数')
- 1
- 2
- 3
- 4
-
监听的组件
import emitter from 'utils/mitter.js'//监听emitter.on('事件名',(val)=>{ // val})// 第二种写法: 第一个参数写成 *,监听所有的事件触发 emmiter.on('*',(eventType,msg)=>{ console.log(`监听到的事件类型:${eventType},参数:${msg}`); })
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11