关于前端:围绕Vue-3-Composition-API构建一个应用程序包含一些最佳实践

5次阅读

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

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

本文 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.js
import {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.js
const 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.js

const 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.js
const 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 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

正文完
 0