BACK
Featured image of post 圖解 Option API vs Composition API

圖解 Option API vs Composition API

Composition API 是以邏輯功能來分割程式碼,像是寫原生JavaScript 一樣,我們會把實現同樣功能的程式碼寫在附近。 但Options API 是以程式碼的性質來分割程式碼,例如有3個函式各自用來實現不同功能,但如果它們都是 computed 函式,就全都一起寫在 computed 屬性裏。

參考網站
參考網站


回顧 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>

具體看這裡


comments powered by Disqus