Code前端首页关于Code前端联系我们

一、Vue2里JSX和template的slot核心区别是啥?

terry 19小时前 阅读数 10 #Vue

p>咱做Vue2项目时,用JSX写组件插槽(slot)总容易懵?要么插槽不显示,要么作用域传值不对,甚至具名插槽混得乱七八糟…其实JSX里的slot和template写法差异不小,今天从区别、基础用法、实战场景、避坑点四个维度唠明白,以后写JSX插槽再也不踩雷~

先看template的slot:靠<slot>标签、name属性、v-slot指令实现,是「声明式写法」——你告诉Vue“这里要插内容”,Vue解析模板时自动处理插槽分发,比如子组件写<slot name="header"></slot>,父组件用<template #header>...</template>,逻辑全藏在Vue的模板解析规则里。

再看JSX的slot:JSX本质是h函数的语法糖,插槽得靠this.$slots(匿名/具名插槽)、this.$scopedSlots(作用域插槽)主动“抓取”内容,父组件传插槽时要通过编程式逻辑控制(比如传函数、加slot属性),可以理解为:JSX把“声明式插内容”变成了“用JS逻辑主动传、主动渲染”。

举个直观对比:

子组件(template vs JSX)

  • template版本:

    <template>  
    <div>  
      <slot name="header"></slot> <!-- 具名插槽 -->  
      <slot :msg="msg"></slot> <!-- 作用域插槽 -->  
    </div>  
    </template>  
    <script>  
    export default { data() { return { msg: 'hi' } } }  
    </script>  
  • JSX版本(用render函数写JSX):

    export default {  
    data() { return { msg: 'hi' } },  
    render(h) {  
      return (  
        <div>  
          {this.$slots.header} {/* 对应template的<slot name="header"> */}  
          {this.$scopedSlots.default({ msg: this.msg })} {/* 对应template的<slot :msg="msg"> */}  
        </div>  
      );  
    }  
    };  

父组件(template vs JSX)

  • template版本:

    <MyComponent>  
    <template #header>头部</template> <!-- 传具名插槽 -->  
    <template #default="scope">{{ scope.msg }}</template> <!-- 传作用域插槽 -->  
    </MyComponent>  
  • JSX版本(用render函数写JSX):

    render(h) {  
    return (  
      <MyComponent>  
        <div slot="header">头部</div> {/* 传具名插槽,用slot属性 */}  
        {({ msg }) => <div>{msg}</div>} {/* 传作用域插槽,用函数接收数据 */}  
      </MyComponent>  
    );  
    }  

总结区别:template是“声明规则让Vue自动处理”,JSX是“用JS主动控制插槽的传和渲染”,语法和逻辑更偏向JavaScript。

JSX里写slot分几步?匿名、具名、作用域全拆解

JSX写插槽,核心是「子组件主动渲染插槽 + 父组件主动传插槽内容」,下面分三种插槽类型拆解步骤:

匿名插槽(默认插槽)怎么玩?

子组件:在JSX里用this.$slots.default渲染默认插槽内容,如果没写这行,父组件传的默认内容不会显示(和template逻辑一致,得主动渲染)。

父组件:直接把内容当“子元素”传给子组件,不需要额外标记。

举个例子:

  • 子组件DefaultSlot.jsx

    export default {  
    render(h) {  
      return (  
        <div class="card">  
          <h2>组件自身标题</h2>  
          {this.$slots.default} {/* 渲染父组件传的默认内容 */}  
        </div>  
      );  
    }  
    };  
  • 父组件调用:

    <DefaultSlot>  
    <p>这是父组件传给默认插槽的内容</p>  
    <button>还能传任意HTML结构</button>  
    </DefaultSlot>  

渲染后结构:

<div class="card">  
  <h2>组件自身标题</h2>  
  <p>这是父组件传给默认插槽的内容</p>  
  <button>还能传任意HTML结构</button>  
</div>  

具名插槽咋搞?

子组件:用this.$slots['插槽名']渲染指定具名插槽(比如this.$slots.header)。

父组件:给要传的内容加slot="插槽名"属性,告诉子组件“这段内容对应哪个插槽”。

举个完整例子:

  • 子组件HeaderSlot.jsx(拆分头部、主体插槽):

    export default {  
    render(h) {  
      return (  
        <div class="card">  
          <header>{this.$slots.header}</header> {/* 渲染header插槽 */}  
          <main>{this.$slots.default}</main> {/* 渲染默认插槽 */}  
        </div>  
      );  
    }  
    };  
  • 父组件调用:

    <HeaderSlot>  
    <div slot="header">  
      <h1>这是页面大标题</h1>  
      <p>副标题也能塞这儿</p>  
    </div>  
    <p>这是主体内容,属于默认插槽</p>  
    <button>甚至可以混着传</button>  
    </HeaderSlot>  

渲染后结构:

<div class="card">  
  <header>  
    <h1>这是页面大标题</h1>  
    <p>副标题也能塞这儿</p>  
  </header>  
  <main>  
    <p>这是主体内容,属于默认插槽</p>  
    <button>甚至可以混着传</button>  
  </main>  
</div>  

作用域插槽(带数据的插槽)咋实现?

子组件:用this.$scopedSlots['插槽名'](要传的数据),把数据包成对象传给父组件,比如传时间:this.$scopedSlots.footer({ time: new Date() })

父组件:传一个函数作为子元素,函数的参数就是子组件传过来的数据,比如{({ time }) => <div>时间:{time}</div>}

举个例子(子组件给父组件传当前时间):

  • 子组件TimeSlot.jsx

    export default {  
    render(h) {  
      return (  
        <div class="time-card">  
          {this.$scopedSlots.footer({ time: new Date() })}  
        </div>  
      );  
    }  
    };  
  • 父组件调用:

    <TimeSlot>  
    {({ time }) => (  
      <div class="footer">  
        最新更新时间:{time.toLocaleTimeString()}  
      </div>  
    )}  
    </TimeSlot>  

渲染后,父组件能拿到子组件的时间数据,动态显示:

<div class="time-card">  
  <div class="footer">最新更新时间:15:23:45</div>  
</div>  

哪些场景下JSX写slot更顺手?

JSX的“编程式”特性,让它在这些场景下比template更灵活高效:

需要复杂逻辑判断

如果父组件传插槽时,要根据权限、状态等写一堆if/else、循环,JSX的JavaScript语法比template的v-if/v-for更直观。

根据用户角色显示不同操作栏”:

<ActionSlot>  
  {() => {  
    if (user.role === 'admin') {  
      return (  
        <div>  
          <button>删除</button>  
          <button>编辑</button>  
        </div>  
      );  
    } else {  
      return <button>仅查看</button>;  
    }  
  }}  
</ActionSlot>  

子组件ActionSlot.jsx只需渲染this.$scopedSlots.default(),逻辑全在父组件JSX里,清晰又好维护。

封装高度可定制的通用组件

比如写表格组件<MyTable>,列的渲染、操作按钮全靠插槽,用JSX可以动态生成列的插槽内容(比如循环数组渲染多列),比template写一堆<template #column>更高效。

伪代码示例:

<MyTable :data="tableData">  
  {columns.map((col, idx) => (  
    <template slot={col.key}>  
      {/* 这里用JS逻辑定制每列的渲染 */}  
      {({ row }) => <span>{row[col.key]}</span>}  
    </template>  
  ))}  
</MyTable>  

函数式组件配合JSX

Vue2的函数式组件(functional: true)本身靠render函数运行,用JSX写插槽天然契合——不需要this上下文,传参更直接。

函数式组件示例:

const FunctionalCard = {  
  functional: true,  
  render(h, context) {  
    return (  
      <div class="func-card">  
        {context.slots().header} {/* 取具名插槽header */}  
        {context.scopedSlots.default({ msg: 'func msg' })} {/* 传作用域数据 */}  
      </div>  
    );  
  }  
};  
// 父组件调用  
<FunctionalCard>  
  <div slot="header">函数式组件的头部</div>  
  {({ msg }) => <div>{msg}</div>}  
</FunctionalCard>  

JSX写slot最容易踩的3个坑,避坑指南来了!

作用域插槽“拿不到数据”?检查参数和传值

坑点:父组件函数的参数名和子组件传的不一致,或者子组件没调用this.$scopedSlots

比如子组件传{ list: data },父组件写成({ data }) => ...,参数名对不上就拿空值。

解决:子组件和父组件约定好传参的key,比如子组件固定传{ item: ... },父组件用({ item }) => ...接收。

具名插槽“不显示”?检查插槽名和渲染逻辑

坑点:父组件用了v-slot:header(template写法)但JSX里得用slot="header";或者子组件渲染this.$slots.header,父组件传slot="head"(名字对不上)。

解决:统一插槽名!子组件this.$slots['xxx']和父组件slot="xxx"xxx必须完全一致。

“不响应式”?注意数据绑定

坑点:JSX里写插槽时,用了非响应式数据(比如普通变量、闭包变量),数据更新后插槽不刷新。

比如错误写法:

export default {  
  render(h) {  
    let count = 0;  
    setInterval(() => count++, 1000); // count不是响应式的  
    return (  
      <MyComponent>  
        {() => <div>{count}</div>}  
      </MyComponent>  
    );  
  }  
};  

解决:把数据放到data、计算属性或ref里(确保响应式),比如改成:

export default {  
  data() { return { count: 0 } },  
  mounted() {  
    setInterval(() => this.count++, 1000);  
  },  
  render(h) {  
    return (  
      <MyComponent>  
        {() => <div>{this.count}</div>}  
      </MyComponent>  
    );  
  }  
};  

JSX写slot的核心逻辑

记住这张“流程表”:

  1. 子组件要渲染插槽 → 用this.$slots(匿名/具名)或this.$scopedSlots(作用域)主动抓取;
  2. 父组件传内容 → 匿名插槽直接写子元素,具名插槽加slot属性,作用域插槽传函数并接收参数;
  3. 避坑关键 → 统一插槽名、确保数据响应式、约定作用域传参的key

其实JSX里的slot没那么难,核心是理解“编程式控制插槽”和“template声明式”的区别,再结合场景练几个例子,自然就通了~下次写Vue2 JSX组件,插槽再也不慌啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门