共计 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.js
和 featureB.js
导出了 Ref
和ComputedRef
类型,因而所有这些数据都是响应式的。
然而,这个特定的片段可能看起来有点矫枉过正。
- 设想一下,这个组件有 500 多行代码,而不是 10 行。通过将逻辑拆散
到 use__.js
文件中,代码变得更加可读。 - 咱们能够在多个组件中自在地重复使用
.js
文件中的可组合函数 不再有无渲染组件与作用域槽的限度,也不再有混合函数的命名空间抵触。因为可组合函数间接应用了 Vue 的ref
和computed
,所以这段代码能够与你我的项目中的任何.vue
组件一起应用。
陷阱 1:setup 中的生命周期钩子
如果生命周期钩子(onMounted
,onUpdated
等)能够在 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>
- 一开始时,创立一个等于 null 的响应式
ref
- 调用了异步函数 script setup 的上下文是同步的,所以该组件会渲染
- 当
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 解锁的新性能,给出了各种辅助函数。就像咱们写的 useFeatureA
和useFeatureB
一样,这个库能够让咱们导入预制的实用函数,以可组合的格调编写。上面是它的工作原理的一个片段。
<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
新的 defineProps
和defineEmits
语法
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}
。
💡 留神,不须要手动导入 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<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 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。