Vue3.2.x中的小技巧以及注意事项
前言
vue3
在2022年的2月7号成为了vue默认版本,并且随之而来的还有vue3
的新文档, 并且从实际使用的角度来说,vue3
确实比vue2
使用起来更加的舒服,所以觉得经过一段时间的使用,来分享一下使用过程中的小技巧以及注意事项。
小技巧
关于减少.value
的使用
-
使用
watch
来监听Ref
数据的时候, 可以做到省略.value
的使用, 例如:const value = ref(1); // 省略() => value.value watch(value, (v) => { // 省略v.value console.log(v); }, { immediate: true }); setTimeout(() => { value.value = 2; }, 1000);
-
使用
vue3.2.25
以上版本提供的$ref
, 还是跟上面的代码实现一样的功能该功能是一个实验性能, 需要相应的配置, 这里以
vite
为例, 需要在vite.config.ts
的vue plugin
中添加一个reactivityTransform
属性, 请看下面的配置。如果用的是其他工程化工具, 可以参考vue
的新文档, 文档中有详细的说明。plugins: [ vue({ reactivityTransform: true, }) ]
const count = $ref(1); // 增加了() => count watch( () => count, (v) => { console.log(v); }, { immediate: true } ); setTimeout(() => { // 省略了count.value count++; }, 1000);
输出:
这里需要说明一下使用
$ref
需要注意的问题, 首先该功能是一个实验性性能, 需要相应的配置, 并且vue
的文档中指出该方法是一个编译器宏使用时无需引入, 但为了ts
和编辑器的无端报错, 个人还是喜欢显示的引入, 就像这样import { $ref } from 'vue/macros'
接着再说一下$ref
的另一个很严重的问题, 就是丢失响应式, 为什么会丢失响应式呢? 其实这部分官方文档已经做出了说明, 请看下面的代码
// App.vue
import { $ref } from "vue/macros";
import { useApp } from "./App";
let count = $ref(1);
useApp(count);
setTimeout(() => {
console.log("change");
count++;
}, 1500);
// App.ts
import { watch } from "vue";
export const useApp = (count) => {
watch(
() => count,
(c) => {
console.log("watch", c);
},
{ immediate: true }
);
};
上面代码中
App.ts
里面的watch
只会执行一次, 很明显,count
丢失了响应性 如何解决这个问题呢?请看下面的代码:
// App.vue
import { $ref, $$ } from "vue/macros"; // 引入$$
import { useApp } from "./App";
let count = $ref(1);
useApp($$(count)); // useApp(count) --> useApp($$(count))
setTimeout(() => {
console.log("change");
count++;
}, 1500);
// App.ts
import { watch } from "vue";
export const useApp = (count) => {
watch(
count, // () => count --> count
(c) => {
console.log("watch", c);
},
{ immediate: true }
);
};
可以看到, 我们在传递
$ref
值的时候 需要用一个$$
方法包裹一下, 这样就不会丢失响应性了, 具体更详细的使用方法, 还是希望大家仔细阅读一下vue
的新文档
关于减少import
导入语句
发现这个功能是无意间的,在使用
element-plus
的时候, 查看elment-plus
官网 指南 快速开始, 其中提到了自动导入的功能, 文档中说的是 首先下载对应的插件npm install -D unplugin-vue-components unplugin-auto-import
, 然后如果使用的是vite的话, 需要在vite.config中添加几条配置, 就像下面一样:
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default {
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
因为好奇去看了这两个包的介绍, 发现不光可以自动导入组件, 还可以自动导入方法, 例如心细的小伙伴已经发现,
.value
那部分的代码 不管是ref
还是$ref
我都没有写import
语句来导入, 这里就用到了这两个插件, 我们来看一下如果要自动导入vue
的方法对应的配置。
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default {
plugins: [
// ...
AutoImport({
imports: ["vue", "vue/macros"], // 增加这一行代码
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
在这样的配置下就可以愉快的减少
import
导入了。
关于在script setup
中声明组件名字
在
script setup
的方式刚发布的时候, 我就一直在纠结这个问题, 因为项目中有很多的递归组件, 如果没有name
来做标识的话, 势必会产生问题
-
刚开始
vue的issues
中其他用户提出的解决方式是在.vue
文件中定义两个script
标签, 其中一个用来定义组件的name
, 而另一个用来编写组件逻辑, 例如下面这样:这种方式相信对于一些有强迫症或者完美主义者来说是完全不能接受的, 包括我 也不能接受, 所以在
vue
的issues
中就有一个用户开发了一个插件来解决这个问题。 -
unplugin-vue-define-options
插件
下载插件
npm i unplugin-vue-define-options -D
我们直接来看一下这个插件的使用方式: 在vite中使用
// vite.config.ts
import DefineOptions from 'unplugin-vue-define-options/vite'
import Vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [Vue(), DefineOptions()],
})
ts项目需要在tsconfig.json中添加一个配置
{
"compilerOptions": {
"types": ["unplugin-vue-define-options"]
}
}
使用方式
<script setup lang="ts">
defineOptions({
name: 'App'
})
</script>
该插件的功能远远不止定义组件的
name
, 还可以定义组件的props
、emits
、render
等,有兴趣的小伙伴可以去看一下,感觉可以利用这一特性才做一些骚操作,不过尤大大觉得这种方式不太好。
注意事项
关于响应式的问题
props
不能使用解构的方式来使用, 例如下面的例子
// Parent.vue
<template>
<ChildVue ref="childRef" v-bind="data" />
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import ChildVue from "./views/Child.vue";
const data = reactive({ name: 'veloma' });
setTimeout(() => {
data.name = 'timer';
}, 1500);
</script>
// Child.vue
<template>
<div>{{ data.name }}</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
name: string;
}>();
const data = reactive({ ...props });
</script>
上面的例子在子组件中, 通过
reactive
将props
进行了解构,Parent
组件中1.5s
后更新name
, 这时我们会发现Child
组件中的模板并不会产生更新, 那如何来解决这个问题呢? 首先有两种解决方式:
- 使用
3.2.25
或以上的版本直接解构defineProps
, 例如这样const { name } = defineProps<{ name: string }>()
2.或者通过computed
来解构, 例如const data = computed(() => ({ ...props }))
模板循环中加不加key
的问题
关于这个问题, 在
vue
新文档中有提到这样一句话
只看这句话的话是没有任何问题的,但在实际的使用过程中, 举个🌰:
// Parent.vue
<template>
<div>
<ChildVue v-for="item of list" v-bind="item"></ChildVue>
<button @click="onClick">按钮</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ChildVue from "./views/Child.vue";
const list = ref([{ name: "veloma" }, { name: "timer" }, { name: "lucy" }]);
const onClick = () => {
console.log('点击');
const item = { ...list.value[0] };
item.name = "veloma1111";
list.value[0] = item;
};
</script>
// Child.vue
<template>
<div>{{ name }}</div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
defineProps<{
name: string;
}>();
onMounted(() => {
console.log("mounted");
});
</script>
我们看上面的代码会发现功能非常的简单, 子组件接收一个
name
属性, 父组件循环渲染子组件, 且子组件中有一个onMounted
钩子, 我们希望的是, 当点击按钮的时候触发子组件的onMounted
钩子, 乍一看是没有任何问题的, 但实际是不会触发的, 看结果:
我们发现
click
事件确实触发了, 而数据也确实变化了, 页面也变化了, 但就是没有触发子组件的onMounted
钩子, 那这是怎么回事呢?实际上在vue
处理这一步的时候 重用了之前name
为veloma
的Child
组件, 重用不会产生挂载, 也就不会触发onMounted
钩子, 那要怎么解决呢?其实很简单, 只需要给Child
组件一个key
即可.
总结
到目前为止其实还有好多公司没有升级到
vue3
, 但是我相信 在不久的将来 甚至就是今年,vue3 + vite + typescript
一定会覆盖大部分的公司, 所以建议小伙伴们还是需要仔细认真的多看两遍vue
的新文档, 系统的了解一下vue3
的变化为以后的升级做好准备, 加油!
转载自:https://juejin.cn/post/7082644554574200839