本文最后更新于 2024-03-22T23:32:40+00:00
插槽
Vue2:插槽 Slots | Vue.js
Vue3:插槽 Slots | Vue.js
作用:HTML 内容的传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <button class="saveBtn"> <slot></slot> </button>
<FancyButton> 保存 </FancyButton>
<button class="saveBtn"> 保存 </button>
|
渲染逻辑如下:
其中的<slot>
标签标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
上述代码,类比如下:
1 2 3 4 5 6 7 8 9
| FancyButton('保存')
function FancyButton(slotContent) { return `<button class="saveBtn"> ${slotContent} </button>` }
|
插槽内容可以是任意合法的模板内容,不局限于文本,可以是 HTML、组件 等
默认内容
1 2 3 4 5 6 7 8 9 10 11 12 13
| <button class="saveBtn"> <slot>提交</slot> </button>
<FancyButton> </FancyButton>
<button class="saveBtn"> 提交 </button>
|
当无插槽内容时,可以展示<slot>
内的内容
具名插槽
<slot>
标签可以name
属性,用来标记插槽内容的展示
name 的值可以自定义,默认为default
1 2 3 4
| <button class="saveBtn"> <slot>提交</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
| <div> <header> <slot name="header" /> </header> <section> <slot name="default" /> // 等价于 <slot /> </section> <footer> <slot name="footer" /> </footer> </div>
<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>
<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
| <div> <header> <slot name="header" /> </header> <section> <slot name="default" /> // 等价于 <slot /> </section> <footer> <slot name="footer" /> </footer> </div>
<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
| <template> <button class="saveBtn"> <slot>提交</slot> </button> </template> <script setup> const count = 1 </script>
<template> <FancyButton> {{ flag ? '提交' : '暂存' }} // '提交' {{ count }} // 报错,count 是子组件的数据,无法在该作用域下访问 </FancyButton> </template> <script setup> const flag = true </script>
<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
| <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
| <Content v-slot="defaultSlotProps"> <div>我是主要内容 xxxxxxx:{{ defaultSlotProps.contentDefaultString }}</div> </Content>
|
具名插槽接受传参语法:<template v-slot:slotName="slotProps"> ... </template>
1 2 3 4 5 6
| <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
| <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) { return `<div>我是主要内容 xxxxxxx:${ slotProps.contentDefaultString }</div>` }, header(slotProps) { 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"; _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) } } </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())
|