美文网首页
vue2 + Composition API 实践

vue2 + Composition API 实践

作者: liuniansilence | 来源:发表于2021-12-14 16:16 被阅读0次

    响应式API

    • ref
    • unref
    • toRef
    • toRefs
    • isRef
    • customRef
    • shallowRef
    • triggerRef
    • computed
    • watch
    • watchEffect
    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了。

    相关文章

      网友评论

          本文标题:vue2 + Composition API 实践

          本文链接:https://www.haomeiwen.com/subject/jptyfrtx.html