3-2、Vue 进阶

插槽

Vue2:插槽 Slots | Vue.js

Vue3:插槽 Slots | Vue.js

作用:HTML 内容的传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// FancyButton.vue
<button class="saveBtn">
<slot></slot>
</button>

// Index.vue
<FancyButton>
保存
</FancyButton>

// 最终渲染出来的 DOM 为:
<button class="saveBtn">
保存
</button>

渲染逻辑如下:

其中的<slot>标签标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

上述代码,类比如下:

1
2
3
4
5
6
7
8
9
// 父元素传入插槽内容
FancyButton('保存')

// FancyButton 在自己的模板中渲染插槽内容
function FancyButton(slotContent) {
return `<button class="saveBtn">
${slotContent}
</button>`
}

插槽内容可以是任意合法的模板内容,不局限于文本,可以是 HTML、组件 等

默认内容

1
2
3
4
5
6
7
8
9
10
11
12
13
// FancyButton.vue
<button class="saveBtn">
<slot>提交</slot>
</button>

// Index.vue
<FancyButton>
</FancyButton>

// 最终渲染出来的 DOM 为:
<button class="saveBtn">
提交
</button>

当无插槽内容时,可以展示<slot>内的内容

具名插槽

<slot>标签可以name属性,用来标记插槽内容的展示

name 的值可以自定义,默认为default

1
2
3
4
// FancyButton.vue
<button class="saveBtn">
<slot>提交</slot> // 等价于<slot name="defalut">提交</slot>
</button>

name="defalut"时,则称为默认插槽,用来承载父元素的所有无名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Content.vue
<div>
<header>
<slot name="header" />
</header>
<section>
<slot name="default" /> // 等价于 <slot />
</section>
<footer>
<slot name="footer" />
</footer>
</div>

// Index.vue
<Content>
<template v-slot:header> // v-slot:header 等价于 #header
<h1>我是标题</h1>
</template>
<template #default> // #default 可以省略不要
<div>我是主要内容 xxxxxxx</div>
</template>
<template #footer>
<p>我是底部内容 xxxxxxx</p>
</template>
</Content>

// 最终渲染出来的 DOM 为:
<div>
<header>
<h1>我是标题</h1>
</header>
<section>
<div>我是主要内容 xxxxxxx</h1>
</section>
<footer>
<p>我是底部内容 xxxxxxx</p>
</footer>
</div>

渲染逻辑如下:

上述代码,类比如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Content({
default: {
// ...
},
header: {
// ...
},
footer: {
// ...
},
})

function Content(slots) {
return `
<div>
<header>
${slots.header}
</header>
<section>
${slots.default}
</section>
<footer>
${slots.footer}
</footer>
</div>
`
}

动态插槽名

支持插槽内容的名称为变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Content.vue
<div>
<header>
<slot name="header" />
</header>
<section>
<slot name="default" /> // 等价于 <slot />
</section>
<footer>
<slot name="footer" />
</footer>
</div>

// Index.vue
<template>
<Content>
<template v-slot:[headerName]> // v-slot:[headerName] 等价于 #[headerName]
<h1>我是标题</h1>
</template>
<div>我是主要内容 xxxxxxx</h1>
<template #[footerName]>
<p>我是底部内容 xxxxxxx</p>
</template>
</Content>
</template>
<script setup>
const headerName = "header"
const footerName = "footer"
</script>

渲染作用域

插槽内容仅能访问其定义时作用域的数据,不能访问子组件的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// FancyButton.vue
<template>
<button class="saveBtn">
<slot>提交</slot>
</button>
</template>
<script setup>
const count = 1
</script>

// Index.vue
<template>
<FancyButton>
{{ flag ? '提交' : '暂存' }} // '提交'
{{ count }} // 报错,count 是子组件的数据,无法在该作用域下访问
</FancyButton>
</template>
<script setup>
const flag = true
</script>


// 最终渲染出来的 DOM 为:
<button class="saveBtn">
提交
</button>

作用域插槽

如果想要插槽内容访问子组件的数据,可以通过<slot>标签回传值
传出语法:<slot :propKey1="propValue1" :propKey2="propValue2" ... />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Content.vue
<template>
<div>
<header>
// 具名插槽传值
<slot name="header" :contentHeaderString="contentHeaderString" />
</header>
<section>
// 默认插槽传值
<slot name="default" :contentDefaultString="contentDefaultString" />
</section>
<footer>
<slot name="footer" />
</footer>
</div>
</template>
<script setup lang="ts">
const contentHeaderString = '我是 Content 组件内的 contentHeaderString 变量'
const contentDefaultString = '我是 Content 组件内的 contentDefaultString 变量'
</script>

默认插槽接受传参语法:<组件名 v-slot="slotProps"> ... </组件名>

1
2
3
4
// Index.vue
<Content v-slot="defaultSlotProps"> // 默认插槽接受传参
<div>我是主要内容 xxxxxxx:{{ defaultSlotProps.contentDefaultString }}</div>
</Content>

具名插槽接受传参语法:<template v-slot:slotName="slotProps"> ... </template>

1
2
3
4
5
6
// Index.vue
<Content>
<template v-slot:header="slotProps"> // 可简写为 #header="slotProps",具名插槽接受传参
<h1>我是标题:{{ slotProps.contentHeaderString }}</h1>
</template>
</Content>

当存在其他命名槽时,默认槽必须在自定义元素上使用“<template>”

1
2
3
4
5
6
7
8
9
10
11
12
// Index.vue
<Content>
<template v-slot:header="slotProps"> // 可简写为 #header="slotProps",具名插槽接受传参
<h1>我是标题:{{ slotProps.contentHeaderString }}</h1>
</template>
<template v-slot="defaultSlotProps"> // 默认插槽接受传参,必须使用 <template>
<div>我是主要内容 xxxxxxx:{{ defaultSlotProps.contentDefaultString }}</div>
</template>
<template #footer>
<p>我是底部内容 xxxxxxx</p>
</template>
</Content>

渲染逻辑如下:

上述代码,类比如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Content({
default(slotProps) {
// slotProps = { contentHeaderString }
return `<div>我是主要内容 xxxxxxx:${ slotProps.contentDefaultString }</div>`
},
header(slotProps) {
// slotProps = { contentHeaderString }
return `<div>我是标题:${ slotProps.contentHeaderString }</div>`
},
footer: {
// ...
},
})

function Content(slots) {
const contentHeaderString = '我是 Content 组件内的 contentHeaderString 变量'
const contentDefaultString = '我是 Content 组件内的 contentDefaultString 变量'

return `
<div>
<header>
${slots.header({ contentHeaderString })}
</header>
<section>
${slots.default({ contentDefaultString })}
</section>
<footer>
${slots.footer}
</footer>
</div>
`
}

过滤器

Vue3 已不支持

Vue2:过滤器 — Vue.js

在模板里面对数据的二次加工

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>{{ message | capitalize }}</div>
<div :id="id | capitalize">xxx</div>
</template>
<script>
export default {
data() {
return {
message: 'hello',
id: 'div-1'
}
},
filters: {
capitalize(value) {
return value[0].toUpperCase() + value.slice(1)
}
}
}
</script>

// 最终渲染出来的是:
<div>Hello</div>
<div id="Div-1">xxx</div>

注意:过滤器内部的 this 不会自动绑定到 Vue 实例上,其值为undefined

原因是:过滤器本质是纯函数(对进来的数据处理然后返回结果),所以设计时就不应该跟 Vue 实例挂钩

JSX

基础

全称:JavaScript XML,是一种在 JavaScript 中嵌入类似 HTML 语法的扩展语法

1
const Hello = <div>Hello, World!~</div>

看起来像 HTML,本质还是 JS 代码,最终会编译为 JS 代码(代表了对应的 DOM/VDOM),最后使用框架的能力渲染到页面上

上述代码经过编译后为(React 举例):

1
2
3
4
import { jsx as _jsx } from "react/jsx-runtime";
/*#__PURE__*/_jsx("div", {
children: "Hello, world!~"
});

Vue2 中 JSX 写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
export default {
data() {
return {
money: 100,
};
},
render() {
const MoneyDom =
this.money > 99 ? <h1>99+ 元</h1> : <h2>{this.money} 元</h2>;

return MoneyDom;
},
};
</script>

// 最终渲染出来的是:
<h1>99+ 元</h1>

Vue3 中 JSX 写法(需要配置):

1
2
3
4
5
6
7
8
9
10
import { defineComponent, ref } from 'vue'

export default defineComponent({
setup() {
const money = ref(100)
const MoneyDom = money.value > 99 ? <h1>99+ 元</h1> : <h2>{money.value} 元</h2>

return () => MoneyDom
}
})

语法糖实现

v-model:事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { defineComponent, ref } from 'vue'

export default defineComponent({
setup() {
const inputValue = ref('')
const handleOnInput = (event) => {
inputValue.value = event.target.value
}
return () => (
<div>
<input @input={handleOnInput}/>
<p>输入的内容是:{ inputValue }</p>
</div>
)
}
})

v-if:三目运算

1
2
3
4
5
6
7
8
9
10
11
12
import { defineComponent, ref } from 'vue'

export default defineComponent({
setup() {
const isAdd = true
return () => (
<div>
<button>{ isAdd ? '新建' : '保存' }</button>
</div>
)
}
})

v-for:循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { defineComponent, ref } from 'vue'

export default defineComponent({
setup() {
const userList = [{ name: 'lisi', age: 29 }, { name: '张三', age: 39 }]
return () => (
<div>
<ul>
{
userList.map(item => {
return <li key={item.name}>
<span>姓名:{item.name}</span>
<span>年龄:{item. age}</span>
</li>
})
}
</ul>
</div>
)
}
})

好处:更加灵活

坏处:结构不够清晰

混入

Vue2:混入 — Vue.js

Vue3(不再推荐使用):混入 | Vue.js

基础

一种组合组件中的可复用功能的方式。更关注于组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var mixin = {
data() {
return {
message: 'hello',
foo: 'abc'
}
}
}

<script>
export default {
mixins: [mixin],
data() {
return {
message: '你好',
bar: 'def'
}
},
created() {
console.log(this.$data)
// { message: "你好", foo: "abc", bar: "def" }
}
}
</script>

顺序

生命周期函数

同名时,先调用混入的,后调用组件的

类似于:

1
2
3
4
5
6
最终生命周期函数 = [
mixin1.生命周期函数,
mixin2.生命周期函数,
...
组件.生命周期函数
].map(fn => fn())

其他的

同名时,使用组件的

类似于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
最终 data = Object.assgin(
mixin1.data(),
mixin2.data(),
...
组件.$data()
)

最终 methods = Object.assgin(
mixin1.methods,
mixin2.methods,
...
组件.methods
)

// 其他等......

继承

Vue2:继承 | Vue.js

Vue3(不再推荐使用):继承 | Vue.js

一种继承组件中的可复用功能的方式。更关注于继承

写法与顺序都跟混入一样,关键词为extends: extendObj即可

extends 与 mixins共存时,extends优先级更高

类似于:

1
2
3
4
5
6
7
最终生命周期函数 = [
extends.生命周期函数,
mixin1.生命周期函数,
mixin2.生命周期函数,
...
组件.生命周期函数
].map(fn => fn())

3-2、Vue 进阶
https://mrhzq.github.io/职业上一二事/前端面试/前端八股文/3-2、Vue 进阶/
作者
黄智强
发布于
2024年1月13日
许可协议