天天看點

一張動圖了解Vue3的Composition Api

這個文章其實很簡單, 隻要能說明composition的好處,就是極好的,我們用一個非常簡單的萬金油場景,比如我們有一個非常簡單的to do list

回顧Option

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

複制

需求複雜之後,就會多出watch,computed,inject,provide等配置,這個.vue檔案也會逐漸增大

Option的缺陷--反複橫跳

相信大部分同學都維護過超過200行的.vue元件,新增或者修改一個需求,就需要分别在data,methods,computed裡修改 ,滾動條反複上下移動,我稱之為『反複橫跳』 比如我們簡單的加個拍腦門的需求 加個累加器 ,這種寫代碼上下反複橫條的感覺, 相信大家都懂的, 

動畫示範

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

複制

Option的缺陷:mixin和this

反複橫跳的本質,在于功能的分塊組織,以及代碼量太大了,如果我們能把代碼控制在一屏,自然就解決了,vue2裡的解決方案,是使用mixin來混合, 我們抽離一個counter.js

export default {
  data() {
    return {
      count:1
    }
  },
  computed: {
    double() {
      return this.count * 2
    }
  },
  methods:{
    add(){
      this.count++
    }
  }
}           

複制

在App.vue中

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命名沖突

如果有兩個mixin,就更有意思了,比如我們又有一個需求,實時顯示滑鼠的坐标位置x,并且有一個乘以2的計算屬性湊巧也叫double,再整一個mixin

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個地方用到,他根本不知道會不會有人和他沖突,然後用一下

import Counter from './counter'
import Mouse from './mouse'
export default {
  mixins:[Counter,Mouse],
  ...... 
}           

複制

兩個mixin裡都有double這個數,尴尬,看效果 ,lsp的count被覆寫了 很尴尬,而且在App.vue這裡,你完全不知道這個double到底是哪個,調試很痛苦

Composition

composition就是為了解決這個問題存在的,通過組合的方式,把零散在各個data,methods的代碼,重新組合,一個功能的代碼都放在一起維護,并且這些代碼可以單獨拆分成函數 ,也就是大帥的這兩個gif

一張動圖了解Vue3的Composition Api
一張動圖了解Vue3的Composition Api

我們用vue3示範一下功能,具體api就不解釋了 直接vue3文檔搞起就可以

<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

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}
}           

複制

直接使用

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的命名沖突問題 mouse.js

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

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就全部是資料的來源,非常精簡絲滑

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一次

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

複制