其他
构建下拉、标签页、菜单等需共享隐式状态的关联组件时…
当下拉、标签页、菜单等一组相关子组件需要共享内部状态、协调工作,又不想显式逐层传 props 时,用复合组件模式。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
构建下拉、标签页、菜单等需共享隐式状态的特定
解决思路
- 使用 React Context API 在父复合组件与子组件之间共享状态:先调用 `createContext()` 创建 `FlyOutContext`。
- 在父组件 `FlyOut` 中用 `useState(false)` 管理 `open`,并提供切换函数 `toggle`;返回 `<FlyOutContext.Provider value={{ open, toggle }}>{props.children}</FlyOutContext.Provider>`,把 `open` 和 `toggle` 传给所有子组件。
- 创建 `Toggle` 子组件:用 `useContext(FlyOutContext)` 读取 `open` 和 `toggle`,渲染可点击元素,并在 `onClick` 中调用 `toggle(!open)`(例如 `<div onClick={() => toggle(!open)}><Icon /></div>`)。
- 把 `Toggle` 作为 `FlyOut` 的静态属性挂载:`FlyOut.Toggle = Toggle`,这样使用方只需导入 `FlyOut` 即可访问子组件。
- 创建 `List` 子组件:用 `useContext(FlyOutContext)` 读取 `open`,根据 `open` 决定是否渲染子内容(例如 `return open && <ul>{children}</ul>`)。
- 创建 `Item` 子组件:渲染 `<li>{children}</li>`。
- 把 `List` 和 `Item` 作为 `FlyOut` 的静态属性挂载:`FlyOut.List = List`、`FlyOut.Item = Item`。
- 使用复合组件时,在 `FlyOut` 内组合 `FlyOut.Toggle`、`FlyOut.List` 和多个 `FlyOut.Item`,由父组件内部状态控制显示/隐藏,调用处无需维护状态;只需导入 `FlyOut`,无需单独导入子组件(例如 Edit、Delete 菜单项)。
- 如果不用 Context,可用 `React.Children.map` 遍历 `props.children`,并用 `React.cloneElement(child, { open, toggle })` 给每个子元素注入 `open` 和 `toggle`,子组件改为从 `props` 读取这些值。
- 在复杂场景中 memoize context value,避免 context 值变化导致子组件不必要的 re-render。
- 优先选择 Context 方案而不是 `React.Children.map`,以获得更灵活的组件嵌套能力。
适用边界 · 注意事项
- 子组件之间没有有意义共享状态时不要用,复合组件会引入不必要的 Context 开销。
- 简单一次性 UI 用带 props 的单一组件更清晰时不要用。
- 隐式状态共享会让组件行为对消费者难以预测时不要用;使用 `React.Children.map` 加 `React.cloneElement` 方案时,还要注意它只对直接子元素生效、深层包裹会失效,且浅合并可能覆盖同名 prop。