React 插槽(Slot)

文章目录

概要

React 无原生 标签,但通过 props.children、命名 props、函数 props(Render Props) 三大方案,可实现默认、具名、作用域三类插槽,满足组件内容分发需求。

一、默认插槽(匿名插槽)

React 内置机制,接收组件标签内所有内容,对应 Vue 默认 。

子组件(Card.tsx)
javascript 复制代码
import React from 'react'
import { View, Text } from '@tarojs/components'

interface CardProps {
  children: React.ReactNode
}

const Card: React.FC<CardProps> = ({ children }) => {
  return (
    <View className="card">
      <View className="card-header">默认标题</View>
      <View className="card-body">
        {/* 渲染父组件传入的所有内容 */}
        {children}
      </View>
    </View>
  )
}

export default Card
父组件使用
javascript 复制代码
import Card from './Card'

const Parent = () => {
  return (
    <Card>
      <Text>这是默认插槽内容</Text>
      <View>可嵌套任意组件</View>
    </Card>
  )
}

二、具名插槽(多区域分发)

方案 1:命名 props 传递(推荐)

通过自定义 props 传递 JSX,实现多区域精准分发。

子组件(Layout.tsx)
javascript 复制代码
import React from 'react'
import { View } from '@tarojs/components'

interface LayoutProps {
  header: React.ReactNode
  main: React.ReactNode
  footer: React.ReactNode
}

const Layout: React.FC<LayoutProps> = ({ header, main, footer }) => {
  return (
    <View className="layout">
      <View className="layout-header">{header}</View>
      <View className="layout-main">{main}</View>
      <View className="layout-footer">{footer}</View>
    </View>
  )
}

export default Layout
父组件使用
javascript 复制代码
import Layout from './Layout'

const Parent = () => {
  return (
    <Layout
      header={<Text>页面头部</Text>}
      main={<Text>主体内容</Text>}
      footer={<Text>版权信息</Text>}
    />
  )
}
方案 2:children 对象模式(贴近 Vue 语法)
javascript 复制代码
// 子组件 Modal.tsx
const Modal: React.FC<{ children: { title?: React.ReactNode; content?: React.ReactNode } }> = ({ children }) => {
  const { title, content } = children
  return (
    <View className="modal">
      <View className="modal-title">{title}</View>
      <View className="modal-content">{content}</View>
    </View>
  )
}

// 父组件使用
<Modal>
  {{
    title: <Text>弹窗标题</Text>,
    content: <Text>弹窗内容</Text>
  }}
</Modal>

三、作用域插槽(子传数据给父)

核心:函数 props(Render Props)

子组件提供数据,父组件基于数据自定义渲染,对应 Vue 作用域插槽。

子组件(List.tsx)
javascript 复制代码
import React from 'react'
import { View } from '@tarojs/components'

interface ListProps<T> {
  data: T[]
  renderItem: (item: T) => React.ReactNode
}

const List = <T extends { id: number; name: string }>(props: ListProps<T>) => {
  const { data, renderItem } = props
  return (
    <View className="list">
      {data.map(item => (
        <View key={item.id} className="list-item">
          {/* 子组件将数据回传给父组件渲染 */}
          {renderItem(item)}
        </View>
      ))}
    </View>
  )
}

export default List
父组件使用
javascript 复制代码
import List from './List'

const Parent = () => {
  const listData = [
    { id: 1, name: 'React' },
    { id: 2, name: 'Taro' }
  ]
  return (
    <List
      data={listData}
      renderItem={item => (
        <Text>{item.id} - {item.name}</Text>
      )}
    />
  )
}

四、三种方案对比

五、实战:Taro 小程序通用弹窗(含插槽)

javascript 复制代码
// components/Modal/index.tsx
import React, { useState } from 'react'
import { View, Text, Button } from '@tarojs/components'
import './index.less'

interface ModalProps {
  visible: boolean
  onClose: () => void
  title: React.ReactNode
  children: React.ReactNode
  footer?: React.ReactNode
}

const Modal: React.FC<ModalProps> = ({ visible, onClose, title, children, footer }) => {
  if (!visible) return null

  return (
    <View className="modal-mask">
      <View className="modal-content">
        <View className="modal-header">
          <Text className="modal-title">{title}</Text>
          <Button className="modal-close" onClick={onClose}>×</Button>
        </View>
        <View className="modal-body">
          {/* 默认插槽 */}
          {children}
        </View>
        {footer && (
          <View className="modal-footer">
            {/* 具名插槽:底部按钮区 */}
            {footer}
          </View>
        )}
      </View>
    </View>
  )
}

export default Modal

使用示例

javascript 复制代码
import Modal from '@/components/Modal'

const Page = () => {
  const [modalVisible, setModalVisible] = useState(false)

  return (
    <View>
      <Button onClick={() => setModalVisible(true)}>打开弹窗</Button>
      <Modal
        visible={modalVisible}
        onClose={() => setModalVisible(false)}
        title={<Text>登录提示</Text>}
        footer={
          <View>
            <Button onClick={() => setModalVisible(false)}>取消</Button>
            <Button onClick={() => console.log('确认')}>确认</Button>
          </View>
        }
      >
        {/* 默认插槽:弹窗内容 */}
        <Text>请先完成登录操作</Text>
      </Modal>
    </View>
  )
}
相关推荐
梨子同志5 分钟前
React
前端
万少13 分钟前
22 点后,我靠这个 AI 工具成了"夜间天才程序员"
前端·后端
狂师29 分钟前
比 Playwright 更给力,推荐一个AI Agent的浏览器自动化开源项目!
前端·开源·测试
IT_陈寒38 分钟前
React hooks 闭包陷阱把我的状态吃掉了,原来问题出在这里
前端·人工智能·后端
壹方秘境40 分钟前
使用ApiCatcher在 iOS 上像修改 hosts 一样自定义域名解析
前端·后端·客户端
柳杉1 小时前
可视化大屏设计器脚手架:从设计到交付的一站式方案
前端·three.js·数据可视化
铁皮饭盒1 小时前
3行代码搞定页面截图,Bun.WebView真的简单
javascript
kyriewen14 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒15 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
山河木马15 小时前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学