vue2 + Composition API 实践

2021-12-14  本文已影响0人  liuniansilence

响应式API

1、解构带来的响应式陷阱

我们习惯了ES6的对象解构风格,但这在composition- api里可能会有陷阱。因为结构可能会让你的响应式对象失去预期中的响应特性。

<template>
  <div id="app">
    {{count}}
    <button @click="addCount"></button>
  </div>
</template>
<script>
import { reactive } from '@vue/runtime-dom'
export default {
  setup() {
    const data = reactive({
      count: 0
    })
    function addCount() {
      data.count += 1
    }
    return {
        data,
        count: data.count,
        addCount
    }
  }
};
</script>

比如这里,button的click时候,不会得到预期的count的增加。因为setup执行返回的count并不是响应式的。
也就是说,虽然你的click事件确实的改变了data.count的值,但是这个值并没有响应式的去改变其他引用这个值的地方。怎么去验证我们这个解释呢?

我们可以开着chrome的vue插件,定位到你的组件。然后你可以点击一下button,可以看到count并没有变化,data呢?看起来好像也没有变化?这不是不符合逻辑吗?甚至我们在click的回调函数里打印一下发现是有执行data.count+1 这个操作的。
事实是,data.count确实是执行了的,但是因为不是reactive的,所以插件里没有及时更新这个新数据。如果你把插件先切到别的组件上去,再切回来。你就会发现,data.count是符合预期的!

click操作前的插件看到的数据:


image

我做了2次click后,现在插件里把光标切到别的组件上,再切回来。就可以看到data的变化:


image.png

只是引用了data.count的地方没有被更新,这就说明引用data.count的地方是非reactive的。

我们要要做的,就是改造一下引用data.count的地方。我们在setup里的返回,可以用computedtoRefs来改造一下返回值。

再看一下toRefs改造后的demo:

<template>
  <div id="app">
    {{count}}
    <button @click="addCount"></button>
  </div>
</template>
<script>
import { reactive } from '@vue/runtime-dom'
export default {
  setup() {
    const data = reactive({
      count: 0
    })
    function addCount() {
      data.count += 1
    }
    return {
      ...toRefs(data),
      addCount
    }
  }
};
</script>

第二个问题来了:toRefs一定是安全应对解构的方案么?

不是的,因为toRefs的结构是浅解构的,对于我们demo里的这种简单的对象是work的。但是如果是一个嵌套很深的复杂Object,还是会有解构后响应式断裂的问题。如果数据的层级比较复杂,建议使用computed

2、watch 和 watchEffect

watchEffect 很像React里的useEffect,是一个副作用函数。用法也基本一致。

watch的话,接收2个参数。第一个参数是watch的target,看ts结构,必须是一个ref对象或者computedRef对象;第二个参数是watch的回调函数。

踩坑:
目前@vue/composition-api里的watch有bug。在watch一个数组的时候,触发不了回调函数,从v1.1版本开始就有这个问题。已经有人提了issue,暂未解决(no longer works for multiple sources after v1.1)。

3、composition-api模仿React的useContext

React的hooks出来之后,有个很好用的东西就是Context,很像一个微型Redux,可以很好的跨组件传值,尤其是在组件粒度很细的时候,我们的组件间通信频率也会升高。

之前vue2的时代,其实一直有provideinject可以用。但是provideinject的对象一般是非响应式的。官网是这么记载的:

image.png

vue2的时候,我们一般不太注重如何把一个数据变成响应式的(也不是没有办法,比如Vue.observable(obj)可以把一个对象变成响应式的。如果我们把这个对象provide出去,那么传递的数据也就一直是响应式的了)。

vue3(或者vue2 + @vue/composition-api)后,我们更多的关注到了数据的reactive特性。比如用ref或者reactive关键字来构造一个响应式的对象。我们如果再用provide直接传递一个reactive的对象,岂不是可以模拟出类似React的useContext这样的结构?

外层Context层构造:
import { createApp, defineComponent, provider, inject, reactive, readonly, toRefs } from 'vue';
// Provider 包装组件
const MyConfigProvider = defineComponent({
    name: 'MyConfigProvider',
    props: ['prefixCls', 'title'],
    setup (props, { slots }: SetupContext) {
        const { prefixCls, title } = toRefs(props);
        const context = reactive({
          prefixCls,
          title
        });
        provide('myConfig', readonly(context));
        return () => slots.default?.();
    }
});

// 测试用子组件
const ChildComp = defineComponent({
    name: 'ChildComp',
    setup () {
        const myConfig = inject('myConfig', {});

        return () => (
            <>
                <p>{myConfig.prefixCls}</p>
                <p>{myConfig.title}</p>
            </>
        )
    }
});


// 调用Context层和子组件
const App = defineComponent({
    name: 'App',
    setup () {
        const state = reactive({
          prefixCls: 'myui',
          title: 'MyApp',
          i18n: (key: string) => key
        });
        return () => (
            <div id="#app">
                <MyConfigProvider {...state}>
                    <ChildComp />
                </MyConfigProvider>
            </div>
        )
    }
});


本身vue3(or vue2 + @vue/composition-api)也是支持hooks的。

这样我们就可以按照React hooks的开发习惯去给vue抽hooks了。

上一篇下一篇

猜你喜欢

热点阅读