这是我参与11月更文挑战的9天,活动详情查看:2021最后一次更文挑战
Mixin
选项合并
Mixin 即混入,当组件使用 mixins 选项时, 所有 mixin 对象可以被混合进该组件本身的选项。
对于data中的数据,如果 mixin 对象与自身组件的数据 property 有冲突时,会以组件自身为主。
const myMixin = {
data() {
return { number: 2 }
},
}
const app = Vue.createApp({
data() {
return { number: 1 }
},
mixins: [myMixin],
template:/*html*/ `<div>{{number}}</div>`
})
<div>1</div>
生命周期函数也可以被混入到组件中,mixin 对象的钩子会在组件自身的钩子之前被调用。
如methods、computed、components等这些值为对象的选项将会与组件合并为同一个对象。对象中的键名冲突时,取组件自身的键值对。
全局 mixin
上述提到的是局部 mixin,除此之外,我们还可以创建一个全局 mixin。
const app = Vue.createApp({
template:/*html*/ `
<div>{{number}}</div>
<my-component />
`
})
app.component('my-component', {
template: `<div>{{count}}</div>`
})
app.mixin({
data() {
return {
number: 1,
count: 2
}
}
})
使用全局 mixin 可以在 Vue 实例的所有组件中使用 mixin 中的选项。
自定义选项合并
在 Vue 中,我们可以定义一个自定义选项,使用 this.$options 来访问它。
const app = Vue.createApp({
number: 1,
template:/*html*/ `
<div>{{this.$options.number}}</div>
`
})
自定义选项名冲突时,组件的选项优先级高于 mixin 对象。
const myMixin = {
number: 2
}
const app = Vue.createApp({
number: 1,
mixins: [myMixin],
template:/*html*/ `
<div>{{this.$options.number}}</div>
`
})
<div>1</div>
我们可以在app.config.optionMergeStrategies中自己设置自定义选项的优先级:
app.config.optionMergeStrategies.number = (mixinValue, appValue) => {
return mixinValue || appValue;
}
<div>2</div>
自定义指令
在 Vue 中,对 DOM 元素的操作需要我们使用 $refs 来实现。这样的代码无法被复用。
const app = Vue.createApp({
mounted() {
this.$refs.input.focus()
},
template:/*html*/ `
<input ref='input'/>
`
})
我们可以使用 directive 来定义全局自定义指令并在相应的 DOM 调用:
const app = Vue.createApp({
template:/*html*/ `
<input v-focus/>
`
})
app.directive('focus', {
mounted(el) {
el.focus();
},
})
我们还可以定义一个局部自定义指令,首先声明一个指令定义对象,在组件中使用directive选项:
const directives = {
focus: {
mounted(el) {
el.focus();
}
}
}
const app = Vue.createApp({
directives: directives,
template:/*html*/ `
<input v-focus/>
`
})
钩子函数
一个指令定义对象提供以下几个周期函数:created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted。
动态指令参数
自定义组件可以传参,参数可以是动态的。例如 v-pos:[direction]='100' 中, direction 是动态参数。
const app = Vue.createApp({
data() {
return { position: 'left' }
},
template:/*html*/ `
<input v-pos:[position]='50'/>
`
})
app.directive('pos', {
mounted(el, binding) {
el.style.position = 'fixed';
const s = binding.arg || top;
el.style[s] = binding.value + 'px';
},
updated(el, binding) {
el.style.position = 'fixed';
const s = binding.arg || top;
el.style[s] = binding.value + 'px';
},
})
指令定义对象中的钩子函数接收两个参数,第一个参数是该 DOM 元素的引用,第二个参数是传递的参数,上述例子中为binding。binding.arg 是传递给指令的参数,binding.value是传递给指令的值。
函数简写
mounted 和 updated 中若是相同的行为,可以简写为入下形式:
app.directive('pos', (el, binding) => {
el.style.position = 'fixed';
const s = binding.arg || top;
el.style[s] = binding.value + 'px';
})
对象字面量
如果想传递多个值,指令可以接收 JS 对象的字面量,方法是使用 JS 的表达式来作为参数值:
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
在组件中使用
在组件中使用时,仅在单个根节点的组件中生效。自定义指令会传递到根节点上,并且不会通过v-bind='$attrs'传入另一个元素。
Teleport
Teleport 意为传送门。
首先我们实现如下代码,点击 button 时产生一个蒙层效果:
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 200px;
background-color: orange;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.5;
background-color: black;
}
const app = Vue.createApp({
data() {
return {
show: false
}
},
methods: {
handleClick() {
this.show = !this.show
}
},
template:/*html*/ `
<div class='area'>
<button @click='handleClick'>mask</button>
<div class='mask' v-show='show'></div>
</div>
`
})
我们想要的效果是覆盖整个屏幕的蒙层。如果我们要操作 DOM,需要将 mask 类的元素移到 <body> 标签中,操作麻烦。我们可以使用 <teleport> 来实现。
<teleport> 的 to attribute 的值为要渲染到的标签。
<div class='area'>
<button @click='handleClick'>mask</button>
<teleport to='body'>
<div class='mask' v-show='show'></div>
</teleport>
</div>
与组件一起使用
如果 <teleport> 包含 Vue 组件,则它仍将是 <teleport> 父组件的逻辑子组件。
渲染函数
下面的代码通过模板来创建 HTML,内容过于繁杂:
const app = Vue.createApp({
template:/*html*/ `
<my-title :level='1'>
Hello
</my-title>
`
})
app.component('my-title', {
props: ['level'],
template: `
<h1 v-if="level === 1"><slot /></h1>
<h2 v-else-if="level === 2"><slot /></h2>
<h3 v-else-if="level === 3"><slot /></h3>
<h4 v-else-if="level === 4"><slot /></h4>
<h5 v-else-if="level === 5"><slot /></h5>
<h6 v-else-if="level === 6"><slot /></h6>
`
})
我们可以使用 render 渲染函数通过 JS 来实现 HTML 完成相同的功能:
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
return h(
'h' + this.level, // 标签名
{}, // prop 或 attribute
this.$slots.default() // 包含其子节点的数组,这里使用的是默认插槽
)
}
})
template 被编译后会生成 render 函数,render 函数返回的内容是虚拟 DOM,然后产生真实 DOM。这样做使得 Vue 的性能更快,并且可以跨平台开发。
插件
插件把通用性的功能封装起来。
// 定义插件
const myPlugin = {
install(app, options) {
app.provide('name', options.name);
app.directive('focus', {
mounted(el) {
el.focus();
},
});
app.mixin({
mounted() {
console.log('Mixin');
},
});
app.config.globalProperties.$sayHello = 'Hello World';
}
}
const app = Vue.createApp({
template:/*html*/ `
<my-title />
`
})
app.component('my-title', {
inject: ['name'],
mounted() {
console.log(this.$sayHello)
},
template: `
<div>{{name}}</div>
<input v-focus />
`
})
// 使用插件
app.use(myPlugin, { name: 'Evan' })
插件如果是对象,则会调用其中的install方法,它接收两个参数,一个是 Vue 创建的 app,另一个是用户传入的选项。
除此之外,插件还可以是函数,函数本身会被调用:
const myPlugin = (app, options) => {
// ...
}
通过调用 Vue app 的 use 方法使用插件,它接收两个参数,一个是插件的名字,另一个是传入的选项。第二个参数可选。
下面实现了一个插件示例,该插件是一个校验器插件,对 data 中的 property 进行了校验:
const validatorPlugin = (app, options) => {
app.mixin({
created() {
console.log('created')
for (let key in this.$options.rules) {
const item = this.$options.rules[key];
this.$watch(key, (value) => {
const result = item.validate(value);
if (!result) {
console.log(item.message)
}
})
}
}
})
}
const app = Vue.createApp({
data() {
return {
name: 'Jack',
age: 20
}
},
rules: {
age: {
validate: age => age >= 18,
message: 'too young'
},
name: {
validate: name => name.length >= 3,
message: 'too short'
}
},
template:/*html*/ `
<div>name: {{name}}, age: {{age}}</div>
`
})
app.use(validatorPlugin)
使用插件使代码具有更高的可读性和可扩展性。