React-props - 详解
文章目录
- 前言
- **React 中更高级的 props 设计模式**,
- 高级 Props 模式:Render Props、asChild、Slot 模式、函数式 children、Context 替代
前言
在 React 中,props是组件之间通信的核心机制之一。虽然props看起来简单(像参数一样传入组件),但它实际上有许多关键细节和机制,掌握这些对于写出高效、稳定、易维护的 React 组件非常重要。
✅ 一、什么是 props?
props(全称 properties)是组件的“属性”,用于从父组件向子组件传递数据。
function Hello({ name } ) { return <h1>Hello, { name }!< /h1> } // 利用: <Hello name="Tom" /> ✅ 二、props 的特点
| 特性 | 说明 |
|---|---|
| 只读 | 子组件不能直接修改 props |
| ⛓️ 单向数据流 | 数据只能从父组件流向子组件 |
| 每次 render 都会重新传入 | props 是受控的,与状态更新同步变化 |
| 支持任意类型 | 可以传入字符串、数字、对象、函数、JSX、组件 |
✅ 三、props 的核心细节 & 常见问题
1. 引用?就是props 是新对象还
- 每次父组件 render,子组件都会收到新的
props对象引用(即使值不变)。 - 所以你应该使用
React.memo来优化子组件避免不必要的重渲染。
constMemoChild= React.memo(({ data } ) => { ... } ) ⚠️ 注意:传入的是对象或函数时,
===变了就会触发子组件 render。
2. 函数作为 props:闭包陷阱
<Child onClick={ ( ) => doSomething( ) } /> 这种写法会在每次父组件渲染时创建新的函数,导致子组件无法 memo 优化。解除技巧:
constmemoized= useCallback(( ) => doSomething( ) , [] ) <Child onClick={memoized} /> 3. 默认值 & 解构默认值
function Greeting({ name = "Guest" } ) { return <h1>Hello, { name }< /h1> } 或者使用默认 props(对于类组件):
MyComponent.defaultProps= { name: 'Guest' } ⚠️ 对于函数组件推荐解构默认值。
4. props.children 是什么?
props.children 是组件标签内嵌套的内容:
<Panel> <p>This is child< /p> < /Panel> function Panel({children} ) { return <div> {children}< /div> }
children可以是字符串、JSX、数组、函数等。
5. props + JSX 组合:可传组件、函数、Slot
<MyButton renderIcon={ ( ) => <Icon /> } /> 允许传递函数或组件作为 props,实现类似插槽能力。
四、最佳实践
| 场景 | 建议 |
|---|---|
| 子组件依赖复杂 props 变化 | 用 React.memo + useCallback 优化 |
| 多个 props 逻辑交叉 | 抽象成对象 props:如config, options |
| 子组件行为繁琐 | 用 props 传入回调而不是在组件内部硬编码 |
| 渲染内容灵活 | 使用 props.children 或 render prop 模式 |
✅ 五、TS 中定义 props
type MyProps = { name: stringonClick?: ( ) => voidchildren?: React.ReactNode} constMyComponent: React.FC<MyProps> = ({ name,onClick,children} ) => ( <div> { name }< /div> ) ✅ 总结
| 点 | 说明 |
|---|---|
| 只读 | 组件内部不能修改 props |
| 单向 | 从父 → 子流动,反向用回调 |
| 优化 | 使用 memo、useCallback 避免重复渲染 |
| TS 类型 | 用接口定义 props,增强代码可维护性 |
| children | 支持插槽、嵌套等灵活布局 |
如果你想了解:
props如何与context和state配合?- 如何实现动态 Slot(类似 Vue 的具名插槽)?
- 或如何设计组件库的高级 props 模式(如 render props、asChild 模式)?
React 中更高级的 props 设计模式,
尤其适用于你在封装可复用组件库时的实践需求,包括:
高级 Props 模式:Render Props、asChild、Slot 模式、函数式 children、Context 替代
✅ 一、Render Props 模式
通过 props 传入一个函数,让子组件决定如何渲染内容。
示例:状态控制组件
constToggle= ({children}: { children: (on: boolean , toggle: ( ) => void ) => React.ReactNode} ) => { const [on, setOn] = useState(false ) const toggle = ( ) => setOn(!on) return < > { children(on,toggle) }< /> } // 运用方式 <Toggle> { (on,toggle) => ( <button onClick={toggle}> { on ? 'ON' : 'OFF' }< /button> ) } < /Toggle> ✅ 优点:
- 父组件控制状态
- 子组件控制渲染方式(灵活解耦)
✅ 二、asChild模式(shadcn/ui 的经典模式)
允许你将组件的行为注入到任意 HTML 元素或组件上。
示例:Button 接收asChild决定渲染为哪个标签
<Button asChild> <Link href="/about">Go to About< /Link> < /Button> Button 实现:
import { Slot } from "@radix-ui/react-slot" const Button = ({asChild= false ,children, ...props } ) => { const Comp =asChild? Slot : 'button' return <Comp { ...props }className="btn"> {children}< /Comp> } ✅ 优点:
- 完美兼容无障碍结构
<a>/<Link> - 支持组合式组件
✅ 三、函数式 children 模式(Function-as-Children)
比 render prop更自然(常用于列表渲染、状态暴露):
<MyList> { (item) => <div key={ item.id }> { item.name }< /div> } < /MyList> 类似于 Vue 中的具名插槽默认插槽:
<template #default="slotProps">...</template> ✅ 四、Slot 模式(具名插槽模拟)
React 没有官方 Slot,但可以模拟:
type PanelProps = {header: React.ReactNode footer: React.ReactNode children: React.ReactNode} const Panel = ({header,footer,children}:PanelProps) => ( <div className="panel"> <div className="header"> {header}< /div> <div className="body"> {children}< /div> <div className="footer"> {footer}< /div> < /div> ) // 使用: <Panel header={ <h2>Title< /h2> }footer={ <button>Submit< /button> } > <p>Content< /p> < /Panel> ✅ 五、Context 替代 Deep Props Drilling
当 props 应该一层一层传下去时,可用 Context 替代
constThemeContext= createContext("light" ) function App( ) { return ( <ThemeContext.Provider value="dark"> <Layout/> < /ThemeContext.Provider> ) } function Layout( ) { return <Header/> } function Header( ) { const theme = useContext(ThemeContext) return <h1 className={ theme }>Theme Header< /h1> } 实战建议(封装组件库时)
| 目标 | 推荐做法 |
|---|---|
| 自定义渲染内容 | 使用 render prop 或 children 是函数 |
| 保留标签结构灵活性 | 使用 asChild+ Slot |
| 复杂嵌套内容 | 用具名插槽模式(多个props 渲染区域) |
| 深度通信 | 用 React Context 给予状态 |
| 封装 + 组合 | 使用 Compound Components模式(Header.Body.Footer) |
✅ 总结
| 模式 | 用途 | 类比 Vue |
|---|---|---|
render prop | 内容渲染完全交给父组件 | <slot :data="..."> |
asChild | 自定义渲染元素 | <component :is="..."> |
props.children | 默认内容插槽 | <slot /> |
| Context | 深层组件通信 | provide/inject |
| Slot 模拟 | 多区域结构控制 | 具名 <slot name="..."> |
下面是一个完整封装的Modal(模态框)组件示例,结合了:
- ✅
asChild模式(帮助自定义触发按钮) - ✅ Slot 模式(承受具名插槽:Header、Body、Footer)
- ✅
shadcn/ui+ Radix UI 实现可访问性、动画、组合组件设计
安装依赖(如未安装)
npx shadcn-ui@latestadddialog button 组件结构目录建议:
components/ ui/ modal.tsx ← 模态框主组件封装(含 asChild + slot 模式) features/ demo-modal.tsx ← 示例使用:组合 Header/Body/Footer + 触发按钮 ✅ 1. components/ui/modal.tsx
import * as React from "react" import {Dialog,DialogTrigger,DialogContent,DialogHeader,DialogTitle,DialogDescription,DialogFooter,DialogClose, } from "@/components/ui/dialog" import { Slot } from "@radix-ui/react-slot" interface ModalProps {trigger?: React.ReactNode title?: React.ReactNode description?: React.ReactNode footer?: React.ReactNode children: React.ReactNode asChild?: boolean } export function Modal({trigger, title,description,footer,children,asChild= false , }:ModalProps) { return ( <Dialog> {trigger&& ( <DialogTrigger asChild={asChild}> {trigger} < /DialogTrigger> ) } <DialogContent> { (title ||description) && ( <DialogHeader> { title && <DialogTitle> { title }< /DialogTitle> } {description&& <DialogDescription> {description}< /DialogDescription> } < /DialogHeader> ) } <div className="py-2"> {children}< /div> {footer&& ( <DialogFooter> {footer} < /DialogFooter> ) } < /DialogContent> < /Dialog> ) } ✅ 2. 示例用法features/demo-modal.tsx
import { Modal } from "@/components/ui/modal" import {Button} from "@/components/ui/button" export default function DemoModal( ) { return ( <Modal trigger={ <Button>打开模态框< /Button> } title="模态框标题"description=模态框描述"就是"这footer={ < > <DialogClose asChild> <Button variant="outline">取消< /Button> < /DialogClose> <Button>确认< /Button> < /> } > <p>你可能在这里放任何自定义内容,如表单、提示、嵌套组件等。< /p> < /Modal> ) } ✅ 3. 使用asChild自定义触发器样式
<Modal asChild trigger={ <a className="underline cursor-pointer">点我打开< /a> } ... > ✅ 总结功能支持
| 功能 | 实现方式 |
|---|---|
| 具名插槽 Header/Body/Footer | 使用 title、description、children、footer props |
| 自定义触发按钮 | trigger + asChild |
| 支持嵌套组件内容 | children支持 JSX |
| 可访问性与焦点管理 | 使用 shadcn/uiDialog 组件封装 |
后面可以扩展这个模态框支持:
- ✅ 异步加载内容(Skeleton)
- ✅ 表单提交 loading 状态
- ✅ 动态打开多个 Modal(如嵌套或全局注册)
- ✅ 在服务端渲染中与 RSC 配合
浙公网安备 33010602011771号