做了一夜动画,就为让大家更好的理解Vue3的Composition Api
共 5293字,需浏览 11分钟
· 2021-01-21
转自:掘金 - 蜗牛老湿_大圣
这个文章其实很简单, 只要能说明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文件也会逐渐增大
![](https://filescdn.proginn.com/b43a4375a2a21843286652def3adc939/e389bca6c06cf9883a84d7102ba0d8a3.webp)
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演示一下功能,具体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>
具体看这里:
https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md
- EOF -
专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!
长按关注"前端达人"
![](https://filescdn.proginn.com/2d613a1af6babca967e6ec5425cbe6d1/741584725863649ef3638a846ff5d637.webp)