以下示例使用组合式api。
在组合式api中获取this实例,需要使用:
import {getCurrentInstance} from "vue"; const instance = getCurrentInstance(); const _this = instance.ctx;
模版语法
- 可以动态绑定多个值
v-bind="{id:'myId',class:'myClass'}"
- 表达式的条件控制只支持三元写法
- 模板调用一个方法时
{{ formatDate(date) }}
需注意组件每次更新时都会被重新调用,要保证不改变数据或触发异步操作,造成死循环。 - 支持动态属性和动态事件的绑定
<a :[attributeName]="url" @[eventName]="doSomething>
,注意:不支持表达式,如需可使用计算属性。 - 支持多个根元素
响应式
-
ref()
函数用于任何类型数据深层响应式。 - 专门用于组合式api,接收一个参数并返回一个带
value
属性的对象。 - 在js中使用需要带
.value
,模版中使用无需,例如<button @click="count++">
- 使用
const counter = ref(0);console.log(count.value);count.value ++;console.log(count.value);
,结果0、1。 - 如果需要再在改变数据后执行其他代码请使用
nextTick()
函数,例如:async function increment() { counter++;await nextTick(); // 其他执行代码}
- 选项式api需要在
setup()
函数返回 -
reactive()
用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)的深层响应式。 - 不能替换整个对象,否则将失去响应式,如:
let state = reactive({ count: 0 });state = reactive({ count: 1 })
,state失去了响应式。 - 使用解构时或属性传递给函数时,得到变量会失去响应式。如:
let { count } = reactive({ count: 0 })
,count将失去响应式。 -
shallowRef()
函数同ref
函数,区别是它是浅层响应式。 -
shallowReactive()
行数同理它是浅层响应式。
计算属性
计算属性会返回一个ref对象,使用时和ref对象一样
-
计算属性可以通过setter来可写:
<script setup> import { ref, computed } from 'vue' const firstName = ref('John') const lastName = ref('Doe') const fullName = computed({ // getter get() { return firstName.value + ' ' + lastName.value }, // setter set(newValue) { // 注意:我们这里使用的是解构赋值语法 [firstName.value, lastName.value] = newValue.split(' ') } }) </script>
现在当你再运行 fullName.value = 'John Doe' 时,setter 会被调用而 firstName 和 lastName 会随之更新。
class于style的绑定
- 绑定对象
- 模板中绑定一个对象
const isActive = ref(true)
在模板中<div :class="{ active: isActive }">
- 直接绑定一个对象
const classObject = reactive({active: true,'text-danger': false})
在模板中<div :class="classObject">
- 也可以绑定一个返回对象的计算属性
- 绑定数组
- 模板中绑定一个数组
const activeClass = ref('active')
在模板中<div :class="[activeClass]">
- 模板中使用三元条件
<div :class="[isActive ? activeClass : '']">
- 可以嵌套对象
<div :class="[{ active: isActive }]">
- 对组件使用时,如果组件有多个根元素,可以通过组件的
$attrs
属性来指定作用于哪个根元素。
渲染
v-if
与v-for
同时存在时,v-if
会先被执行,其中无法使用v-for
的变量值进行判断。-
在定义
v-for
变量时,可以使用解构:<li v-for="{ message } in items"> {{ message }} </li> <!-- 有 index 索引时 --> <li v-for="({ message }, index) in items"> {{ message }} {{ index }} </li>
可以使用
of
关键词代替in
,作用类似js的迭代,<div v-for="item of items">
v-for
可以遍历一个对象<li v-for="(value, key, index) in myObject">
v-for
可以接受一个数字,值从1开始,<span v-for="n in 10">
n的第一个值为1当使用
<template v-for>
时,key
应该被放置在这个<template>
容器上
事件
$event
用于模板中的方法传递原生的事件参数<button @click="warn('123', $event)">
事件修饰符,多个需注意调用顺序
.stop
单击事件将停止向上传递.prevent
阻止事件的默认行为.self
事件来源本身时才会触发,子元素不触发以下与原生 addEventListener 事件相对应:
-
.capture
捕获模式,指向内部元素的事件,在被内部元素处理前,先被外部处理 -
.once
点击事件最多被触发一次 -
.passive
强制执行,不会因为事件中有阻止行为就停止,一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。请不要和.prevent
同时使用。 按键修饰符可以监听键盘按键的事件
<input @keyup.enter="submit" />
常用的有.enter、.tab、.delete (捕获“Delete”和“Backspace”两个按键)、.esc、.space、.up、.down、.left、.right、.ctrl、.alt、.shift、.meta(在 Mac 键盘上,meta 是 Command 键 (⌘)。在 Windows 键盘上,meta 键是 Windows 键 (⊞))
鼠标按键修饰符,
.left、.right、.middle
-
.exact
精确修饰符,在使用按键修饰符时只能按指定键,才会触发例如:<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 --> <button @click.ctrl="onClick">A</button> <!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 --> <button @click.ctrl.exact="onCtrlClick">A</button> <!-- 仅当没有按下任何系统按键时触发 --> <button @click.exact="onClick">A</button
双向绑定
多个复选框绑定到同一个数组或集合
const checkedNames = ref([]); <input type="checkbox" value="Jack" v-model="checkedNames"> <input type="checkbox" value="John" v-model="checkedNames"> <input type="checkbox" value="Mike" v-model="checkedNames">
select需要提供一个空值选项,否则初始值为空时,ios下将无法选择
<option disabled value="">Please select one</option>
复选框
true-value、false-value
是 Vue 特有的 attributes,仅支持和v-model
配套使用。如:<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
v-model
同样也支持非字符串类型的值绑定,如:<select v-model="selected"></select><option :value="{ number: 123 }">123</option></select>
修饰符
<input v-model.lazy="msg" />
-
.lazy
在change时触发 -
.number
输入自动转为数字 -
.trim
去除内容中两端的空格
生命周期
- 调用 onMounted 时,需在组件初始化时被同步注册,不能异步,例如放在setTimeout中是无效的。
- onMounted() 也可以在一个外部函数中调用,只要调用栈是同步的,且最终起源自 setup() 就可以。
- 生命周期顺序
setup()、onMounted()、onUpdated()、onUnmounted()、onBeforeMount()、onBeforeUpdate()、onBeforeUnmount()、onErrorCaptured()、onRenderTracked()、onRenderTriggered()、onActivated()、onDeactivated()、onServerPrefetch()、onErrorCaptured()
,如果使用了KeepAlive组件,还包括onActivated、onDeactivated
数据监听
watch 的第一个参数: ref (包括计算属性)、响应式对象、 getter 函数(
() => x.value + y.value
)、或多个数据源组成的数组要监听响应式对象的属性,需要使用getter函数,例如:
const obj = reactive({ count: 0 }); watch( () => obj.count, (count) => { console.log(`count is: ${count}`) } )
监听响应式对象,默认为深监听,使用getter函数将是浅监听,第三个参数加上deep会变为深监听
{ deep: true }
第三个参数加上
{ immediate: true }
,将在初始时直接调用第三个参数加上
{ once: true }
,将只会监听一次watchEffect()
会自动监听回调中所有被用到的属性,简化操作-
// 将会自动监听todoId watchEffect(async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}` ) data.value = await response.json() })
watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。 监听器的回调是在父组件更新后,所属组件Dom被更新前,此时在监听器回调中无法访问被更新后的dom的。
第三个参数加上
flush: 'post'
可以在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM。watchPostEffect()
作用同上。第三个参数加上
flush: 'sync'
可以创建一个同步触发的侦听器,它会在 Vue 进行任何更新之前触发。注意:同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。watchSyncEffect()
作用同上。异步创建监听器时,需要手动停止防内存泄漏
const unwatch = watchEffect(() => {});unwatch();
,如果需要等待一些异步数据,可以看在监听器回调中使用if判断
访问dom元素
<input ref="input">
属性ref可以直接访问dom元素,例如:
<script setup> import { ref, onMounted } from 'vue' // 声明一个 ref 来存放该元素的引用 // 必须和模板里的 ref 同名 const input = ref(null) onMounted(() => { input.value.focus() }) </script> <template> <input ref="input" /> </template>
如果是在v-for
模板中循环ref将是一个数组
<script setup> const itemRefs = ref([]) onMounted(() => console.log(itemRefs.value)) </script> <template> <ul> <li v-for="item in list" ref="itemRefs"> {{ item }} </li> </ul> </template>
- ref还可以绑定为一个函数,会在每次组件更新时都被调用
<input :ref="(el) => { }">
,当该元素被卸载时,el值为null - 在子组件上使用ref,这种情况下引用中获得的值是组件实例。
如果子组件使用的是选项式 API,对子组件的每一个属性和方法都有完全的访问权
使用了
<script setup>
的组件,默认是无法访问子组件的任何东西,除非子组件在其中通过defineExpose()
显式暴露(无需导入)。<script setup> import { ref } from 'vue' const a = 1 const b = ref(2) // 像 defineExpose 这样的编译器宏不需要导入 defineExpose({ a, b }) </script>
组件
defineProps()
方法用于组件传递数据,无需导入,如:defineProps(['title'])
。defineEmits()
方法用于定义组件的事件,无需导入。<script setup> const emit = defineEmits(['enlarge-text']) emit('enlarge-text') // 事件校验 const emit2 = defineEmits({ // 没有校验 click: null, // 校验 submit 事件 submit: ({ email, password }) => { if (email && password) { return true } else { console.warn('Invalid submit event payload!') return false } } emit2('submit', { email, password }) }) </script>
动态组件
<component :is="{被注册的组件名或导入的组件对象}"></component>
组件书写必须闭合标签
<my-component></my-component>
元素标签限制解决,例如 table下必须tr等
<table> <!-- 会出现问题 --> <blog-post-row></blog-post-row> <!-- 需要使用is属性和vue:前缀,才可以解析 --> <tr is="vue:blog-post-row"></tr> </table>
全局注册组件
app.component('MyComponent', MyComponent).component('ComponentA', ComponentA)
props如果是对象或数据,默认值需要使用工厂函数返回
如果props给出null或undefined则跳过类型检查
自定义验证
validator(value, props) {}
defineProps()
中的参数不可以访问<script setup>
中定义的其他变量props的type类型检测可以是自定义的类
布尔值默认值类型转换,当type类型包含bool时,默认值为布尔值,只有和string同时使用且string在bool前默认值是为空字符串
// disabled 将被解析为空字符串 (disabled="") defineProps({ disabled: [String, Boolean] })
v-model
双向绑定
建议使用
defineModel()
<script setup> const model = defineModel() </script> <template> <input v-model="model" /> </template>
以前实现
<script setup> const props = defineProps(['modelValue']) const emit = defineEmits(['update:modelValue']) </script> <template> <input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" /> </template>
多个
v-model
<script setup> const firstName = defineModel('firstName') const lastName = defineModel('lastName') </script> <template> <input type="text" v-model="firstName" /> <input type="text" v-model="lastName" /> </template> <UserName v-model:first-name="first" v-model:last-name="last" />
属性透传
只有一个根节点的组件,属性默认透传在根节点,如果根节点是组件,会继续传递给这个组件。
使用inheritAttrs: false
来禁止这种传递。
<script setup> defineOptions({ inheritAttrs: false }) // ...setup 逻辑 </script> <template> <div class="btn-wrapper"> <button class="btn" v-bind="$attrs">click me</button> </div> </template>
如果是多根节点,默认不会透传。需要手动绑定v-bind="$attrs"
。
透传的属性不具备响应式特性,如需要请使用props。
<script setup> import { useAttrs } from 'vue' // 访问组件中所有透传的属性 const attrs = useAttrs() </script>
插槽
具名插槽定义
<slot name="header"></slot>
,使用<template v-slot:header></template>
v-slot:
可以简写为#
,例如:<template #header></template>
还可以定义动态插槽
<template v-slot:[dynamicSlotName]>
,<template #[dynamicSlotName]>
插槽返回子组件的数据
<!-- <MyComponent> 的模板 --> <div> <slot :text="greetingMessage" :count="1"></slot> </div> <MyComponent v-slot="slotProps"> {{ slotProps.text }} {{ slotProps.count }} </MyComponent>
注入
provide()
函数为后代组件提供数据<script setup> import { provide } from 'vue' provide(/* 注入名 */ 'message', /* 值 */ 'hello!') </script>
还可以使用app应用全局的provide
import { createApp } from 'vue' const app = createApp({}) app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
inject()
函数为当前组件注入父组件provide()
提供的数据。<script setup> import { inject } from 'vue' const message = inject('message','这里可以定义默认值,防止父级没有provide提供') </script>
逻辑复用
组合式函数使用use
开头、自定义指令以v
开头,驼峰法命名。
组合式函数
组合式函数使用
use
开头,驼峰写法,例如:useMouse()
。一个组合式函数可以调用一个或多个其他的组合式函数。
toValue()
格式值,如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。组合式函数结合
watchEffect()
就可以做到自动响应操作。组合式函数内返回的对象建议使用ref,这样在使用时返回的值可以结构后保持响应式
// x 和 y 是两个 ref const { x, y } = useMouse(); // 如果不想用上面的结构,你可以看使用reactive包裹 const mouse = reactive(useMouse()) // mouse.x 链接到了原来的 x ref console.log(mouse.x)
组合式函数只能在setup中同步调用
自定义指令
自定义指令以
v
开头的驼峰写法。 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。<script setup> // 在模板中自定义指令 v-focus const vFocus = { mounted: (el) => el.focus() } </script> <template> <!--使用--> <input v-focus /> </template>
可以在入口文件中定义
app.directive('focus', {})
。指令钩子
const myDirective = { // 在绑定元素的 attribute 前 // 或事件监听器应用前调用 created(el, binding, vnode, prevVnode) { // 下面会介绍各个参数的细节 }, // 在元素被插入到 DOM 前调用 beforeMount(el, binding, vnode, prevVnode) {}, // 在绑定元素的父组件 // 及他自己的所有子节点都挂载完成后调用 mounted(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件更新前调用 beforeUpdate(el, binding, vnode, prevVnode) {}, // 在绑定元素的父组件 // 及他自己的所有子节点都更新后调用 updated(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件卸载前调用 beforeUnmount(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件卸载后调用 unmounted(el, binding, vnode, prevVnode) {} }
钩子参数说明:
-
el
:指令绑定到的元素。这可以用于直接操作 DOM。 -
binding
:一个对象,包含以下属性。 -
value
:传递给指令的值。例如在v-my-directive="1 + 1"
中,值是 2。 -
oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。 -
arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是 "foo"。 -
modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。 -
instance
:使用该指令的组件实例。 -
dir
:指令的定义对象。 -
vnode
:代表绑定元素的底层 VNode。 -
prevNode
:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用 - 钩子参数
- 示例:
<div v-example:foo.bar.bar2="baz">
foo为参数(参赛可以是动态的v-e:[arg].bar=),bar为修饰符,baz为值(值可以是对象)。 简化指令,后跟一个函数会在动在
mounted
和updated
时都调用app.directive('color', (el, binding) => { // 这会在 `mounted` 和 `updated` 时都调用 el.style.color = binding.value })
组件上使用时注意:组件只能有一个根节点。
插件
插件形式:
import { createApp } from 'vue' const app = createApp({}) // 1、安装函数,如:myPluginFun(app,options){ } app.use(myPluginFun, { /* 可选的参数,会传递给 安装函数myPluginFun */ }) // 2、拥有 install() 方法的对象 const myPlugin = { install(app, options) { // 配置此应用 } }
插件常见场景
- 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令
- 通过 app.provide() 使一个资源可被注入进整个应用
- 向 app.config.globalProperties 中添加一些全局实例属性或方法
pinia状态管理器
创建
使用defineStore()
函数创建一个Store,第一个参数为id,第二个参数可接受两类值:Setup 函数或 Option 对象
// stores/counter.js import { defineStore } from 'pinia' // 对象创建 export const useCounterStore = defineStore('counter', { // 为了完整类型推理,推荐使用箭头函数 state: () => { return { count: 0 } }, getters:{ double: (state) => state.count * 2, }, actions: { increment() { this.count++ }, }, }) // 函数创建,需要return才行 export const useCounterStore = defineStore('counter', () => { // 等于state 属性 const count = ref(0); // 等于getters属性 const doubleCount = computed(() => count.value * 2) // 等于actions function increment() { count.value++ } return { count, increment,doubleCount } })
使用
<script setup> import { useCounterStore } from '@/stores/counter' // 可以在组件中的任意位置访问 `store` 变量 const store = useCounterStore() console.log(store.count) </script>
store 是一个用 reactive 包装的对象,无需.value
。
使用 storeToRefs()
可以从store结构,保持数据的响应式,继续上面例子:
<script setup> import { storeToRefs } from 'pinia' const store = useCounterStore(); // `name` 和 `doubleCount` 是响应式的 ref // 同时通过插件添加的属性也会被提取为 ref // 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性 const { name, doubleCount } = storeToRefs(store); // 作为 action 的 increment 可以直接解构 const { increment } = store </script>
其他
性能优化时,尽量减少不必要的组件抽象,使用组合式函数。可以使用shallowRef
和shallowReactive
浅层响应式函数。
vue渲染html时,可以使用h()
函数和jsx
,[查看语法](https://cn.vuejs.org/guide/extras/render-function.html#jsx-tsx])。