有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

1. Vue 3和Composition API的情况

Vue 3曾经公布了一年,它的次要新性能是:Composition API。从2021年秋季开始,举荐新我的项目应用Vue 3的 script setup 语法,所以心愿咱们能看到越来越多的生产级应用程序建设在Vue 3上。

这篇文章旨在展现一些乏味的办法来利用Composition API,以及如何围绕它来结构一个应用程序。

2. 可组合函数和代码重用

新的组合API开释了许多乏味的办法来重用跨组件的代码。温习一下:以前咱们依据组件选项API宰割组件逻辑:data、methods、created 等。

//  选项API格调data: () => ({    refA: 1,    refB: 2,  }),// 在这里,咱们常常看到500行的代码。computed: {  computedA() {    return this.refA + 10;  },  computedB() {    return this.refA + 10;  },},

有了Composition API,咱们就不会受限于这种构造,能够依据性能而不是选项来拆散代码。

setup() {    const refA = ref(1);        const computedA = computed(() => refA.value + 10);        /*         这里也可能是500行的代码。        然而,这些性能能够放弃在彼此左近!        */    const computedB = computed(() => refA.value + 10);        const refB = ref(2);    return {      refA,      refB,      computedA,      computedB,    };  },

Vue 3.2引入了<script setup>语法,这只是setup()函数的语法糖,使代码更加简洁。从当初开始,咱们将应用 script setup 语法,因为它是最新的语法。

<script setup>import { ref, computed } from 'vue'const refA = ref(1);const computedA = computed(() => refA.value + 10);const refB = ref(2);const computedB = computed(() => refA.value + 10);</script>

在我看来,这是一个比拟大想法。咱们能够把这些性能分成本人的文件,而不是用通过搁置 在script setup中的地位来放弃它们的拆散。上面是同样的逻辑,把文件宰割开来的做法。

// Component.vue<script setup>import useFeatureA from "./featureA";import useFeatureB from "./featureB";const { refA, computedA } = useFeatureA();const { refB, computedB } = useFeatureB();</script>// featureA.js import { ref, computed } from "vue";export default function () {  const refA = ref(1);  const computedA = computed(() => refA.value + 10);  return {    refA,    computedA,  };}// featureB.js import { ref, computed } from "vue";export default function () {  const refB = ref(2);  const computedB = computed(() => refB.value + 10);  return {    refB,    computedB,  };}

留神,featureA.jsfeatureB.js导出了RefComputedRef类型,因而所有这些数据都是响应式的。

然而,这个特定的片段可能看起来有点矫枉过正。

  • 设想一下,这个组件有500多行代码,而不是10行。通过将逻辑拆散到use__.js文件中,代码变得更加可读。
  • 咱们能够在多个组件中自在地重复使用.js文件中的可组合函数 不再有无渲染组件与作用域槽的限度,也不再有混合函数的命名空间抵触。因为可组合函数间接应用了Vue的refcomputed,所以这段代码能够与你我的项目中的任何.vue组件一起应用。

陷阱1:setup 中的生命周期钩子

如果生命周期钩子(onMountedonUpdated等)能够在setup外面应用,这也意味着咱们也能够在咱们的可组合函数外面应用它们。甚至能够这样写:

// Component.vue<script setup>import { useStore } from 'vuex';const store = useStore();store.dispatch('myAction');</script>// store/actions.jsimport { onMounted } from 'vue'// ...actions: {  myAction() {    onMounted(() => {            console.log('its crazy, but this onMounted will be registered!')        })  }}// ...

而且Vue甚至会在vuex外部注册生命周期钩子! (问题是:你应该)

有了这种灵活性,理解如何以及何时注册这些钩子就很重要了。请看上面的片段。哪些onUpdated钩子将被注册?

<script setup lang="ts">import { ref, onUpdated } from "vue";// 这个钩子将被注册。咱们在 setup 中失常调用它onUpdated(() => {  console.log('✅')});class Foo {  constructor() {    this.registerOnMounted();  }  registerOnMounted() {     //它也会注册! 它是在一个类办法中,但它是在      //在 setup 中同步执行    onUpdated(() => {       console.log('✅')    });  }}new Foo();// IIFE also works(function () {  onUpdated(() => {    state.value += "✅";  });})();const onClick = () => {    /*     这不会被注册。这个钩子是在另一个函数外面。    Vue不可能在setup 初始化中达到这个办法。    最蹩脚的是,你甚至不会失去一个正告,除非这个     函数被执行! 所以要留神这一点。    */   onUpdated(() => {    console.log('❌')  });};// 异步IIFE也会不行 :((async function () {  await Promise.resolve();  onUpdated(() => {    state.value += "❌";  });})();</script>

论断:在申明生命周期办法时,应使其在setup初始化时同步执行。否则,它们在哪里被申明以及在什么状况下被申明并不重要。

陷阱2:setup 中的异步函数

咱们常常须要在咱们的逻辑中应用async/await。天真的做法是尝试这样做:

<script setup lang="ts">import { myAsyncFunction } from './myAsyncFunction.jsconst data = await myAsyncFunction();</script><template>  Async data: {{ data }}</template>

然而,如果咱们尝试运行这段代码,组件基本不会被渲染。为什么?因为 Promise 不跟踪状态。咱们给 data 变量赋了一个 promise,然而Vue不会被动更新它的状态。侥幸的是,有一些变通办法:

解决方案1:应用.then语法的ref

为了渲染该组件,咱们能够应用.then语法。

<script setup>import { ref } from "vue";import { myAsyncFunction } from './myAsyncFunction.jsconst data = ref(null);myAsyncFunction().then((res) =>  data.value = fetchedData);</script><template>  Async data: {{ data }}</template>
  1. 一开始时,创立一个等于null的响应式ref
  2. 调用了异步函数script setup 的上下文是同步的,所以该组件会渲染
  3. myAsyncFunction() promise 被解决时,它的后果被赋值给响应性 data ref,后果被渲染

这种形式有本人优缺点:

  • 长处是:能够应用
  • 毛病:语法有点过期,当有多个.then.catch链时,会变得很蠢笨。

解决方案2:IIFE

如果咱们把这个逻辑包在一个异步IIFE外面,咱们就能够应用 async/await的语法。

<script setup>import { ref } from "vue";import { myAsyncFunction } from './myAsyncFunction.js'const data = ref(null);(async function () {    data.value = await myAsyncFunction()})();</script><template>  Async data: {{ data }}</template>

这种形式也有本人优缺点:

  • 长处:async/await语法
  • 毛病:能够说看起来不那么洁净,依然须要一个额定的援用

解决方案3:Suspense (实验性的)

如果咱们在父组件中用<Suspense>包装这个组件,咱们就能够自在在setup 中自在应用async/await!

// Parent.vue<script setup lang="ts">import { Child } from './Child.vue</script><template>  <Suspense>        <Child />    </Suspense></template>// Child.vue<script setup lang="ts">import { myAsyncFunction } from './myAsyncFunction.jsconst data = await myAsyncFunction();</script><template>  Async data: {{ data }}</template>
  • 长处:到目前为止,最扼要和直观的语法
  • 毛病:截至2021年12月,这依然是一个实验性的性能,它的语法可能会扭转。

<Suspense> 组件在子组件 setup 中有更多的可能性,而不仅仅是异步。应用它,咱们还能够指定加载和回退状态。我认为这是创立异步组件的前进方向。Nuxt 3曾经应用了这个个性,对我来说,一旦这个个性稳定下来,它可能是首选的形式

解决方案4:独自的第三方办法,为这些状况量身定做(见下节)。

长处。最灵便

毛病:对package.json的依赖

3. VueUse

VueUse库依附Composition API解锁的新性能,给出了各种辅助函数。就像咱们写的useFeatureAuseFeatureB一样,这个库能够让咱们导入预制的实用函数,以可组合的格调编写。上面是它的工作原理的一个片段。

<script setup lang="ts">import {  useStorage,    useDark} from "@vueuse/core";import { ref } from "vue";/*     一个实现localStorage的例子。     这个函数返回一个Ref,所以能够立刻用`.value`语法来编辑它。    用.value语法编辑,而不须要独自的getItem/setItem办法。*/const localStorageData = useStorage("foo", undefined);</script>

我无奈向你举荐这个库,在我看来,它是任何新的Vue 3我的项目的必备品。

  • 这个库有可能为你节俭很多行代码和大量的工夫。
  • 不影响包的大小
  • 源代码很简略,容易了解。如果你发现该库的性能不够,你能够扩大该性能。这象征在抉择应用这个库时,不会有太大的危险。

上面是这个库如何解决后面提到的异步调用执行问题。

<script setup>import { useAsyncState } from "@vueuse/core";import { myAsyncFunction } from './myAsyncFunction.js';const { state, isReady } = useAsyncState(    // the async function we want to execute  myAsyncFunction,  // Default state:  "Loading...",  // UseAsyncState options:  {    onError: (e) => {      console.error("Error!", e);      state.value = "fallback";    },  });</script><template>  useAsyncState: {{ state }}  Is the data ready: {{ isReady }}</template>

这种办法能够让你在setup外面执行异步函数,并给你回退选项和加载状态。当初,这是我解决异步的首选办法。

4. 如果你的我的项目应用Typescript

新的definePropsdefineEmits语法

script setup 带来了一种在Vue组件中输出 props 和 emits 的更快形式。

<script setup lang="ts">import { PropType } from "vue";interface CustomPropType {  bar: string;  baz: number;}//  defineProps的重载。// 1. 相似于选项API的语法defineProps({  foo: {    type: Object as PropType<CustomPropType>,    required: false,    default: () => ({      bar: "",      baz: 0,    }),  },});// 2. 通过一个泛型。留神,不须要PropType!defineProps<{ foo: CustomPropType }>();// 3.默认状态能够这样做。withDefaults(  defineProps<{    foo: CustomPropType;  }>(),  {    foo: () => ({      bar: "",      baz: 0,    }),  });// // Emits也能够用defineEmits进行简略的类型化defineEmits<{ (foo: "foo"): string }>();</script>

就集体而言,我会抉择通用格调,因为它为咱们节俭了一个额定的导入,并且对null和 undefined 的类型更加明确,而不是Vue 2格调语法中的{ required: false }

留神,不须要手动导入 definePropsdefineEmits。这是因为这些是Vue应用的非凡宏。这些在编译时被解决成 "失常 的选项API语法。咱们可能会在将来的Vue版本中看到越来越多的宏的实现。

可组合函数的类型化

因为typescript要求默认输出模块的返回值,所以一开始我次要是用这种形式写TS组合物。

import { ref, Ref, SetupContext, watch } from "vue";export default function ({  emit,}: SetupContext<("change-component" | "close")[]>): // 上面的代码真的有必要吗?{  onCloseStructureDetails: () => void;  showTimeSlots: Ref<boolean>;  showStructureDetails: Ref<boolean>;  onSelectSlot: (arg1: onSelectSlotArgs) => void;  onBackButtonClick: () => void;  showMobileStepsLayout: Ref<boolean>;  authStepsComponent: Ref<string>;  isMobile: Ref<boolean>;  selectedTimeSlot: Ref<null | TimeSlot>;  showQuestionarireLink: Ref<boolean>;} {  const isMobile = useBreakpoints().smaller("md");  const store = useStore();    // and so on, and so on    // ... }

这种形式,我认为这是个谬误。其实没有必要对函数返回进行类型化,因为在编写可组合的时候能够很容易地对它进行隐式类型化。它能够为咱们节俭大量的工夫和代码行。

import { ref, Ref, SetupContext, watch } from "vue";export default function ({  emit,}: SetupContext<("change-component" | "close")[]>) {  const isMobile = useBreakpoints().smaller("md");  const store = useStore();    // The return can be typed implicitly in composables}

如果EsLint将此标记为谬误,将`
'@typescript-eslint/explicit-module-boundary-types': 'error'
`,放入EsLint配置(.eslintrc)。

Volar extension

Volar是作为VsCode和WebStorm的Vue扩大来取代Vetur的。当初它被正式举荐给Vue 3应用。对我来说,它的次要特点是:typing props and emits out of the box。这很好用,特地是应用Typescript的话。

当初,我总是会抉择Vue 3我的项目中应用Volar。对于Vue 2, Volar依然实用,因为它须要更少的配置 。

5. 围绕组合API的利用架构

将逻辑从.vue组件文件中移出

以前,有一些例子,所有的逻辑都是在script setup 中实现的。还有一些例子是应用从.vue文件导入的可组合函数的组件。

大代码设计问题是:咱们应该把所有的逻辑写在.vue文件之外吗?有利有弊。

所有的逻辑都放在 setup中移到专用的.js/.ts文件
不须要写一个可组合的,不便间接批改可扩大更强
重用代码时须要重构不须要重构
更多模板

我是这样抉择的:

  • 在小型/中型我的项目中应用混合办法。一般来说,把逻辑写在setup外面。当组件太大时,或者当很分明这些代码会被重复使用时,就把它放在独自的js/ts文件中
  • 对于大型项目,只需将所有内容编写为可组合的。只应用setup来解决模板名称空间。

作者:Noveo 译者:小智 起源:noveogroup

原文:https://blog.noveogroup.com/2...

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。