从一行好奇的代码说起:Vue怎么没有React的props.children

引言

最近在学习 React 的过程中,我发现了一个有趣的特性:父子组件间的 props.children 传递。这让我不禁思考:Vue 中是否也有类似的功能?

React 中的 Children 传递

基础示例

先来看一个简单的父子组件示例:

父组件 App.tsx

tsx 复制代码
import Son from './study/Son.tsx';

function App() {
  return (
    <>
     <Son message={'this is a reactApp'}>
      <span>this is a span</span>
     </Son>
    </>
  )
}

export default App

子组件 Son.tsx

tsx 复制代码
function Son(props: any) {
    console.log(props);
    
    return <div>{props.message}</div>;
}
export default Son;

这是如何工作的?

通过控制台的输出内容可以看到,在 React 中,当你在组件标签内部放置内容时,这些内容会作为 props.children 传递给子组件。这是一个非常强大的特性,它允许组件作为容器来包裹其他组件或元素。

所以,在子组件中,只要使用props.children就可以把它显示出来。:

Vue 中的类似功能

在我的认知中,Vue 似乎不能这样写?实际上,Vue 也有类似的功能,但语法不同:

Vue 3 中的插槽 (Slots)

vue 复制代码
<!-- 父组件 -->
<template>
  <Son message="this is a vue app">
    <span>this is a span</span>
  </Son>
</template>

<!-- 子组件 Son.vue -->
<template>
  <div>
    <div>{{ message }}</div>
    <slot></slot> <!-- 这里会渲染父组件传递的内容 -->
  </div>
</template>

<script setup>
defineProps(['message'])
</script>

Vue 使用 <slot> 元素来接收父组件传递的内容,而 React 使用 props.children。两者语法不同,概念相似,但也有一些区别的。

props.children 与 slot 的本质差异

React 的逻辑:数据驱动,责任自负

在 React 中,当父组件这样写:

tsx 复制代码
<Son>
  <span>我是内容</span>
</Son>

实际上被转译成:

javascript 复制代码
React.createElement(Son, {
  children: React.createElement('span', null, '我是内容')
})

关键点

  1. 数据已经送达 :子组件无论如何都能在 props.children 中访问到这个 <span> 元素
  2. 处理权完全在子组件:子组件可以选择渲染、修改、忽略,甚至把它放到别的地方
  3. 无法"拒绝接收" :这份数据(React 元素引用)已经作为参数传递给了子组件函数
tsx 复制代码
// 子组件可以有各种处理方式
function Son(props) {
  // 1. 正常渲染
  // return <div>{props.children}</div>
  
  // 2. 包装后再渲染
  // return <div className="wrapper">{props.children}</div>
  
  // 3. 有条件地渲染
  // return props.show ? props.children : null
  
  // 4. 拆分处理
  // const childrenArray = React.Children.toArray(props.children)
  // return childrenArray.map(child => <div className="item">{child}</div>)
  
  // 5. 完全忽略
  return <div>我不需要 children</div>
}

Vue 的逻辑:模板驱动,需要显式声明

现象观察

父组件

vue 复制代码
<template>
  <Son message="Hello">
    <span class="important">我是重要内容!</span>
  </Son>
</template>

子组件

vue 复制代码
<!-- 情况1:有 slot 签收 -->
<template>
  <div>
    <div>{{ message }}</div>
    <slot></slot> <!-- span 会被渲染 -->
  </div>
</template>

<!-- 情况2:没有 slot 签收 -->
<template>
  <div>
    <div>{{ message }}</div>
    <!-- 没有 slot,span 就像不存在一样 -->
  </div>
</template>

在 Vue 中:

  1. 编译时处理:Vue 在编译阶段处理插槽内容
  2. 需要显式签收 :只有在子组件模板中写了 <slot>,内容才会被渲染
  3. 未签收就丢弃 :如果没有 <slot>,内容在渲染阶段就被丢弃了

代码背后的逻辑

javascript 复制代码
// Vue 在编译时决定是否包含插槽内容
// 如果子组件没有 slot,父组件的内容就不会被包含在渲染函数中
  • 插槽是模板特性:slot 是模板语法的一部分
  • 编译时决策:在编译阶段决定是否包含内容
  • 更安全的默认行为:防止意外渲染

React 的优势与风险

优势

  1. 灵活性极高:可以任意操作 children
  2. 模式多样:支持 render props、HOC 等模式
  3. 运行时控制:可以在运行时动态决定如何处理
tsx 复制代码
// React 的灵活模式
function Toggle({ children }) {
  const [isOn, setIsOn] = useState(false);
  
  return (
    <>
      <button onClick={() => setIsOn(!isOn)}>
        Toggle
      </button>
      {/* 运行时决定渲染哪个 child */}
      {isOn ? children : null}
    </>
  );
}

风险

  1. 可能浪费资源:即使不渲染,children 也被创建和传递
  2. 需要更多注意:必须显式处理,否则可能意外遗漏

Vue 的优势与局限

优势

  1. 性能优化:未使用的插槽内容不会被包含
  2. 更安全:不会意外渲染未声明的内容
  3. 模板清晰:在模板中明确显示插槽位置
vue 复制代码
<!-- Vue 3 组合式 API 中 -->
<script setup>
// 可以访问 slots
import { useSlots } from 'vue'

const slots = useSlots()
// 检查是否有某个插槽内容
const hasHeader = slots.header
</script>

局限

  1. 灵活性较低:操作插槽内容不如 React 灵活
  2. 模板限制:必须在模板中声明插槽位置
相关推荐
计算机学姐2 小时前
基于SpringBoot的演出购票系统【2026最新】
java·vue.js·spring boot·后端·spring·tomcat·intellij-idea
用户6802659051192 小时前
2025年十大终端管理软件推荐指南
vue.js·后端·面试
孜孜不倦不忘初心2 小时前
Axios 常用配置及使用
前端·axios
sTone873752 小时前
vscode 二开踩坑记录
前端
用户8168694747252 小时前
Effect 执行时机与事件循环交错关系
前端·react.js
心在飞扬2 小时前
langchain学习总结-OutputParser组件及使用技巧
前端·后端
llq_3502 小时前
Ant Design v5 样式兼容性问题与解决方案
前端
triumph_passion2 小时前
React Hook Form 状态下沉最佳实践
前端·react.js
心在飞扬2 小时前
langchain学习总结-两个Runnable核心类的讲解与使用
前端·后端