关于javascript:探索-Vue-Composition-API

3次阅读

共计 7257 个字符,预计需要花费 19 分钟才能阅读完成。

摸索 Vue Composition API

它是什么

它是 Vue3 推出的重磅性能之一,是一种新的编写 vue 组件的形式,实现了相似于 React hooks 的逻辑组成与复用, 应用形式灵便简略,并且加强了类型推断能力,让 Vue 在构建大型利用上也有了用武之地。

为什么要应用

因为 OptionsAPI 的推出,其实就是满足于 JQ 时代的开发者疾速的上手接入,因为这个写法和和以前的写法很像。

然而,当下的 Web 利用越来越简单,前端会做更多的事件,应用 OptionsAPI 会带来十分多的困惑,仅仅应用 data、computed、methods、watch 这些,在绝大多数的状况,是能够了解的,因为心智累赘没有这么重,也是很容易了解的。然而,当组件变的更大时,业务变的更简单是,逻辑关注点的区域就会变长,而屏幕的显示是无限的。我须要在不同的区域来回跳转,当我新增一部分逻辑的时候,须要重复横跳,在代码中来回穿梭。

复用之殇

兴许你会说,把代码抽离进去啊,咱们有 mixins,咱们还有 extends,咱们不仅有组合,还有继承,然而,因为这是 vue 来实现的,并不是 native 的, 所以甚至没法 peekcmmand + 左键 只会通知你找不到定义

this.productList = this.generateProducts(productGroups);比方这行代码,很常见,然而如果我通知你,generateProducts 并不在以后文件下,你怎么搜?

置信聪慧的你会间接全局搜寻,如果这是一个微前端利用,你的办法来自于基座利用,是不是还得切利用搜或者是开一个工作区?

或者你对文件构造很相熟,先去 extends 里搜寻基类,再去 mixins 数组里挨个搜寻,最初再去全局 install 里搜,甚至去搜 Vue.prototype.xx,数据起源齐全含糊,this 是个黑盒,终于,你找到了那段办法,你晓得了它的逻辑,然而曾经深夜了。女朋友都睡着了。

命名抵触

聪慧的你,曾经解决了上述问题,同时,也学会了应用 mixins 这样的高端操作,代码能够复用起来了,灰溜溜地写了个 mixins

export default {data() {
    return {x: 0,};
  },
  methods: {// ...},
  computed: {double() {return this.x * 2;},
  },
};

而后另外一个你援用队友里的 mixin 里,也有这个,最终后果就是导致有程序运算呈现问题,须要解决命名抵触。所以你为了查这段问题,查到了次日深夜,女朋友又睡着了。

在 Vue 2 中,mixin 是将局部组件逻辑形象成可重用块的次要工具。然而,他们有几个问题:

  1. mixin 很容易发生冲突:因为每个个性的属性都被合并到同一个组件中,所以为了防止 property 名抵触和调试,你依然须要理解其余每个个性。
  2. 可重用性是无限的:咱们不能向 mixin 传递任何参数来扭转它的逻辑,这升高了它们在形象逻辑方面的灵活性

总结

对象式 API 存在的问题

  • 按 API 类型组织代码,复杂度高了后重复横跳
  • 不利于复用
  • 潜在命名抵触
  • 上下文失落

组合式 API 提供的的能力

  • 按性能 / 逻辑组织
  • 极易复用
  • 可灵便组合
  • 提供更好的上下文反对

根本用法

综上所述,如果咱们可能将与同一个逻辑关注点相干的代码配置在一起会更好。Vue 重磅推出了 CompositionAPI 来帮忙咱们做到这一点。

setup

  • 执行机制
    setup 是在创立组件实例并实现 props 初始化之后执行的,也是在beforeCreate 钩子之前 执行,无法访问 option(data、comupted、methods 等)选项,而 option 可应用 setup 中返回的变量。也就是说,即应用了 nuxt,咱们在 nuxt 给定的哪些 options 下,也能够应用 this.xxx 来拜访到 setup 返回的 xxx

    它只会执行一次,并且它的 return 的值,就是作为数据和逻辑之间的连接点。

  • 没有 this:在解析其余组件选项之前就曾经调用了 setup()
  • 承受两个参数:

    1. props:组件传参
    2. context:执行上下文,蕴含三个属性办法:attrs、slots、emit
    import {toRefs} from "vue";
    export default {
      props: {title: String,},
      setup(props) {console.log(props.title);
        const {title} = toRefs(props);
        console.log(title.value);
      },
    };

其中,props 是响应式的,当然你别解构它,一旦解构,就会失落响应状态。如果想要解构它,须要 toRefs 来平安的实现操作。

context 是一个一般的 JavaScript 对象,也就是说,它不是响应式的,这意味着你能够平安地对 context 应用 ES6 解构。

  • Ref/Reactive

    利用这两个函数,让数据成为响应式的,然而这两者也是有区别的,如果你应用的是值类型,你最好应用 ref,如果是对象类型,就用reactive

    其中,ref 的应用,须要 .value , 而 reactive 的对象,是会主动 UnWrap,不须要应用 .value,然而,不能解构,因为会让它失落响应状态,如果非要的话,toRefs

    import {ref, reactive} from "vue";
    
    let foo = 0;
    let bar = ref(0);
    
    foo = 1;
    bar = 1; // ts-error
    bar.value = 1;
    // 对于.value,官网说法就是能够利用它,良好的辨别数据是 ref 的还是一般的,当然,应用起来的确烦了一点
    
    const foo = {prop: 0};
    const bar = reactive({prop: 0});
    
    foo.prop = 1;
    bar.prop = 1;

当然,在 模板 中和 watch中,是不须要.value 的,它都会主动 unwrap

const counter = ref(0);

watch(counter, (count) => {console.log(count);
});

当然,有时候你会说,如果我一个函数,它既能够承受响应式数据,又能够接管非响应式数据,这怎么写?还好,官网同样提供了一个 unref 操作

如果传入一个 Ref,返回这个 .value,否则,原样返回

function unref(r) {return isRef(r) ? r.value : r;
}
  • 生命周期
选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

其实就是加上 on

export default {setup() {
    // mounted
    onMounted(() => {console.log("Component is mounted!");
    });
  },
};

因为 setup 是围绕 beforeCreatecreated生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在 setup 函数中编写。

  • Provide / Inject
    咱们也能够在组合式 API 中应用 provide/inject。两者都只能在以后流动实例的 setup() 期间调用

    因为用于在 setup 中,所以也更加灵便,能够把他们变成响应式的。

    当然,须要留神的一点就是,一旦变成响应式了后,批改对应的值的中央应该在数据提供的地位批改。约定大于配置。因为你在外面改也是能够的,然而那样会让数据变的不可控。

    // app.vue
    <template>
     <Marker />
    </template>
    <script>
    import {provide, reactive, ref} from 'vue'
    import MyMarker from './MyMarker.vue;
    export default {setup() {const location = ref("North Pole");
             const geolocation = reactive({
                 longitude: 90,
                 latitude: 135,
             });
    
             const updateLocation = () => {location.value = "South Pole";};
    
             provide("location", location);
             provide("geolocation", geolocation);
             provide("updateLocation", updateLocation);
       }
    }
    </script>
    
    // xx.vue
    import {inject} from 'vue'
    export default {setup() {const userLocation = inject('location', 'The Universe')
         const userGeolocation = inject('geolocation')
         const updateUserLocation = inject('updateLocation')
    
         return {
             userLocation,
             userGeolocation,
             updateUserLocation
         }
     }
    }

逻辑区域在一起的劣势

如果在 vue2.x 中,如果写一段带有申请 loading、error 视图的页面,咱们可能会这样写

<template>
  <div>
    <div v-if="error">failed to load</div>
    <div v-else-if="loading">loading...</div>
    <div v-else>hello {{fullName}}!</div>
  </div>
</template>

<script>
import {createComponent, computed} from 'vue'

export default {data() {
   // 集中式的 data 定义 如果有其余逻辑相干的数据就很容易凌乱
   return {
       data: {
           firstName: '',
           lastName: ''
       },
       loading: false,
       error: false,
   },
 },
 async created() {
     try {
       // 治理 loading
       this.loading = true
       // 取数据
       const data = await this.$axios('/api/user')
       this.data = data
     } catch (e) {
       // 治理 error
       this.error = true
     } finally {
       // 治理 loading
       this.loading = false
     }
 },
 computed() {
     // 没人晓得这个 fullName 和哪一部分的异步申请无关 和哪一部分的 data 无关 除非仔细阅读
     // 在组件大了当前更是如此
     fullName() {return this.data.firstName + this.data.lastName}
 }
}
</script>

数据和逻辑被扩散在了各个 option 中,这还只是一个逻辑,如果又多了一些逻辑,多了 data、computed、methods?如果你是一个新接手这个文件的人,你如何迅速的分辨分明这个 method 是和某两个 data 中的字段关联起来的?

那么如果,如果咱们用上了 vue3,配合一个库叫 vue-request

<template>
  <div>
    <div v-if="error">failed to load</div>
    <div v-else-if="loading">loading...</div>
    <div v-else>hello {{fullName}}!</div>
  </div>
</template>

<script>
import {computed} from "vue";
import useSWR from "vue-request";

export default {setup() {
    // useRequest 帮你治理好了取数、缓存、甚至标签页聚焦从新申请、甚至 Suspense...
    const {data, loading, error} = useRequest("/api/user", fetcher);
    // 轻松的定义计算属性
    const fullName = computed(() => data.firstName + data.lastName);
    return {data, fullName, loading, error};
  },
};
</script>

如许的简单明了。然而,如果逻辑仍然很多,有人会批,还是会写出意大利面条代码,那么就要看弱小的复用操作了。

复用

后面咱们说了,应用 mixins 的毛病,那么应用了组合 API 后,咱们复用就容易的多。

import {ref, onMounted, onUnmounted} from "vue";

export function useMousePosition() {const x = ref(0);
  const y = ref(0);

  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => {window.addEventListener("mousemove", update);
  });

  onUnmounted(() => {window.removeEventListener("mousemove", update);
  });

  return {x, y};
}

这是一个鼠标挪动的例子。在自定义 hook 中,咱们返回了 x,y 值,说到底,其实就是,咱们只关怀 xy 的值。

又或者是一个倒计秒的简略例子,我传进去一个值,返回残余秒数。

import {ref, onMounted, onUnmounted} from "vue";

export function useCountDown(initSec = 60) {const sec = ref(initSec);

  function tiktok() {sec.value--;}

  let timer = null;

  onMounted(() => {timer = setInterval(() => {tiktok();
    }, 1000);
  });

  onUnmounted(() => {timer && clearInterval(timer);
  });

  return sec;
}

在组件中应用他们

import {useMousePosition} from "./mouse";

export default {setup() {const { x, y} = useMousePosition();
    const sec = useCountDown(100);
    return {x, y, sec};
  },
};

几乎太容易了有没有。代码是不是也很简略,没有可恶的 this 了。同样的,你甚至能够 useA 外面应用 useB, useB 外面应用 useC

比照 React Hooks

首先,咱们先看下面在复用里写的 useCountDown 把它用 react hooks 写进去就是

import {useState, useEffect} from "react";

function useCount() {const [count, setCount] = useState(0);
  useEffect(() => {const timer = setInterval(() => {setCount((v) => v - 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return {count,};
}

export default function App() {const { count} = useCount();

  return <div>{count}</div>;
}

很像对不对。

其实 React Hook 的问题也有很多

  1. 不要在循环,条件或嵌套函数中调用 Hook,因为它是一个链表的构造
  2. useEffect 的依赖容易造成心智累赘,所有人浏览这段代码,都须要残缺的浏览完这些依赖触发的中央
  3. 因为闭包的起因,useEffect 等外部捕捉的,都是过期的变量。

而 Vue 以上三个问题都没有。并且因为 setup 函数只调用一次,性能上占优,当然,根本原因就是因为它的数据是响应式的,我间接改就能够读取到最新的值。

VueUse

一个应用了组合式 API,你大略会想用的一个 hooks 库。来自于官网团队。

如何在 Vue 2.x 中应用

我还须要 vuex 吗?

script-setup

最初总结

  1. compostionAPI的呈现,让应用 vue 写简单利用,不再变得顾此失彼
  2. 有了 compostionAPI, 能够让咱们的代码写的更好(熟练掌握各设计模式)、写的更优雅
  3. 对于一个新人来说,如果是从 setup 看起,会比看 options 来的更容易
  4. 将来 vuereact 会更像,要晓得,react 为了推出concurrentmode 做了大量的铺垫。不晓得 vue 会如何跟进相似策略。

参考文章

  1. VueUse 作者 Anthony Fu 分享可组合的 Vue
  2. 那个忙了一夜的 Vue3 动画很好,就是太短了
  3. Vue3 到底好在哪里?(和 React Hook 的具体比照)
  4. 介绍一下 Vue Conf 21 大会上:尤大提到 script setup 语法!
  5. Vue CompositionAPI 陷阱
  6. vue2.x vueuse demo
  7. vueuse 文档
  8. Vue-demi 帮忙你主动引入 vue2 还是 vue3 的库,当然,明确晓得的状况下没必要用。
  9. https://github.com/vuejs/comp… compostionAPI 文档
  10. https://github.com/vuejs/rfcs… script setup 2.x+3.x
  11. https://github.com/vuejs/rfcs… script setup 3.x
正文完
 0