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>