复合组件与 Render Props
约 553 字大约 2 分钟
布欧-Lewyon
2026-05-15
复合组件
复合组件(Compound Component)让组件以声明式方式组合使用。
// Select + Select.Option 模式
import { createContext, useContext, useState } from 'react'
interface SelectContextType {
value: string
onChange: (value: string) => void
open: boolean
setOpen: (open: boolean) => void
}
const SelectContext = createContext<SelectContextType | null>(null)
// 主组件
function Select({ children, value, onChange }: {
children: React.ReactNode
value: string
onChange: (v: string) => void
}) {
const [open, setOpen] = useState(false)
return (
<SelectContext.Provider value={{ value, onChange, open, setOpen }}>
<div className="select-wrapper">
{children}
</div>
</SelectContext.Provider>
)
}
// 触发器
function Trigger({ placeholder }: { placeholder?: string }) {
const ctx = useContext(SelectContext)!
return (
<button className="select-trigger" onClick={() => ctx.setOpen(!ctx.open)}>
{ctx.value || placeholder || '请选择'}
</button>
)
}
// 下拉菜单
function Options({ children }: { children: React.ReactNode }) {
const ctx = useContext(SelectContext)!
if (!ctx.open) return null
return <div className="select-options">{children}</div>
}
// 选项
function Option({ value, children }: { value: string; children: React.ReactNode }) {
const ctx = useContext(SelectContext)!
return (
<div
className={`select-option ${ctx.value === value ? 'active' : ''}`}
onClick={() => { ctx.onChange(value); ctx.setOpen(false) }}
>
{children}
</div>
)
}
// 导出命名空间
Select.Trigger = Trigger
Select.Options = Options
Select.Option = Option
// 使用
function App() {
const [city, setCity] = useState('')
return (
<Select value={city} onChange={setCity}>
<Select.Trigger placeholder="选择城市" />
<Select.Options>
<Select.Option value="beijing">北京</Select.Option>
<Select.Option value="shanghai">上海</Select.Option>
</Select.Options>
</Select>
)
}Render Props
以函数作为 children 传递渲染控制权。
// 鼠标跟踪组件
function MouseTracker({ children }: {
children: (state: { x: number; y: number }) => React.ReactNode
}) {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handler = (e: MouseEvent) => setPosition({ x: e.clientX, y: e.clientY })
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
return <>{children(position)}</>
}
// 使用
function App() {
return (
<MouseTracker>
{({ x, y }) => (
<div>
<p>鼠标位置: {x}, {y}</p>
</div>
)}
</MouseTracker>
)
}受控与非受控
// 受控组件
function ControlledInput({ value, onChange }: {
value: string
onChange: (v: string) => void
}) {
return <input value={value} onChange={e => onChange(e.target.value)} />
}
// 非受控组件
function UncontrolledInput({ defaultValue = '' }: { defaultValue?: string }) {
const [value, setValue] = useState(defaultValue)
return <input value={value} onChange={e => setValue(e.target.value)} />
}
// 混合:同时支持受控和非受控
function Input({ value: externalValue, onChange, defaultValue }: {
value?: string
onChange?: (v: string) => void
defaultValue?: string
}) {
const [internalValue, setInternalValue] = useState(defaultValue || '')
const isControlled = value !== undefined
const displayValue = isControlled ? value : internalValue
return (
<input
value={displayValue}
onChange={e => {
const v = e.target.value
if (isControlled) onChange?.(v)
else setInternalValue(v)
}}
/>
)
}小结
- 复合组件:Context + 子组件组成声明式 API(Ant Design Select、Modal)。
- Render Props:函数作为 children 传递渲染内容(数据获取、状态逻辑)。
- 受控组件由父组件控制状态,非受控组件自己管理。
- 现代 React 中自定义 Hook 逐渐替代 Render Props 模式。
上一节:Axios 与 SWR 下一节:HOC 与错误边界
