參考網站
參考網站
回顧 Option API
在了解 Composition Api 之前,首先回顧下我們使用 Option Api 遇到的問題,我們在 Vue2 中常常會需要在特定的區域(data、methods、watch、computed…)編寫負責相同功能的代碼。
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
| <template>
<div id="app">
<input type="text" v-model="val" @keyup.enter="addTodo">
<ul>
<li v-for="todo in todos" :key="todo.id">{{todo.title}}</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return{
val:'',
todos:[
{id:0, title:'吃饭', done:false},
{id:1, title:'睡觉', done:false},
{id:2, title:'lsp', done:false},
]
}
},
methods:{
addTodo(){
this.todos.push({
id:this.todos.length,
title:this.val,
done:false
})
this.val = ''
}
}
}
</script>
|
Option Api 的缺陷
反覆橫跳
隨著業務複雜度越來越高,代碼量會不斷的加大;由於相關業務的代碼需要遵循 option 的配置寫到特定的區域,導致後續維護非常的複雜,代碼可複用性也不高。
- 相信大部分同學都維護過超過200行的 .vue 組件,新增或者修改一個需求,就需要分別在 data、methods、computed 裡修改,滾動條反复上下移動,我稱之為『反复橫跳』,比如我們簡單的加個拍腦門的需求加個累加器,這種寫代碼上下反复橫條的感覺,相信大家都懂的:
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
| <template>
<div id="app">
<h1 @click="add">LSP {{count}}号 double is{{double}}</h1>
<input type="text" v-model="val" @keyup.enter="addTodo">
<ul>
<li v-for="todo in todos" :key="todo.id">{{todo.title}}</li>
</ul>
</div>
</template>
<script>
import Counter from './counter'
export default {
mixins:[Counter],
data(){
return{
count:1,
val:'',
todos:[
{id:0, title:'吃饭', done:false},
{id:1, title:'睡觉', done:false},
{id:2, title:'lsp', done:false},
]
}
},
computed: {
double() {
return this.count * 2
}
},
methods:{
addTodo(){
this.todos.push({
id:this.todos.length,
title:this.val,
done:false
})
this.val = ''
},
add(){
this.count++
}
}
}
</script>
|
mixin 和 this
- 反覆橫跳的本質,在於功能的分塊組織,以及代碼量太大了,如果我們能把代碼控制在一屏,自然就解決了,vue2 裡的解決方案,是使用 mixin 來混合, 我們抽離一個 counter.js:
counter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| export default {
data() {
return {
count:1
}
},
computed: {
double() {
return this.count * 2
}
},
methods:{
add(){
this.count++
}
}
}
|
App.vue
1
2
3
4
5
6
7
8
| import Counter from './counter'
export default {
mixins:[Counter],
data(){
...
},
...
}
|
這樣確實拆分了代碼,但是有一個很嚴重的問題,就是不打開 counter.js,App.vue 裡的 this 上,count、add這些屬性,是完全不知道從哪來的,你不知道是 mixin
,還是全局 install
,還是 Vue.prototype.count
設置的,數據來源完全模糊,調試爽死你,這也是 option 的一個大問題,this
是個黑盒,template 裡寫的 count 和 double,完全不知道從哪來的。
如果有兩個 mixin
,就更有意思了,比如我們又有一個需求,實時顯示鼠標的坐標位置 x,並且有一個乘以 2 的計算屬性湊巧也叫 double,再整一個 mixin:
useMouse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| export default {
data() {
return {
x:0
}
},
methods:{
update(e){
this.x = e.pageX
}
},
computed:{
double(){
return this.x*2
}
},
mounted(){
window.addEventListener('mousemove', this.update)
},
destroyed(){
window.removeEventListener('mousemove', this.update)
}
}
|
這是一個獨立維護的 mixin,可能在 N 個地方用到,他根本不知道會不會有人和他衝突,然後用一下:
App.vue
1
2
3
4
5
6
| import Counter from './counter'
import Mouse from './mouse'
export default {
mixins:[Counter,Mouse],
......
}
|
兩個 mixin 裡都有 double 這個數,尷尬,看效果,lsp 的 count 被覆蓋了很尷尬,而且在 App.vue 這裡,你完全不知道這個 double 到底是哪個,調試很痛苦。
Composition Api
composition 就是為了解決這個問題存在的,通過組合的方式,把零散在各個 data、methods 的代碼重新組合,一個功能的代碼都放在一起維護,並且這些代碼可以單獨拆分成函數,顯然我們可以更加優雅的組織我們的代碼,函數。讓相關功能的代碼更加有序的組織在一起。
我們用vue3演示一下功能,具體api就不解釋了直接vue3文檔搞起就可以:
App.vue
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
| <template>
<div id="app">
<input type="text" v-model="val" @keyup.enter="addTodo">
<ul>
<li v-for="todo in todos" :key="todo.id">{{todo.title}}</li>
</ul>
</div>
</template>
<script>
import { reactive, ref, toRefs } from 'vue'
export default {
setup(){
let val = ref('')
let todos = reactive([
{ id:0, title:'吃饭', done:false },
{ id:1, title:'睡觉', done:false },
{ id:2, title:'lsp', done:false },
])
function addTodo(){
todos.push({
id: todos.length,
title: val.value,
done: false
})
val.value = ''
}
return {val, todos, addTodo}
}
}
</script>
|
利用函數我們可以把功能完整獨立的拆分成模塊或者函數,方便組織代碼,並且解決了 mixin 混亂的問題。
比如我們的累加器,抽離一個counter.js:
counter.js
1
2
3
4
5
6
7
8
9
10
| import { ref, computed } from 'vue'
export default function useCounter(){
let count = ref(1)
function add(){
count.value++
}
let double = computed(()=>count.value*2)
return { count, double, add }
}
|
直接使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
| import { reactive, ref, toRefs } from 'vue'
+ import useCounter from './counter'
export default {
setup(){
let val = ref('')
...
+ let { count,double,add } = useCounter()
return {
val, todos, addTodo,
+ count, double, add
}
}
}
|
再來一個鼠標位置也不在話下,而且可以很好地利用解構賦值的別名,解決 mixin 的命名衝突問題:
useMouse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import { ref, onMounted, onUnmounted, computed } from 'vue'
export default function useMouse(){
let x = ref(0)
function update(e){
x.value = e.pageX
}
let double = computed(()=>x.value*2)
onMounted(()=>{
window.addEventListener('mousemove', update)
})
onUnmounted(()=>{
window.removeEventListener('mousemove', update)
})
return { x, double }
}
|
模板裡直接用 doubelX:
1
2
3
4
5
6
7
| let { count, double, add } = useCounter()
let { x, double:doubleX } = useMouse()
return {
val, todos, addTodo,
count, double, add,
x, doubleX
}
|
script setup
不過有的同學可能,還有一個小小的吐槽,那就是 setup 函數最後的 return 也是集中的,如果行數太多,一樣會橫條一下下。
這個好解決,因為本身我們可以把 todos 也抽離成函數,這樣 setup 就全部是數據的來源,非常精簡絲滑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import useCounter from './counter'
import useMouse from './mouse'
import useTodo from './todos'
export default {
setup(){
let { val, todos, addTodo } = useTodo()
let { count, double, add } = useCounter()
let { x, double:doubleX } = useMouse()
return {
val, todos, addTodo,
count, double, add,
x, doubleX
}
}
}
|
是不是賊爽呢,如果有些同學就是不想啥都抽離,還是覺得統一 return 很麻煩, 我們可以使用 vue3 的 setup script 功能,把 setup 這個配置也優化掉一個功能 export 一次:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <script setup>
import useCounter from './counter'
import useMouse from './mouse'
import useTodo from './todos'
let { val, todos, addTodo } = useTodo()
export { val, todos, addTodo }
let { count, double, add } = useCounter()
export { count, double, add }
let { x, double:doubleX } = useMouse()
export { x, doubleX }
</script>
|
具體看這裡