本文最后更新于 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())
 
  |