vue2的几个痛点

  • 随着组件增长,组件会越来越大,越来越丑,因此可读性和可维护性变差
    options API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script>
    export default{
    data(){
    return {
    searchData:'',
    sortingData:'',
    }
    },
    methods:{
    searchFn(){

    },
    sortingFn(){

    }
    }
    }
    </script>

    composition API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script>
    export default{
    setup(){
    return {...useSearch(),...useSorting()}
    }
    }

    function useSearch(){

    }

    function useSorting(){

    }
    </script>

    对比两种方式的区别,当组件越来越大,options API 写起来如同琦玉的认真往返跳

  • 跨多个组件重用代码片段时,所有重用方法都有缺陷

    1. mixins:会导致属性名冲突,也不清楚这些mixins如何交互,而且还是不能够灵活对应功能进行重用
    2. mixins工厂:更加复杂,需要命名空间这种强大的规则和约束,并且仍然需要查看每个mixin的内部暴露了哪些属性
    3. 插槽:降低可读性,不够灵活
    4. 高阶组件:从没用过,听说就不是人用的东西
      Vue3 提供了composition Functions
      use/search.js
      1
      2
      3
      export default function useSearch(getResults){
      ...
      }
      use/sorting.js
      1
      2
      3
      export default function useSorting({input, options}){
      ...
      }
      search.vue
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import useSearch from '@/use/search'
      import useSorting from '@/use/sorting'

      export default {
      setup (){
      const productSearch = useSearch()
      const resultSorting = useSorting({})

      return {productSearch, resultSorting}
      }
      }
      唯一的缺点是,现在有两种不同的语法用于定义组件
      mixin是原来最常用的,这是一种交叉继承,说白了就是一个儿子有俩爹,一个爹教你会飞,一个爹教你会游,但是传宗接代后你不知这个技能来自谁,数据和方法不知道是从哪来的
      尤大:coposition API吊打mixin
  • 对Typescript支持有限

composition API 的基本姿势

setup和ref
setup没有this,setup是在beforeCreate和created之间执行
setup其实可以理解为做初始化,肯定是最早执行
ref多数使用场景是基本类型,何为响应式对象,必须要是对象,所以需要ref包装一下

composition API中methods基本用法

setup中想改变ref定义的响应式对象,需要用这个对象的value属性,模板上引用则不需要

composition API中computed基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
import {ref, computed} from "vue";
export default{
setup (){
const capacity = ref(3);
const attending = ref(["Tim","Bob","Joe"])
const spaceLeft = computed (() =>{
//要加value,因为用的事两个ref对象
return capacity.value - attending.value.length;

})
function increaseCapacity(){ capacity.value++ }
return { capacity, increaseCapacity, attending, spacesLeft };
}
}
</script>

computed返回值是一个readOnly的泛型ref,不能直接改,不然会报错,想改的话必须用get,set用法
如果computed放在reactive中使用,等于对ref进行了拆箱操作,则不需要后面跟value

composition API中reactive响应式语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
import {reactive, computed, toRefs} from "vue";
export default{
setup (){
const event = reactive({
capacity: 4,
attending = ref(["Tim","Bob","Joe"])
spaceLeft = computed (() =>{

return event.capacity.value - event.attending.length;

})
})

function increaseCapacity(){ capacity.value++ }
//可以直接返回event对象
return { event, increaseCapacity};
//或者用toRefs进行拆箱,模板引用时就不用引用event了
//return { ...toRefs(event), increaseCapacity};
}
}
</script>

何时用ref,何时用reactive?
理论上reactive更常用些,ref作用在于拆分,可以用于业务符合,或者就单个值,直接用ref就完了
isRef能判断一个变量是不是ref,unRef可以对ref进行拆箱
< script setup >语法糖有的话,就不用考虑这些事了

composition Function模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
import {reactive, computed, toRefs} from "vue";
export default{
setup (){
return useEventSpace();
}
}
function useEventSpace(){
const event = reactive({
capacity: 4,
attending = ref(["Tim","Bob","Joe"])
spaceLeft = computed (() =>{

return event.capacity.value - event.attending.length;

})
})

function increaseCapacity(){ capacity.value++ }
return { event, increaseCapacity};
}
</script>

也可以从不同文件引入
use/event-space.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {reactive, computed, toRefs} from "vue";
export default function useEventSpace(){
const event = reactive({
capacity: 4,
attending = ref(["Tim","Bob","Joe"])
spaceLeft = computed (() =>{

return event.capacity.value - event.attending.length;

})
})

function increaseCapacity(){ capacity.value++ }
return { event, increaseCapacity};
}

可以引用多个文件

1
2
3
4
5
6
7
import useEventSpace from "@/use/event-space"
import useMapping from "@/use/mapping"
export default {
setup(){
return {...useEventSpace(),...useMapping()}
}
}

但是这样会导致不能清晰看出哪些方法来自哪个composition function,可以用解构赋值的写法写

1
2
3
4
5
6
7
8
9
10
import useEventSpace from "@/use/event-space"
import useMapping from "@/use/mapping"
export default {
setup(){

const { capacity, attending, spacesLeft, increaseCapacity} = useEventSpace();
const {map,embedId} = useMapping();
return {capacity, attending, spacesLeft, increaseCapacity,map,embedId}
}
}

setup理应很清爽,就是各种功能模块调用
可复用的话,直接放到use文件夹下的JS文件内,不可复用直接拿到setup外,说白了只是在写js,不是在写vue(有hooks那味了,useState)
这样确实利于维护,但是跟hooks用法一样,但是底层实现完全不一样
为什么可以解决vuex的持久化?

lifecycle hooks生命周期钩子

生命周期钩子变动
beforeDestroy () -> beforeUnmount()
destroyed() -> unmounted()

在setup中使用生命周期钩子,只需要在钩子前增加”on”

1
2
3
4
5
6
7
8
9
10
11
import { onBeforeMount, onMounted } from "vue"
export default {
setup(){
onBeforeMount(()=>{
console.log("Before Mount!")
})
onMounted(()=>{
console.log("Mounted!")
})
}
}

其中缺少了beforeCreated和created,因为实际周期是setup在beforeCreated和created之间调用,也可以理解为放在setup中的代码,就是放在beforeCreated和created中

现在多了两个生命周期钩子:

  1. onRenderTracked: render function第一次访问响应式依赖时调用,一般调试时用于查看哪些依赖项在被追踪,
  2. onRenderTriggered: 触发新渲染时调用,一般调试时用于查看哪些依赖项触发了组件的重新渲染

setup函数中的生命周期钩子,例如onMounted,其实只是将onMounted传进去的这个函数保存在onMounted数组里,当mounted这个钩子真的触发时,就把数组里的函数都执行一遍,所以setup函数中有多个相同的生命周期钩子则会叠加,并不会替换(发布订阅模式,一个一个订阅,统一发布)

数据侦听watch

加入了一个新api,watchEffect

1
2
3
4
5
6
7
8
9
10
11
12
import { ref, watchEffect } from "vue"
import eventapi from "@/api/event.js"
export default {
setup(){
const searchInput = ref("")
const results = ref(0)
watchEffect(()=>{
results.value = eventApi.getEventCount(searchInput.value)
})
return {searchInput, results}
}
}

watchEffect会立即执行,参数函数中有响应式对象改变时,将会重新运行

当然watch还是可以用的

1
2
3
watch(searchInput,(newVal,oldVal) => {
...
})

这里如果watch一个ref,那么直接这样写就行了,如果watch一个reactive对象其中的一个值,那要以回调参数的返回值来写?(存在疑问)
当然还可以监听多个参数

1
2
3
watch([firstName,LastName],([newFirst,newFirst],([oldFirst,oldFirst]) => {
...
})

别忘了watch还有immediate选项

1
2
3
4
watch(searchInput,(newVal,oldVal) => {
results.value = eventApi.getEventCount(searchInput.value)

},{immediate:true})

共享状态 sharing state

B站资源 :
https://www.bilibili.com/video/BV12k4y1y75T?from=search&seid=165729587306922938