第十节:复习mixin/extends 和 开启Vue3的Composition Api(setup、reactiveApi、refApi、readonly)
一. Vue2.x的mixin和extends
1. mixin简介
(1). 目前我们是使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取。
(2). 在Vue2和Vue3中都支持的一种方式就是使用Mixin来完成:
A. Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。
B. 一个Mixin对象可以包含任何组件选项。
C. 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中。
2. mixin入门和合并规则
(1). 合并规则
(2). 代码实战
A. 封装方法common1.js
export const myCommon1 = { created() { console.log('---------------我是created2执行--------------') }, data() { return { msg: 'hello msg2', title: 'hello title2', name:'hello name2', }; }, methods: { Test1() { console.log('test1111111'); }, Test2() { console.log('test222222'); } } }
B. 组件代码
<template> <div> <h4>{{msg}}</h4> <h4>{{title}}</h4> <h5><button @click="Test1">点我1</button></h5> <!--下面代码引入common1.js的时候再打开,否则注释掉 --> <h4>{{name}}</h4> <h5><button @click="Test2">点我2</button></h5> </div> </template> <script> import { myCommon1 } from './common1.js' export default { mixins:[myCommon1], created() { console.log('---------------我是created1执行--------------') }, data() { return { msg: 'hello msg1', title: 'hello title1' }; }, methods: { Test1() { console.log('test1'); } } } </script>
运行结果:msg、title、和Test1方法,调用的都是组件自身定义的内容; name 和Test2方法则走的是common1.js中的内容。
3. mixin的全局混入
如果组件中的某些选项,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的mixin:
A. 全局的Mixin可以使用 应用app的方法 mixin 来完成注册;
B. 一旦注册,那么全局混入的选项将会影响每一个组件;
main.js中代码:
const app = createApp(App); // 测试mixin的全局混入 app.mixin({ created() { console.log('---------------我是created2执行--------------') }, data() { return { msg: 'hello msg2', title: 'hello title2', name:'hello name2', }; }, methods: { Test1() { console.log('test1111111'); }, Test2() { console.log('test222222'); } } }); app.mount('#app')
4. extends的使用
另外一个类似于Mixin的方式是通过extends属性:允许声明扩展另外一个组件,类似于Mixins;
抽离的组件
<template> <div> </div> </template> <script> export default { data() { return { msg: 'hello msg2', title: 'hello title2', name:'hello name2', }; }, methods: { Test1() { console.log('test1111111'); }, Test2() { console.log('test222222'); } } } </script> <style scoped> </style>
父组件
<template> <div> <h4>{{msg}}</h4> <h4>{{title}}</h4> <h5><button @click="Test1">点我1</button></h5> </div> </template> <script> import BasePage from './BasePage.vue'; export default { extends:BasePage, created() { console.log('---------------我是created1执行--------------') }, data() { return {}; }, methods: { } } </script> <style scoped> </style>
5. 总结
在开发中extends用的非常少,在Vue2中比较推荐大家使用Mixin,而在Vue3中推荐使用Composition API。
二. ComPosition Api-setup
1. Options Api的缺点
(1). 在Vue2中,我们编写组件的方式是Options API:
Options API的一大特点就是在对应的属性中编写对应的功能模块;
比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;(composition api中这些统统干掉,写在setup中了)
(2). 这种代码有一个很大的弊端:
当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
2. Composition Api简介
Vue Composition API(VCA)的思想就是:同一个逻辑关注点相关的代码收集在一起会更好。
在Vue组件中,Composition Api将所有的代码都写在setup函数中。 用它来替代之前所编写的大部分其他选项;比如methods、computed、watch、data、生命周期等等;
3. Composition Api入门-setup
(1). 参数
(2). 返回值
setup的返回值可以在模板template中被使用;即之前的data选项、methord都是在返回值暴露。
(3). 代码分享
子组件
<template> <div> <h3>我是child1组件</h3> <h4>{{msg}}</h4> <h4>{{counter}}</h4> <h4><button @click="AddNum()">加1</button></h4> </div> </template> <script> export default { props: { msg: { type: String, default: 'Hello Child1' } }, /* 参数说明: 1. props:获取父组件传递过来的props中定义的属性 2. context (或写成 {attrs, slots, emit} ) A.attrs:所有的非prop的attribute; B.slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到); C.emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件); 注:setup中不能使用this关键字 结果剖析: 这里点击按钮,页面上显示的值没有响应式的增加,console.log中增加了。 原因:对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作。 */ // setup(props, context) { setup(props, { attrs, slots, emit }) { // 1. 测试setup的相关参数 console.log(props.msg); console.log(attrs.id, attrs.class); console.log(slots); console.log(emit); // 2.编写相关业务 let counter = 100; const AddNum = () => { counter++; console.log(counter); }; // 3. 测试setup的返回值 return { title: 'hello title1', counter, AddNum } } } </script> <style scoped> </style>
父组件
<template> <div> <child1 msg="hello setup" id="j_child1" class="ypfClass"></child1> </div> </template> <script> import child1 from './child1.vue'; export default { components: { child1 }, data() { return {}; } } </script> <style scoped> </style>
剖析:
这里点击按钮,页面上显示的值没有响应式的增加,console.log中增加了。
原因:对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作。
注:setup中不可以使用this
三. reactiveApi
1. 用法
作用:为在setup中定义的数据提供响应式的特性。
代码如下:
<template> <div> <h3>我是child1组件</h3> <h4>{{myData.counter}}</h4> <h4><button @click="AddNum()">加1</button></h4> </div> </template> <script> import { reactive } from 'vue'; export default { props: { msg: { type: String, default: 'Hello Child1' } }, setup(props, { attrs, slots, emit }) { // reactiveApi写法 const myData = reactive({ counter: 100 }); const AddNum = () => { myData.counter++; console.log(myData.counter); }; return { myData, AddNum } } } </script>
2. 原理
这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
3. reactive Api对传入的类型是有限制的
对数组的补充说明
let data1=reactive([]); // data1数组无法做到响应式
let data2=reactive({
list1:[],
}) //data2.list1数组可以响应式
let data3=ref([]); // data3数组可以做到响应式
4. reactive其它Api补充
四. refApi
1. 简介
ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;它内部的值是在ref的 value 属性中被维护的;
注:
(1)在模板template中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;
(2)在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;
(3)模板中的解包是浅层的解包,如果放在对象里,则不能解包;但是我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包:
2. 实战
<template> <div> <h3>我是child1组件1111</h3> <!-- 1. refApi的在template中默认进行浅层解包,不写.vaule --> <h4>1. refApi的浅层解包:{{counter}}</h4> <!-- 2. 对象包裹不能解包 --> <h4>2. 对象包裹不能解包:{{myInfo1.counter.value}}</h4> <!-- 3. 将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包 --> <h4>3. 将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包:{{myInfo2.counter}}</h4> <h4><button @click="AddNum()">加1</button></h4> </div> </template> <script> import { reactive, ref } from 'vue'; export default { props: { msg: { type: String, default: 'Hello Child1' } }, setup(props, { attrs, slots, emit }) { //1. refApi写法 let counter = ref(100); //2.普通对象包裹,则不能解包 let myInfo1 = { counter } //3.reactive包裹ref对象,又会自动解包 let myInfo2=reactive({ counter }) const AddNum = () => { counter.value++; console.log(counter.value); }; return { counter, myInfo1, myInfo2, AddNum } } } </script>
3. 补充ref其它Api
代码分享:
<template> <div> <h3>{{info1}}</h3> <h3>{{info2}}</h3> <h3><button @click="Edit1">修改1</button></h3> <h3><button @click="Edit2">修改2</button></h3> <h3><button @click="Edit3">修改3</button></h3> </div> </template> <script> import { ref, shallowRef, triggerRef } from 'vue'; export default { setup(props, context) { // 1. ref响应式对象 var info1 = ref({ age1: 10 }); // 2. 浅层的ref对象 var info2 = shallowRef({ age2: 20 }); // 默认的ref支持响应式 var Edit1 = () => { info1.value.age1++; } // 下面的修改不支持响应式 var Edit2 = () => { info2.value.age2++; } // 下面的修改支持响应式 var Edit3= () => { info2.value.age2++; // 手动触发和 shallowRef 相关联的副作用 triggerRef(info2); } return { info1, info2, Edit1, Edit2, Edit3 } } } </script>
五. readonly
1. 背景
我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
(1). Vue3为我们提供了readonly的方法;
(2). readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
2. 使用规则
在开发中常见的readonly方法会传入三个类型的参数:普通对象、reactive返回的对象、ref的对象;
在readonly的使用过程中,有如下规则:
(1). readonly返回的对象都是不允许修改的;
(2). 但是经过readonly处理的原来的对象是允许被修改的;
A. 比如 const info = readonly(obj),info对象是不允许被修改的;
B. 当obj被修改时,readonly返回的info对象也会被修改;
C. 但是我们不能去修改readonly返回的对象info;
(3). 其实本质上就是readonly返回的对象的setter方法被劫持了而已;
核心代码分享:
setup() { //1. 普通对象 const info1 = { name: 'ypf' }; const readonlyInfo1 = readonly(info1); const EditContent1 = () => { readonlyInfo1.name = 'ypf2'; console.log(readonlyInfo1); }; //2.reactive对象 const info2 = reactive({ name: 'ypf' }); const readonlyInfo2 = readonly(info2); const EditContent2 = () => { readonlyInfo2.name = 'ypf2'; console.log(readonlyInfo2); }; //3.ref对象 const info3 = ref('ypf'); const readonlyInfo3 = readonly(info3); const EditContent3 = () => { readonlyInfo3.value = 'ypf2'; console.log(readonlyInfo3); }; return { EditContent1, EditContent2, EditContent3 }
}
运行结果:
3. 应用场景
在我们传递给其他组件数据时,往往希望其他组件仅仅使用我们传递的内容,但是不允许它们修改传递的内容,就可以使用readonly了。
如下:向home组件中传递了1个readonlyInfo对象,该对象只能被使用,不能被修改。
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。