阅读视图

发现新文章,点击刷新页面。

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: truedefault0})

attribute透传

  • 组件接收的所有未在props中声明的属性会透传
  • 多根节点默认不会透传,除非显示透传,使用$attrs
  • 可以通过vue提供的方法获取所有透传的attrs,attrs不是响应式的,需要响应式的需要props定义。
import { useAttrs } from 'vue'
const attrs = useAttrs();
  • 想要父组件的属性透传给自己的子组件可以使用$attrs, attrs对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 classstylev-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
❌