vue2 + Composition API 实践

响应式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了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容