IT科技

围绕Vue 3 Composition API构建一个应用程序,包含一些优秀实践!

时间:2010-12-5 17:23:32  作者:应用开发   来源:IT科技类资讯  查看:  评论:0
内容摘要:1、Vue 3和Composition API的状况Vue 3已经发布了一年,它的主要新功能是:Composition API。从2021年秋季开始,推荐新项目使用Vue 3的​​script set

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  语法,因为它是最新的语法。

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

import useFeatureA from "./featureA";

import useFeatureB from "./featureB";

const { refA, computedA } = useFeatureA();

const { refB, computedB } = useFeatureB();

// 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.js和featureB.js​导出了Ref和ComputedRef类型,因此所有这些数据都是响应式的。

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

想象一下,站群服务器这个组件有500多行代码,而不是10行。通过将逻辑分离到use__.js文件中,代码变得更加可读。我们可以在多个组件中自由地重复使用.js​文件中的可组合函数 不再有无渲染组件与作用域槽的限制,也不再有混合函数的命名空间冲突。因为可组合函数直接使用了Vue的ref和computed​,所以这段代码可以与你项目中的任何.vue组件一起使用。陷阱1:setup 中的生命周期钩子

如果生命周期钩子(onMounted,onUpdated​等)可以在setup里面使用,这也意味着我们也可以在我们的可组合函数里面使用它们。甚至可以这样写:

// Component.vue

import { useStore } from vuex;

const store = useStore();

store.dispatch(myAction);

// store/actions.js

import { onMounted } from vue

// ...

actions: {

myAction() {

onMounted(() => {

console.log(its crazy, but this onMounted will be registered!)

})

}

}

// ...

而且Vue甚至会在vuex内部注册生命周期钩子!

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

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。天真的做法是尝试这样做:

import { myAsyncFunction } from ./myAsyncFunction.js

const data = await myAsyncFunction();

Async data: { { data }}

</template>

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

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

为了渲染该组件,我们可以使用.then语法。

import { ref } from "vue";

import { myAsyncFunction } from ./myAsyncFunction.js

const data = ref(null);

myAsyncFunction().then((res) =>

data.value = fetchedData

);

Async data: { { data }}

</template>一开始时,创建一个等于null的响应式ref调用了异步函数script setup 的上下文是同步的,所以该组件会渲染当myAsyncFunction() promise 被解决时,它的结果被赋值给响应性 data ref,结果被渲染

这种方式有自己优缺点:

优点是:可以使用。缺点:语法有点过时,当有多个.then和.catch链时,会变得很笨拙。解决方案2:IIFE

如果我们把这个逻辑包在一个异步IIFE里面,我们就可以使用 async/await的语法。

import { ref } from "vue";

import { myAsyncFunction } from ./myAsyncFunction.js

const data = ref(null);

(async function () {

data.value = await myAsyncFunction()

})();

Async data: { { data }}

</template>

这种方式也有自己优缺点:

优点:async/await语法。缺点:可以说看起来不那么干净,仍然需要一个额外的引用。解决方案3:Suspense (实验性的)

如果我们在父组件中用<Suspense>​包装这个组件,我们就可以自由在setup 中自由使用async/await!

// Parent.vue

import { Child } from ./Child.vue

// Child.vue

import { myAsyncFunction } from ./myAsyncFunction.js

const data = await myAsyncFunction();

Async data: { { data }}

</template>优点:到目前为止,最简明和直观的语法缺点:截至2021年12月,这仍然是一个实验性的功能,它的语法可能会改变。

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

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

优点。最灵活。

缺点:对package.json的依赖。

3、VueUse

VueUse库依靠Composition API解锁的新功能,给出了各种辅助函数。就像我们写的​​useFeatureA​​​和​​useFeatureB​​一样,这个库可以让我们导入预制的实用函数,以可组合的风格编写。下面是它的工作原理的一个片段。

import {

useStorage,

useDark

} from "@vueuse/core";

import { ref } from "vue";

/*

一个实现localStorage的例子。

这个函数返回一个Ref,所以可以立即用`.value`语法来编辑它。

用.value语法编辑,而不需要单独的getItem/setItem方法。

*/

const localStorageData = useStorage("foo", undefined);

</script>

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

这个库有可能为你节省很多行代码和大量的时间。不影响包的大小。源代码很简单,容易理解。如果你发现该库的功能不够,你可以扩展该功能。这意味在选择使用这个库时,不会有太大的风险。

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

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";

},

}

);

useAsyncState: { { state }}

Is the data ready: { { isReady }}

</template>

这种方法可以让你在setup里面执行异步函数,并给你回退选项和加载状态。现在,这是我处理异步的首选方法。

4、如果你的项目使用Typescript

新的defineProps和defineEmits语法

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

import { PropType } from "vue";

interface CustomPropType {

bar: string;

baz: number;

}

// defineProps的重载。

// 1. 类似于选项API的语法

defineProps({

foo: {

type: Object as PropType,

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 }。

 注意,不需要手动导入 defineProps​ 和 defineEmits​。这是因为这些是Vue使用的特殊宏。这些在编译时被处理成 "正常 的选项API语法。我们可能会在未来的Vue版本中看到越来越多的宏的实现。

可组合函数的类型化

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

import { ref, Ref, SetupContext, watch } from "vue";

export default function ({

emit,

}: SetupContext<("change-component" | "close")[]>):

// 下面的代码真的有必要吗?

{

onCloseStructureDetails: () => void;

showTimeSlots: Ref;

showStructureDetails: Ref;

onSelectSlot: (arg1: onSelectSlotArgs) => void;

onBackButtonClick: () => void;

showMobileStepsLayout: Ref;

authStepsComponent: Ref;

isMobile: Ref;

selectedTimeSlot: Ref;

showQuestionarireLink: Ref;

} {

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来处理模板名称空间。
copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap