vue3之composition-api的使用(包含watch watchEffect)

是什么

composition-api官方介绍

  • vue2:options-api,组件按照选项组织,就是将组件各个部分严格写在methods、computed、watch、data等等里面(特定的区域写特定的代码);

  • vue3:composition-api,组件按照逻辑组织,按功能点去写,实现一个功能的逻辑都写在一块。

组合式api就像是将原本散落在组件实例里的各个properties都一起放到setup里进行调用(为什么是调用,因为定义都放在组件实例外面了 如watch属性可以用ref实现,mount钩子函数可以用onMounted函数来实现)。

为什么

基于vue2的大型组件很难维护

一个功能往往需要在不同的vue配置项中定义属性和方法,比较分散,项目小还好,清晰明了,但是项目大了后,一个methods中可能包含20多个方法,往往分不清哪个方法对应着哪个功能。

基于vue2封装的组件逻辑复用困难

你想,你都在一个组件的各项配置中去写了属性和方法了,何来复用一说。

首先来说一下为什么会出现跨组件代码复用的场景:投放中台有许多功能相似的组件(那为什么不写成同一个组件咧,是因为它们模板差异很大 写成同一个组件不方便维护),所以肯定会有可以复用的逻辑能抽离出来。举个很简单的例子,很多组件都会需要用到解析从投放中台获取到的数据 的逻辑,但如果在每个组件里都去写一次肯定代码冗余度会很大,所以需要复用。

原来通常会用到mixin去实现跨组件代码复用,但mixin有几个很大的缺点:

  • 属性和方法的来源变得难以确定:当引入了多个mixin或mixin中间有嵌套观喜的时候,对于某个属性or方法到达是从哪个mixin引入的,就难确定了;

  • 命名冲突:如果不指定命名空间,那从mixin中引入的属性和方法默认会在同一个命名空间中,这样后引入的mixin会覆盖前引入的mixin中的属性or方法(这还不是最恐怖的,覆盖了引入的第三方包中的属性or方法才是最恐怖的);

  • 不能避免隐式属性:mixin也是个小组件,可能那个组件里面会有希望被复用的逻辑和不希望被复用的逻辑,但一旦被mixins里写了,那都能够被外面用到。

vue2支持typescript有限

Vue 依靠一个简单的 this 上下文来暴露 property,现在使用 this 的方式是比较微妙的。(比如 methods 选项下的函数的 this 是指向组件实例的,而不是这个 methods 对象)。换句话说,Vue 现有的 API 在设计之初没有照顾到类型推导,这使适配 TypeScript 变得复杂。

怎么做

其实要做的事情也很简单,我们只需要把之前散落在组件里各个部分的代码按逻辑功能分为一个个区块写在setup里就可以了(通常这一个个区块会变为一个个函数,方便复用和类型检查)。

先看看setup函数概况:

setup(props, context)

  • props是响应式属性,不可普通的使用ES6解构 这样会破坏响应式,可以用vue的toRefs函数

  • context是普通的JS对象,可以使用结构,里面有实例的三个属性 attrs slots emit。

记住,setup的执行时间在生命周期钩子beforeCreate之前,所以当然拿不到this实例,如果实在要拿就只能在mounted里拿(不是onMounted 而是真正的mounted)。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法

响应式声明

响应式很好理解,就是当改变一个数据值的时候 所有引用到它的地方都会做出相应变化。之前写在data里的属性就是我们需要响应式声明的对象,因此那些值都需要进行响应式声明,先看看有哪几种响应式声明的方法:

  • ref:vue3 出现的,我们可以将一个原始数据类型(primitive data type)转换成一个带有响应式特性的数据类型.只能是原始数据类型,若是对象的话不能监听到深层变化。(原始数据类型共有7个,分别是String、Number、BigInt、Boolean、Symbol、Null、Undefined)

  • reactive:vue3 出现的,我们可以将一个复杂数据类型转换成一个带有响应式特性的数据类型,也就是说我们可以监听到其深层属性的变化;

简单来说就是两种,简单数据类型用ref,复杂数据类型用reactive。

// 返回数据部分
const bannerDataStat = ref('fprom.big_fund_card.fund');

const bannerStatConfig = ref('{}');
const bannerFundInfo = reactive(
  ref({
    link: '',
    mainTitle: '',
    subTitle: '',
    otherTips: '',
    rate: [],
    rateText: '',
  }),
);
const diamondImgStyle = ref('');
const dpFundCardStyle = ref('');
const { productCard } = toRefs(props);
return {
   bannerDataStat,
   bannerFundInfo,
   bannerStatConfig,
   diamondImgStyle,
};

响应式声明完毕后return出去,整个组件都能用到了。(注意在setup里面使用响应式声明的数据时要用,value去赋值和取值)。

使用生命周期钩子

setup中可以注册生命周期钩子,在原来的生命周期函数名前加 on 即可,从下面的截图中可以看到。(注意,setup函数里没有beforeCreate和created生命周期的注册函数,任何要写在这两个生命周期函数中的代码都可以直接写在seup中)

父组件传值响应式解构
toRefs:我们在setup里能拿到的props是响应式的,但直接将其解构的话拿到的属性会失去响应式,但用toRefs函数处理再解构的属性值均为响应性的ref属性(toRefs的原理是 它会将对象的key值都转化为带.value的ref对象)

使用toRefs函数解构出来的productCard对象也是响应式的,同时在onMounted里用watch去监听它的变化,若变化了我们重新渲染相关数据。

const { productCard } = toRefs(props);

// mounted钩子函数部分
onMounted(async () => {
  console.log('productcard---', productCard);

  watch(productCard, async (newData) => {
    console.log('======', newData);
    const cardRes = await useProductCardData(newData.data);

    bannerStatConfig.value = cardRes.bannerStatConfig;
    bannerFundInfo.value = cardRes.bannerFundInfo;
    diamondImgStyle.value = cardRes.diamondImgStyle;
    dpFundCardStyle.value = cardRes.dpFundCardStyle;
  });
});

注意到我们在这里用一个useProductCardData函数来进行所有的数据处理,包括了数据拉取 数据标准化,得到的结果再重新交给组件去渲染使用。每次父组件传值发生更新我们都需要更新一次子组件的数据。

同时我们将useProductCardData函数写在另外的文件里去引入,这种函数式的写法更加方便了逻辑复用和类型检查

可以看到,这样对组件进行改造之后,真正写在组件内部的逻辑得到了大大简化,数据请求、处理的细节部分都放到了另外的函数中,组件只需要负责声明需要的变量和函数调用即可。

说一说watch和watchEffect

1、watch是惰性执行,也就是只有监听的值发生变化的时候才会执行,但是watchEffect不同,每次代码加载watchEffect都会执行(忽略watch第三个参数的配置,如果修改配置项也可以实现立即执行)

2、watch需要传递监听的对象,watchEffect不需要,它自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(vue3开始可以同时指定多个)。

3、watch只能监听响应式数据:ref定义的属性和reactive定义的对象,如果直接监听reactive定义对象中的属性是不允许的,除非使用函数转换一下

4、watch如果监听reactive定义的对象是不起作用的,只能监听对象中的属性。

都会 会返回一个用于停止这个监听的函数

项目中注意点

由于setup函数执行的时机很早(在create之前)所以有时候数据什么的都还没有获取到,有些操作(这里通常是要异步请求的数据的请求操作需要的参数还没到)也无法执行,所以若需要通过异步请求来获取数据的操作,必须要监听该获取到的数据。

其实主要是,在setup的onMounted里执行操作的时候 某些数据还没获取到,所以没有需要的数据。

可以用上面的watch或watchEffect:

  • watch主要是可以监听指定的对象,比如我们需要当父组件传来的某个数据到达时发送某个请求获取数据;

  • watch无法直接监听对象属性的变化,第一个参数要写成一个函数返回对象的某个属性才可,如 ()=>obj.v1

  • watchEffect无需指定监听的对象,只要我们在回调函数里用到了某个数据对象,其发生变化后就能执行相应的操作。

这两个函数执行后都会返回一个可以stop该监听的函数,在异步请求数据返回后通常会执行该stop函数。

onMounted(async () => {
    const stopWatch = watchEffect(async () => {
        if (Object.keys(assetData.value).length && Object.keys(FUND_CFG.value).length) {
            finalList.value = list ? await useEntryListData(list, assetData, FUND_CFG, props?.planListRes) : list;
​
            stopWatch();
       }
   });
});
    
onMounted(() => {
    const stopWatch = watch(unionAsset, async () => {
        if (Object.keys(unionAsset.value).length !== preLength) {
            const urlParam = getUrlParam();
​
            preLength = Object.keys(unionAsset.value).length;
            unionFunds.value = (await useUnionData(urlParam, unionAsset.value, 'combination')) || {};
​
            stopWatch();
       }
   });
});

这里可以看到,上面用watch去监听父组件传值变化和下面这两个有些不一样,监听props传值的时候没有去stop监听,这是因为父组件传值发生更新是很常见的情况,而且更新了的话子组件是必然也要更新的,下面两种情况中涉及到的可能更新的数据 只可能在它们初始化的时候更新一次,后面不会再更新了,所以就需要stop掉来减少资源消耗。

posted @ 2022-09-05 16:47  TRY0929  阅读(518)  评论(0编辑  收藏  举报