指令
Vue3保留Vue2的以下指令
- v-text
- v-html
- v-show
- v-if
- v-else
- v-else-if
- v-for
- v-on
- v-bind
- v-model
- v-slot
- v-pre
- v-once
- v-cloak
保留的指令大部分使用方法与v2一样,少数指令在保留原来语法的基础上,增加一些语法
1.改动指令
v-model
v-model的改变,体现在组件中的v-model
使用语法
<template>
<Child v-model="text" />
</template>
<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'
const text = ref('123')
</script><template>
<input
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue']) // 接收props
const emit = defineEmits(['update:modelValue']) // 接受自定义事件
</script>原理解析
上述父组件中使用的v-model会被编译为如下代码
<Child
:modelValue="text"
@update:modelValue="$event => (text = $event)"
/>也就是说,vue3组件中使用v-model绑定一个值相当于:
- 给这个组件添加了一个名为
modelValue的prop,值为绑定的变量- 注意:传递的属性名一定为
modelValue
- 注意:传递的属性名一定为
- 添加名为
update:modelValue的自定义事件,作用是把新值赋值给双向绑定的变量- 在vue2中,给组件绑定的事件为input事件
在子组件中,依然是表单的value动态绑定,和input事件,而调用的是父组件的update:modelValue自定义事件把新的值传给父组件
v-model参数
组件上的 v-model 也可以接受一个参数:
<template>
<div>
<Child v-model:name="myname"/>
</div>
</template>
<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'
const myname = ref('456')
</script><template>
<div>
{{ text }}
<input :value="name" @input="emit('update:name', $event.target.value)" />
</div>
</template>
<script setup>
defineProps(['name'])
const emit = defineEmits(['update:name'])
</script>上述父组件调用子组件相当于以下语法
<Child :name="myname" @update:name="$event=>(myname = $event)"/>这样,子组件中接受的属性名就不用固定写成modelValue
多个v-model
同一个组件中,不能使用多个v-model,但是使用v-model参数的话,就可以多个使用
<template>
<Child v-model:value1="value1" v-model:value2="value2" />
</template>
<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'
const value1 = ref('123')
const value2 = ref('123')
</script><template>
<div>
<input :value="value1" @input="emit('update:value1', $event.target.value)" />
<input :value="value2" @input="emit('update:value2', $event.target.value)" />
</div>
</template>
<script setup>
defineProps(['value1','value2'])
const emit = defineEmits(['update:value1','update:value2'])
</script>不带参数的v-model和带参数的v-model可以一起使用,不过不带参数的v-model仍然是绑定的modelValue
<template>
<Child v-model="value1" v-model:value2="value2" />
</template>
<script setup>
import Child from './views/Child.vue'
import { ref } from 'vue'
const value1 = ref('123')
const value2 = ref('123')
</script><template>
<div>
<input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" />
<input :value="value2" @input="emit('update:value2', $event.target.value)" />
</div>
</template>
<script setup>
defineProps(['modelValue', 'value2'])
const emit = defineEmits(['update:modelValue', 'update:value2'])
</script>处理v-model修饰符
我们知道 v-model 有一些内置的修饰符,例如 .trim,.number 和 .lazy。在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。
我们来创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:
<Child v-model.capitalize="myText" /><script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="props.modelValue" @input="emitValue" />
</template>带参数的 v-model 修饰符
<UserName
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
/><script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])
console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true }
</script>defineModel
1.新增指令
v-memo
仅在 3.2+ 中支持
语法:
缓存一个模板的子树。接受一个数组,只有当数组中的变量更改时,该元素才会被重新渲染
<div v-memo="[valueA, valueB]">
...
</div>当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。
正确指定缓存数组很重要,否则应该生效的更新可能被跳过。v-memo 传入空依赖数组 (v-memo="[]") 将与 v-once 效果相同。
与 v-for 一起使用
v-memo 仅用于性能至上场景中的微小优化,应该很少需要。最常见的情况可能是有助于渲染海量 v-for 列表 (长度超过 1000 的情况):
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>2.自定义指令
Vue3的<script setup>中,一个自定义指令由一个包含类似组件生命周期钩子的对象来定义
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。
比如一个名为vFocus的自定义指令,使用方式为v-focus
<template>
<div v-directive></div>
</template>
<script setup>
const vDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}
</script>指令的钩子会传递以下几种参数:
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="baz">binding 参数会是一个这样的对象:
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}和内置指令类似,自定义指令的参数也可以是动态的。举例来说:
<div v-example:[arg]="value"></div>这里指令的参数会基于组件的 arg 数据属性响应式地更新。
TIP
除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。
2.1 动态参数
正常给指令传递一个参数如下所示
<div v-dir:name></div>在指令中钩子函数的binding参数的arg可以得到此参数
{
arg:'name',
// ..
}我们可以通过一个中括号,传递一个变量,也就是动态参数
<template>
<div v-dir:[arg]></div>
</template>
<script setup>
import {ref} from 'vue'
const arg = ref( "name123")
const vDir = {
mounted(el, binding) {
console.log(binding) // name123
}
}
</script>这样我们可以需要的时候修改arg的值,来达到动态控制参数的名字
WARNING
态参数值的限制
动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。
动态参数语法的限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。
当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:
<a :[someAttr]="value"> ... </a>上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板不受此限制。
3.全局指令
一个全局指令需要在main.js中使用app.directives()来注册
3.1 详细形式
import { createApp } from 'vue'
const app = createApp(App)
const myDirective = {
created(el, binding, vnode) {},
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVnode) {},
updated(el, binding, vnode, prevVnode) {},
beforeUnmount(el, binding, vnode) {},
unmounted(el, binding, vnode) {}
}
app.directives('directiveName',myDirective)此时对象的名称可以随意定义,但directives()第一个参数名决定了指令最终使用的方式
3.2 简化形式
对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
<div v-color="color"></div>app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})