React中的'插槽'

一、最基础的:children prop(默认插槽)

基本用法

javascript 复制代码
// 父组件
function App() {
  return (
    <Card>
      <h2>这是标题</h2>
      <p>这是内容...</p>
      <button>点击</button>
    </Card>
  );
}

// 子组件 Card(类似 Vue 的默认插槽)
function Card({ children }) {
  return (
    <div className="card">
      {children}  {/* 这里就是插槽位置 */}
    </div>
  );
}

处理没有 children 的情况

jsx

javascript 复制代码
function Card({ children }) {
  return (
    <div className="card">
      {children || <p>默认内容</p>}
    </div>
  );
}

二、命名插槽(类似 Vue 的具名插槽)

方法1:使用多个 props

javascript 复制代码
// 子组件:定义多个插槽位置
function Layout({ header, sidebar, content, footer }) {
  return (
    <div className="layout">
      <header className="header">{header}</header>
      <aside className="sidebar">{sidebar}</aside>
      <main className="content">{content}</main>
      <footer className="footer">{footer}</footer>
    </div>
  );
}

// 父组件使用
function App() {
  return (
    <Layout
      header={<h1>网站标题</h1>}
      sidebar={
        <nav>
          <a href="/">首页</a>
          <a href="/about">关于</a>
        </nav>
      }
      content={<p>主要内容区域...</p>}
      footer={<p>版权信息 © 2024</p>}
    />
  );
}

方法2:使用 children 对象(更接近 Vue 语法)

javascript 复制代码
// 子组件
function Card({ children }) {
  // children 可以是对象
  return (
    <div className="card">
      <div className="card-header">
        {children.header || <h3>默认标题</h3>}
      </div>
      <div className="card-body">
        {children.body || <p>默认内容</p>}
      </div>
      <div className="card-footer">
        {children.footer}
      </div>
    </div>
  );
}

// 父组件
function App() {
  return (
    <Card>
      {{
        header: <h2>自定义标题</h2>,
        body: <p>自定义内容...</p>,
        footer: <button>确认</button>
      }}
    </Card>
  );
}

三、作用域插槽(带数据的插槽)

方法1:使用 render props

javascript 复制代码
// 子组件:提供数据给插槽
function DataList({ data, children }) {
  return (
    <div className="list">
      {data.map((item, index) => (
        // 调用 children 函数,传递数据
        <div key={item.id}>
          {children(item, index)}
        </div>
      ))}
    </div>
  );
}

// 父组件使用
function App() {
  const users = [
    { id: 1, name: '张三', age: 25 },
    { id: 2, name: '李四', age: 30 }
  ];
  
  return (
    <DataList data={users}>
      {(user, index) => (  // 这里接收子组件传递的数据
        <div className="user-item">
          <span>{index + 1}. </span>
          <strong>{user.name}</strong>
          <span> ({user.age}岁)</span>
        </div>
      )}
    </DataList>
  );
}

方法2:使用函数作为 children

javascript 复制代码
// 子组件
function Toggle({ children }) {
  const [isOn, setIsOn] = useState(false);
  
  const toggle = () => setIsOn(!isOn);
  
  return children({ 
    isOn, 
    toggle,
    onText: '开启',
    offText: '关闭'
  });
}

// 父组件
function App() {
  return (
    <Toggle>
      {({ isOn, toggle, onText, offText }) => (
        <button onClick={toggle}>
          {isOn ? onText : offText}
        </button>
      )}
    </Toggle>
  );
}

四、组合组件模式(类似 Web Components 的 <slot>

方法1:使用特殊的子组件

javascript 复制代码
// 定义插槽组件
const CardHeader = ({ children }) => children;
const CardBody = ({ children }) => children;
const CardFooter = ({ children }) => children;

// 容器组件
function Card({ children }) {
  // 从 children 中提取不同插槽的内容
  let header, body, footer;
  
  React.Children.forEach(children, child => {
    if (child.type === CardHeader) {
      header = child.props.children;
    } else if (child.type === CardBody) {
      body = child.props.children;
    } else if (child.type === CardFooter) {
      footer = child.props.children;
    }
  });
  
  return (
    <div className="card">
      {header && <div className="card-header">{header}</div>}
      {body && <div className="card-body">{body}</div>}
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// 父组件使用(类似 Vue 的语法)
function App() {
  return (
    <Card>
      <CardHeader>
        <h2>用户信息</h2>
      </CardHeader>
      <CardBody>
        <p>姓名:张三</p>
        <p>年龄:25</p>
      </CardBody>
      <CardFooter>
        <button>编辑</button>
      </CardFooter>
    </Card>
  );
}

方法2:使用 context + 特殊组件

jsx

ini 复制代码
// 更高级的实现
const CardContext = React.createContext({});

const Card = ({ children }) => {
  const [slots, setSlots] = useState({});
  
  return (
    <CardContext.Provider value={{ registerSlot: setSlots }}>
      {children}
      <div className="card">
        <div className="card-header">{slots.header}</div>
        <div className="card-body">{slots.body}</div>
        <div className="card-footer">{slots.footer}</div>
      </div>
    </CardContext.Provider>
  );
};

const CardHeader = ({ children }) => {
  const { registerSlot } = useContext(CardContext);
  
  useEffect(() => {
    registerSlot(prev => ({ ...prev, header: children }));
  }, [children, registerSlot]);
  
  return null; // 不渲染自身
};

五、使用第三方库实现插槽

1. React Slot(专门库)

复制代码
npm install react-slot
javascript 复制代码
import { Slot, Fill } from 'react-slot';

function Layout() {
  return (
    <div>
      <header>
        <Slot name="header" />
      </header>
      <main>
        <Slot name="content" />
      </main>
    </div>
  );
}

function App() {
  return (
    <Layout>
      <Fill name="header">
        <h1>我的网站</h1>
      </Fill>
      <Fill name="content">
        <p>欢迎光临</p>
      </Fill>
    </Layout>
  );
}

六、实战案例:实现一个完整的弹窗组件

javascript 复制代码
// 弹窗组件(支持插槽)
function Modal({ 
  isOpen, 
  onClose, 
  title,
  children,
  footer 
}) {
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay">
      <div className="modal">
        {/* 标题插槽 */}
        <div className="modal-header">
          <h3>{title}</h3>
          <button onClick={onClose} className="close-btn">×</button>
        </div>
        
        {/* 默认插槽(主要内容) */}
        <div className="modal-body">
          {children}
        </div>
        
        {/* 底部插槽 */}
        <div className="modal-footer">
          {footer || (  // 默认底部
            <button onClick={onClose}>关闭</button>
          )}
        </div>
      </div>
    </div>
  );
}

// 使用示例
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowModal(true)}>打开弹窗</button>
      
      <Modal
        isOpen={showModal}
        onClose={() => setShowModal(false)}
        title="用户设置"
        footer={  // 具名插槽
          <>
            <button onClick={() => setShowModal(false)}>取消</button>
            <button onClick={() => alert('保存成功')}>保存</button>
          </>
        }
      >
        {/* 默认插槽内容 */}
        <form>
          <label>
            用户名:
            <input type="text" />
          </label>
          <label>
            邮箱:
            <input type="email" />
          </label>
        </form>
      </Modal>
    </div>
  );
}

七、高级模式:动态插槽

动态决定插槽位置

javascript 复制代码
function DynamicLayout({ children, layout = 'default' }) {
  // 根据布局类型动态渲染插槽
  const layouts = {
    default: (
      <div className="default-layout">
        <div className="top">{children.top}</div>
        <div className="main">{children.main}</div>
      </div>
    ),
    sidebar: (
      <div className="sidebar-layout">
        <aside>{children.sidebar}</aside>
        <main>{children.main}</main>
      </div>
    ),
    grid: (
      <div className="grid-layout">
        {children.grid?.map((item, index) => (
          <div key={index} className="grid-item">{item}</div>
        ))}
      </div>
    )
  };
  
  return layouts[layout];
}

// 使用
<DynamicLayout layout="sidebar">
  {{
    sidebar: <nav>导航菜单</nav>,
    main: <article>主要内容</article>
  }}
</DynamicLayout>

八、与 Vue 插槽的对比

Vue 插槽 React 对应实现 代码示例
<slot /> {children} <div>{children}</div>
<slot name="header"> 多个 props header={content}
作用域插槽 render props {(data) => <div>{data}</div>}
<template #header> 组件组合 <Card><CardHeader>...</CardHeader></Card>

九、最佳实践建议

何时使用哪种方式?

  1. 简单内容传递children prop
  2. 多个固定区域 → 多个 props(命名插槽)
  3. 需要向父组件传递数据 → render props
  4. 类似 Vue 的具名插槽语法 → 组件组合模式
  5. 复杂插槽逻辑 → Context + 特殊组件

性能考虑

javascript 复制代码
// ❌ 避免每次渲染都创建新的插槽内容
<Card>
  {{
    header: <Header />,  // 每次都是新组件
    body: <Body />
  }}
</Card>

// ✅ 使用 useMemo 或提取到组件外部
const cardContent = useMemo(() => ({
  header: <Header />,
  body: <Body />
}), [dependencies]);

<Card>{cardContent}</Card>

reaact 中的性能优化有很多需要注意的地方,欢迎大家说说平时遇到的情况

相关推荐
xhxxx1 小时前
一个空函数,如何成就 JS 继承的“完美方案”?
javascript·面试·ecmascript 6
韩曙亮1 小时前
【Web APIs】元素可视区 client 系列属性 ② ( 立即执行函数 )
前端·javascript·dom·client·web apis·立即执行函数·元素可视区
秋邱1 小时前
AR 技术创新与商业化新方向:AI+AR 融合,抢占 2025 高潜力赛道
前端·人工智能·后端·python·html·restful
www_stdio1 小时前
JavaScript 原型继承与函数调用机制详解
前端·javascript·面试
羽沢311 小时前
vue3 + element-plus 表单校验
前端·javascript·vue.js
前端九哥1 小时前
如何让AI设计出Apple风格的顶级UI?
前端·人工智能
红石榴花生油1 小时前
Linux服务器权限与安全核心笔记
java·linux·前端
只与明月听1 小时前
一个有趣的面试题
前端·后端·python
红色乌鸦2 小时前
vue3+ts 中使用pinia状态管理
前端