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>
  )
}
相关推荐
方安乐2 小时前
react之通用表格组件最佳实践(TSX)
javascript·react.js·ecmascript
Z_Wonderful2 小时前
React 中基于 Axios 的二次封装(含请求守卫)
javascript·react.js·ecmascript
早點睡3902 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-shimmer-placeholder
javascript·react native·react.js
哈__2 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-splash-screen
javascript·react native·react.js
前端老石人2 小时前
HTML 内容分组终极指南:从语义化标签到现代 Web 结构
前端·html
大转转FE2 小时前
转转前端周刊第191期: 淘宝闪购 AI Agent 的秒级响应记忆系统
前端·人工智能
浮游本尊2 小时前
React 18.x 学习计划 - 第十五天:GraphQL 与实时应用实战
学习·react.js·graphql
怪我冷i2 小时前
在win11进行Rust Web 开发,采用Salvo框架
开发语言·前端·rust
candyTong2 小时前
Claude Code 是怎么跑起来的:从 Agent Loop 理解代理循环实现
前端·后端·ai编程