vue3组合API(setup函数,系列二)
1,用vue2.x,实现一个todos
<template> <div> <form> <input type="text" v-model="stu.id"> <input type="text" v-model="stu.name"> <input type="text" v-model="stu.age"> <input type="submit" @click="addStu"> </form> <ul> <li v-for="(stu, index) in stus" :key="stu.id" @click="remStu(index)"> {{stu.name}} -- {{stu.age}} </li> </ul> </div> </template> <script> export default { name: 'App', data:function () { return { stus:[ {id:1, name:'zs', age:10}, {id:2, name:'ls', age:20}, {id:3, name:'ww', age:30}, ], stu:{ id:'', name:'', age:'' } // 新增功能1的数据 // 新增功能2的数据 } }, methods:{ remStu(index){ this.stus = this.stus.filter((stu, idx) => idx !== index); }, addStu(e){ e.preventDefault(); // Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target) const stu = Object.assign({}, this.stu); this.stus.push(stu); this.stu.id=''; this.stu.name=''; this.stu.age=''; } // 新增功能1的业务逻辑 // 新增功能2的业务逻辑 }, computed:{ // 新增功能1的业务逻辑 // 新增功能2的业务逻辑 }, watch:{ // 新增功能1的业务逻辑 // 新增功能2的业务逻辑 } } </script> <style> </style>
从代码中可以看到,vue2,数据和业务分离了,有新增功能,有删除功能(直接点 li 标签就删除它)。都是标准的方案,没啥可说的。
重点不是这个 Todo List 应用,重点是,Vue3 期望用 组合API 的方式来解决一个应用中,数据和功能分离的问题。即方法和 data 里的数据隔了一层进行调用的问题。
2. vue3组合api
<template> <div> <p>{{count}}</p> <button @click="myFn">按钮</button> </div> </template> <script> import {ref} from 'vue'; export default { name: 'App', // setup函数是组合API的入口函数 setup(){ // let count = 0; // 定义了一个名称叫做count变量, 这个变量的初始值是0 // 这个变量发生改变之后, Vue会自动更新UI let count = ref(0); // 在组合API中, 如果想定义方法, 不用定义到methods中, 直接定义即可 function myFn() { // alert(123); // 获取到count的值 // console.log(count.value); count.value += 1; } // 注意点: // 在组合API中定义的变量/方法, 要想在外界使用, 必须通过return {xxx, xxx}暴露出去 return{count, myFn} } } </script> <style> </style>
什么是 setup
,什么是 ref
,return
的又是啥玩意。
#ref
关于 ref
,可以参阅这篇文章,但我估计没有 ts 基础的人看不大明白,我直接抛结论:
Ref
是这样的一种数据结构:它有个key为Symbol
的属性做类型标识,有个属性value
用来存储数据。这个数据可以是任意的类型,唯独不能是被嵌套了Ref
类型的类型。
Ref
类型的数据,是一种响应式的数据。
Ref
写法简单,但也有弊端,它只能监听一些如数字、字符串、布尔之类的简单数据。复杂数据需要用到以后讲的 reactive
。(实际上也可以用 Ref
来封装对象,只不过在访问上多一层 value
,稍微麻烦了一些)。
#setup
setup
,就是我们最近老是能听到的 Composition API,组合式 API。关于这个 API 的细节,还请参阅官方文档,这里我只期望说一下简单的内容。
setup
选项应该是一个接受 props
和 context
的函数。此外,我们从 setup
返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
也就是说,setup
中创建并 return 的所有东西,都将被得到外部的解析,无论是过去在 data
中创建的数据也好,还是在 methods
创建的方法也好,都将变成允许被响应式地使用,仿佛 Vue2 中的这些 API 都被融合在一起了一样,而实际上 Vue3 也是为了实现这个目的。
有了这两点认识(ref
和setup
),我想上面的代码就变得简单了起来。回到刚刚的 Todo List,我们用这个 API 来实现试一下。
3. 用组合 API 来实现 Todo List
<template> <div> <ul> <li v-for="(item, index) in state.items" :key="item.name" @click="removeItem(index)"> No.{{index}} - {{item.name}} - {{item.age}} </li> </ul> </div> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { // ref函数注意点: // ref函数只能监听简单类型的变化, 不能监听复杂类型的变化(对象/数组) let state = reactive({ items: [ { name: 'GuanYu', age: '56' }, { name: 'ZhangFei', age: '54' }, { name: 'MaChao', age: '77' } ] }); function removeItem(i) { state.items = state.items.filter((currentValue, index) => index != i); }; return { state, removeItem }; } } </script>
这里我们首先实现了 remove
方法。现在实现 add
方法
<template> <div> <form> <input type="text" v-model="newState.item.name"> <input type="text" v-model="newState.item.age"> <input type="submit" @click="addItem"> </form> <ul> <li v-for="(item, index) in state.items" :key="item.name" @click="removeItem(index)"> No.{{index}} - {{item.name}} - {{item.age}} </li> </ul> </div> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { let state = reactive({ items: [ { name: 'GuanYu', age: '56' }, { name: 'ZhangFei', age: '54' }, { name: 'MaChao', age: '77' } ] }); function removeItem(i) { state.items = state.items.filter((currentValue, index) => index != i); }; let newState = reactive({ item: { name: '', age: '' } }); function addItem(e) { e.preventDefault(); state.items.push(Object.assign({}, newState.item)); newState.item.name = ''; newState.item.age = ''; } return { state, removeItem, newState, addItem }; } } </script>
和最开始我们写的 Todo List 在方法上基本一致。而这些都不是重点,重点是通过这种组合 API 的方式,允许我们对数据和方法进行组合的包装,就像这样
<template> <div> <form> <input type="text" v-model="newState.item.name"> <input type="text" v-model="newState.item.age"> <input type="submit" @click="addItem"> </form> <ul> <li v-for="(item, index) in state.items" :key="item.name" @click="removeItem(index)"> No.{{index}} - {{item.name}} - {{item.age}} </li> </ul> </div> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { let {state, removeItem} = originalData(); let {newState, addItem} = newData(state); return { state, removeItem, newState, addItem }; } } function originalData() { let state = reactive({ items: [ { name: 'GuanYu', age: '56' }, { name: 'ZhangFei', age: '54' }, { name: 'MaChao', age: '77' } ] }); function removeItem(i) { state.items = state.items.filter((currentValue, index) => index != i); }; return { state, removeItem }; } function newData(state) { let newState = reactive({ item: { name: '', age: '' } }); function addItem(e) { e.preventDefault(); state.items.push(Object.assign({}, newState.item)); newState.item.name = ''; newState.item.age = ''; }; return { newState, addItem }; } </script>
原始数据和方法直接被定义到 export default
外面了。值得注意的是,由于存在数据传值,记得要为方法添加参数,这里添加的参数是 state
,否则会找不到另一个方法的数据的。
再进一步,你可以将相关的数据和方法全都放在文件里,用export
和import
来进行交流
rem.js文件
import {reactive} from 'vue'; function useRemoveStudent() { let state = reactive({ stus:[ {id:1, name:'zs', age:10}, {id:2, name:'ls', age:20}, {id:3, name:'ww', age:30}, ] }); function remStu(index) { state.stus = state.stus.filter((stu, idx) => idx !== index); } return {state, remStu}; } export default useRemoveStudent;
add.js
import {reactive} from 'vue'; function useAddStudent(state) { let state2 = reactive({ stu:{ id:'', name:'', age:'' } }); function addStu(e) { e.preventDefault(); const stu = Object.assign({}, state2.stu); state.stus.push(stu); state2.stu.id = ''; state2.stu.name = ''; state2.stu.age = ''; } return {state2, addStu} } export default useAddStudent;
主文件app.vue
<template> <div> <form> <input type="text" v-model="state2.stu.id"> <input type="text" v-model="state2.stu.name"> <input type="text" v-model="state2.stu.age"> <input type="submit" @click="addStu"> </form> <ul> <li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)"> {{stu.name}} - {{stu.age}} </li> </ul> </div> </template> <script> import useRemoveStudent from './rem'; import useAddStudent from './add'; export default { name: 'App', setup() { let {state, remStu} = useRemoveStudent(); let {state2, addStu} = useAddStudent(state); return {state, remStu, state2, addStu} } } </script> <style> </style>