一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 4 天,点击查看活动详情。
想着作为一个 Vuer,平时却没有怎么好好的看过官方文档,这次找了个时间看了一番,不得不说 Vue 的文档写的还是很详细的。从里面整理出来一些小技巧,希望能帮助大家提高平时的开发效率。毕竟:
传输一个对象的所有属性
平时我们在开发的时候,经常会通过父组件给子组件传递很多 prop,一般我们可能会这样写:
<child-comp
:prop1="prop1"
:prop2="prop2"
:prop3="prop3"
:prop4="prop4"
...
></child-comp>
这样写当然也是没问题的, 但是这样的写法并不是很优雅,并且每需要多传一个属性给子组件,我就需要多写一个类似:prop="prop"的属性传递,写多了也很繁琐。
其实我们可以使用 v-bind="object"来代替一个个的 :prop-name="propValue", v-bind会传递这个对象中的所有 property给子组件。
假设我们要传递给子组件这么一些属性
post: {
id: 1,
title: 'My Journey with Vue'
}
通过 v-bind,我们可以写成这样:
<child-comp v-bind="post"></child-comp>
这种写法其实是等同于下面这种写法的,但是会方便很多。
<child-comp v-bind:id="post.id" v-bind:title="post.title"></child-comp>
$attrs 实现透传
包含了父作用域中不作为组件
props或自定义事件的attribute绑定和事件。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定,并且可以通过v-bind="$attrs"传入内部组件——这在创建高阶的组件时会非常有用。
简单来说,就是在父作用域传给子组件的一些属性或者事件,但是在子作用域又没有进行定义的,都会被包含在 $attrs 里面,比如 class,style,id,在创建高阶的组件的时候非常有用。
我们在用基础组件的时候,往往需要根据业务场景对这些基础组件进行一些封装,这时候 $attrs就能发挥很大的作用。通过 v-bind="$attrs",轻松实现属性透传。
但是当组件是单个根节点时,非 prop 的 attribute 会自动添加到根结点的 attribute 上,这时候我们可以通过设置inheritAttrs: false将其禁用,以保证非 prop 的 attribute只在我们添加的元素上生效。
// 子组件
<template>
<div>$attrs: <input v-bind="$attrs" /></div>
</template>
<script>
export default {
name: "AttrsChildcomp",
inheritAttrs: false,
created() {
console.log(this.$attrs);
},
};
</script>
// 父组件
<template>
<div>
<s-input
id="1"
class="input"
style="color: red"
placeholder="请输入"
@input="change"
>
</s-input>
</div>
</template>
显示的效果如下:
需要注意的点是,在 Vue2 中,是可以通过 v-on="$listeners" 来获取传递的事件,但是在 Vue3 中 $listeners被移除了,通过 $attrs 就能获取到事件监听器。
具体示例放在了 codesandox 中,可以戳这里。
Props 验证
平时在使用 props 传递的时候,是不是只会这样写
props: ['propsA']
// 或者这样
props: {
propB: {
type: Number,
default: 100
},
}
但其实,prop 也能自定义验证函数,实现更精细的控制,这在开发一个公共类型的组件时尤其有帮助。
props: {
// 自定义验证函数
propC: {
validator(value) {
// 这个值必须与下列字符串中的其中一个相匹配
return ["success", "warning", "danger"].includes(value);
},
},
},
当 prop 验证失败的时候,开发环境下 Vue 将会产生一个控制的告警用于提示。
定义 v-model 修饰符
v-model有许多内置的修饰符,比如 .trim,.number,可以更好的提高我们的开发效率,但是在某些情况下,我们还需要有自定义的修饰符。
我们来演示一个自定义修饰符 capitalize,它可以将 v-model 绑定的字符串第一个字母大写,添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。
// 父组件
<Modifier v-model.capitalize="myText" />
// 子组件
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
<script>
export default {
name: "Modifier",
props: {
// v-model 默认绑定的属性
modelValue: String,
// 修饰符会通过 modelModifiers 传递
modelModifiers: {
default: () => ({}),
},
},
methods: {
emitValue(e) {
let value = e.target.value;
// 在输入的时候,发现存在 capitalize 修饰符,执行首字母变大写的逻辑
if (this.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1);
}
this.$emit("update:modelValue", value);
},
},
created() {
console.log(this.modelModifiers.capitalize); // true
},
};
</script>
在这个示例中,每当输入框触发 input 事件时,都会将首字母变大写。示例在这。
Provide/Inject
有时候我们会碰到一些深度嵌套的组件,当孙孙孙子组件想用顶层父组件的内容,如果仍然将 prop 沿着组件一层一层往下传,会非常的麻烦,这时候就可以使用 provide 和 inject。
父组件用一个 provide 来提供数据,子组件用 inject来接受数据,我们可以将这种方式看作是长距离的 prop,只是父组件并不知道哪些自组件用了这些数据,而子组件也并不知道接收的属性来自哪里。
// 父组件
export default {
name: "ParentComponent",
// provide: {
// provideData: "provideData",
// },
// 一般使用上面的方式即可,但是要使用到组件的实例属性,则需要下面这种方式
provide() {
return {
provideData: this.str.length,
};
},
};
// 孙子组件
export default {
name: "ChildComponent",
inject: ["provideData"],
};
但是这样传递的数据是没有响应式的,即父组件 provide 的数据更改了,孙子组件 inject 到的数据也不能做出相应的更改,但是可以通过 provide 传递一个具有响应式的数据来改变这种情况。
示例戳这里。
指令的动态参数
在 Vue 中,指令的参数是可以使用表达式的,只需要用方括号括起来:
<a v-bind:[attributeName]="url"> ... </a>
// 或者
<a v-on:[eventName]="doSomething"> ... </a>
在示例中,attributeName 和 eventName都会作为表达式被求值,将求得的值作为最终的参数使用。
如果 attributeName 为 href ,那就等价于 v-bind:href。
同理,如果当 eventName 的值为 "focus" 时,v-on:[eventName]将等价于 v-on:focus
监听组件的生命周期
假设我有一个父组件 <parent-component> 和 一个子组件 <child-component>, 我想在子组件更新的时候,父组件进行一些逻辑处理。一般可能会在子组件中的生命周期函数里 emit('something'),但其实有更简洁的方式
<template>
<child-component @hook:updated="onUpdated">
</template>
这样就可以轻松在父组件内部轻松监听到子组件的生命周期,从而避免父子组件产生更强的耦合。
在 Vue2 中,这些事件名和相应的生命周期钩子一致,并带有 hook: 前缀,但是在 Vue3 中,事件名附带的是 vnode- 前缀:
<template>
<child-component @vnode-updated="onUpdated">
</template>
组件样式特性
scoped
当 <style> 标签带有 scoped attribute 的时候,它的 CSS 只会应用到当前组件的元素上。通过PostCSS转换可以看到,在样式后面添加了[data-v-f3f3eg9],来标识这个样式只在该组件内生效。
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
插槽选择器
我们在创建一个组件的时候,为了灵活性,有时候会在组件内部设置插槽,但是在默认情况下,该组件的作用域样式是影响不到 <slot/>里的内容的,因为这些内容被认为是父组件的,但是我们可以通过 :slotted 伪类以确切地将插槽内容作为选择器的目标。
<style scoped>
:slotted(div) {
color: red;
}
</style>
深度选择器
在需求的开发中,难免会用到第三方的组件库,但是在 scoped 下,又不能对第三方组件样式进行修改,放在全局又容易对其他地方产生影响,这时候可以使用 :deep() 这个伪类, 或者使用 ::v-deep 和 >>> 深度选择器。
<style scoped>
.a :deep(.b) {
/* ... */
}
//或者
.a >>> .b {
/* ... */
}
</style>
<style lang="scss" scoped>
.a ::v-deep .b {
/* ... */
}
</style>
全局选择器
有时候需要在同一个组件中同时包含作用域样式和非作用域样式,会这样写:
<style>
/* global styles */
</style>
<style scoped>
/* local styles */
</style>
需要两个 <style> 标签,如果组件中需要的全局样式不是很多的时候,可以使用 :global 伪类来实现,就可以少写一个标签了。
<style scoped>
:global(.red) {
color: red;
}
</style>
$event
有时候在绑定了事件时,我们需要传一些参数的情况下,还想获取原始的 DOM 事件,可以用特殊变量 $event 把它传入方法:
// 在方法中传入 $event
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn(message, event) {
// 现在可以访问到原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
}