vue3开发容易忽略的地方
工作了很多年,使用vue3也有3年了,但是一直像一个螺丝钉一样复制粘贴,没有学习什么东西,能熟练梳理业务逻辑,但是思路和方法跟用vue2没有什么区别。决定重新刷一遍文档,把没用过的,不太熟悉的地方罗列出来,希望能在开发中多多使用。
ref在模板中解包
- 在模板渲染上下文中,只有顶级ref才会被解包.
const count = ref(1);
const obj = ref({id: ref(1)}) // obj.id 在模板中不能被解包
{{obj.id + 1}} // 输出错误:[object object]1
{{obj.id}} // 输出正确:1
- {{ obj.id }} 与 {{ obj.id.value }}等价,前者是文本插值的便利特性。
reactive
-不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
-对解构操作不友好,我们解构reative时,解构后的变量与原变量失去连接。
// 不使用reative泛型参数
const book:Book = reative({name: 'xxx'})
let {count} = reativeDemo;
count++; // 此时,reativeDemo.count不会发生变化。
computed
- computed属性会基于响应式依赖缓存:一个computed属性只会在他的响应式依赖更新时重新计算。
- computed可以获取上一次的值computed((pre) => {return count.value > 0 ? count.value : pre})
- 可以重写computed的get和set方法,但是不建议。
v-show/v-if
- v-show不支持在template上使用(v-if可以)
- v-if有惰性,一开始为false则不会渲染,直到为true才第一次渲染
v-for
- 循环使用item in item或者item of items一样
- (value, key, index) in obj
- v-for与v-if不建议同时使用,v-if优先级更高,所以v-if中不能使用item的值
函数ts类型
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
watch/watchEffect
- deep属性可以为数字,表示监听的深度。
- once属性,只监听一次。
- watchEffect: 用法类似computed,不需要指定监听对象。在同步执行期间,自动追踪所有能访问到的响应式数据,在异步执行期间,只有在第一个await正常工作之前的响应式数据能被追踪。
-
onWatcherCleanup: 注册一个清理函数,当监听器失效并准备重新运行时调用(eg:在watch中id变化调用接口,接口未返回数据时,id又发生了变化,则需要取消正在进行的接口调用,根据新的id重新发起接口)(3.5+) - watch方法会在dom更新之前调用,如果想在dom更新之后调用则需要增加属性
{flush: 'post'},watch方法在任何更新之前调用,则使用{flush: 'sync}(3.5+)
watch(source, callback, {flush: 'post'})
watchEffect(callback, {flush: 'post'})
watchPostEffect(callback)
// 在响应数据变化时同步执行
watch(source, callback, {flush: 'sync'})
watchEffect(callback, {flush: 'sync'})
watchSyncEffect(callback)
- 中止监听器,定义一个变量接受watch或者watchEffect的返回值
const unwatch = watch(source, callback)
unwatch() // 中止监听
模板引用useTemplateRef(3.5+)
- 会自动推断ref的数据类型
- 如果自动推断失败可以自定义类型,使用InstanceType(typeof MyComponent) / HTMLInputElement
const inputRef = useTemplateRef('my-input');
inputRef.focus();
- v-for上使用ref,则useTemplateRef生成的是一个数组,数组的顺序不一定与源数组顺序相同。
<div ref="itemsRef" v-for="item in items" >{{ item }}</div>
const itemsRef = useTemplateRef('itemsRef')
props
- 解构props时,对象数组等不需要在函数中返回。(3.5+)
- 给props赋值的时候使用definedProps<>()
- 定义props分编译时和运行时,运行时可以验证一些复杂数据类型以及定义动态数据。编译时可以使用泛型等复杂数据结构,ts中更推荐使用编译时。
- 给组件传递一个对象的所有属性可以使用v-bind写法
- 子组件不能修改父组件传递的props,可以重新定义一个变量。
- boolean类型的props,为了更贴近原生html,有简介写法。
// 解构props时,对象数组等不需要在函数中返回。(3.5+)
const {name: '', age: 0, hobby: []} = defineProps<{name: string, age: number, hobby: string[]}>();
// 3.5以下版本
// withDefaults中设置默认值时,对象数组等需要在函数中返回,确保多个实例时,每个实例都有自己的副本。
const props = widthDefaults(defineProps<{name: string, age: number, hobby: string[]}>(), {
name: '',
age: 0,
hobby: () => []
})
// 运行时
const props = defineProps({
name: {
type: String,
default: '',
validate: (val) => {...}
}
})
// 编译时
const defineProps<{name: string, age: number, hobby: string[]}>
// boolean类型的props
const props = defineProps({disabled: Boolean})
<myComponent disabled /> // 相当于 :disabled="true",不写默认为false
emit
- 可以带校验,校验通过再emit,否则不emit
- 更简洁的定义语法(3.3+)
// 校验
const emits = defineEmits({
submit: (name: string, id: number) => {
if (name && id) { return true }
else { return false;}
}
})
const emits = defineEmits<{
update: [id: number],
success: [],
}>()
// v-bind
const post = {name: '', id: 0}
<myComponent v-bind="post" />
<myComponent :id="post.id" :name="post.name" />
defineModel
- 定义了一个名为modelValue的prop,本地的ref与其同步
- 定义了一个update:modelValue方法,本地ref变化时触发(.set方法中实现)
// 第一个参数不写默认与ref名相同
const modelValue = defineModel('value', {require: true, default:0})
attribute透传
- 组件接收的所有未在props中声明的属性会透传
-
多根节点默认不会透传,除非显示透传,使用$attrs - 可以通过vue提供的方法获取所有透传的attrs,attrs
不是响应式的,需要响应式的需要props定义。
import { useAttrs } from 'vue'
const attrs = useAttrs();
- 想要父组件的属性透传给自己的子组件可以使用$attrs,
attrs对象包含了除组件所声明的props和emits之外的所有其他 attribute,例如class,style,v-on监听器等等。 - 想要不透传,或者不接受透传可以设置
<childComponent v-bind="$attrs">
// 不透传设置
<scripte setup>
defineOptions({
inheritAttrs: false
})
</script>
slot
- 组件插槽可以传递参数,参数名不能为name,name为插槽名。
// Child
<div class="card">
<slot name="cardContent" value="cardInfo">
</div>
// Parent
<template #cardContent="cardInfo">
{{cardInfo}}
</template>
依赖注入
- 可以inject一个ref的响应式数据,数据变化会同步。在子孙组件修改会同步父组件,在父组件修改会同步到子组件。但是建议所有的更改都在父组件中执行,可以通过传递一个update方法给子孙组件更新。
import { readonly } from 'vue'
// 父组件
const location = ref('');
function updateLocation() {
location.value = "ww"
}
privide('location', {
location,
updateLocation,
})
// 子孙组件
const {location, updateLocation} = inject('location')
- 不想被子孙组件更改的数据使用readonly方法包裹
privide('readonlyLocation', readonly(location))
- vue推荐当依赖注入较多时或写公用组件,使用Symbol防止重名,将所有Symbol定义的key放到一个文件中
// key.js
const locationkey = Symbol();
// parent
import { locationkey } from './key.js'
privide(locationKey, {...})
// child
import { locationkey } from './key.js'
const location = inject(locationkey);
组合式函数
- 方法中使用了vue的组合式API(如ref,watch等)来封装和复用的
有状态逻辑的函数。 - 约定
- 通常使用useXxx命名
- 返回的数据一般都为ref,这样在组件中被解构后仍为响应式数据。
const x = ref(1); const y = ref(2); return { x, y } - 在不同组件中使用useXxx都会重新创建新的数据,各个数据互不影响。
自定义指令
- 由一个包含类似组件生命周期钩子的对象来定义。在ts setup中,v开头的方法都可以直接作为指令使用
- 指令不建议在组件上使用,组件可能有多个子组件,指令不会通过$attrs透传。
<script setup>
const vHighlight = {
mounted: (el) => {
el.classList.add('high-light')
}
}
</script>
<template>
<div v-highlight>aaaaaaaa</div>
</template>
vue官方推荐
- 使用
vscode编辑器 -
vue official:vscode插件,提供实时语言服务 - 浏览器开发者插件:
Vue DevTools -
vue-tsc: 在编译阶段进行类型检查。 - 代码规范:在项目中安装
eslint-plugin-vue; 在IDE中装ESlint插件;在项目中装lint-staged代码提交规范 - 格式化:
prettier,IDE中安装插件,项目中安装prettier,写代码格式化时IDE中的prettier会优先找项目中的prettier配置进行格式化,如果项目中没有则会使用IDE自己的配置。 -
@vitejs/plugin-vue:为 Vite 提供 Vue 单文件组件支持的官方插件。
状态管理
- 用响应式API(ref,reative, watch等)做状态管理。
- 定义一个store文件,将多个组件需要公用的数据放进去,在组件中引用
- 复杂的使用
pinia