关于组件封装
关于组件封装 会考虑什么
1.不要将 数据请求 + 列表渲染 + 分页 + 筛选 全部写在一个组件里
应拆分成:
UserList
、Pagination
、FilterBar
、useUserData
(自定义 Hook)
2.通过 props
传入配置、数据、回调
5.避免将样式写死在组件内部,应该支持外部传入 className、style,或者使用 CSS-in-JS / CSS Modules 等方案。
对于复杂的逻辑或行为,可以通过 Hooks、Render Props、高阶组件等方式提供扩展能力,而不是把所有逻辑都写死。HOC(高阶组件):包装组件,注入额外功能(如权限、日志
多个项目共用同一个组件,不同地方希望这个组件在某些局部展现上略有不同,如何实现这种“差异化需求
✅ 二、解决方案:使用 “插槽(Slot)思想” 实现差异化定制
✅ 在 Vue 中:有原生的 <slot>
标签,非常直观
<!-- BaseCard.vue -->
<template><div class="card"><header v-if="$slots.header"><slot name="header"></slot></header><main><slot></slot> <!-- 默认插槽 --></main><footer v-if="$slots.footer"><slot name="footer"></slot></footer></div>
</template>
使用者可以灵活传入不同内容到不同插槽。
✅ 但在 React 中:没有内置的 <slot>
标签!
不过,React 提供了两种非常灵活的方式,可以实现类似“插槽”的功能:
✅ 三、React 中实现“插槽”功能的两种主流方式
✅ 方式 1:使用 children
属性(最常见,用于默认内容区插槽)
就像 Vue 的默认
<slot></slot>
,你把可变部分通过 组件的 children 传进去。
🔧 适用场景: 当差异部分是组件的 “主体内容”,比如 Card 的内容、Modal 的正文、Form 的字段区等。
✅ 示例:通用 Card 组件,通过 children
定制内容
// BaseCard.jsx
function BaseCard({ children, title }) {return (<div style={{ border: '1px solid #ccc', padding: 16, borderRadius: 8, margin: 16 }}><h3>{title}</h3>{/* 类似 Vue 的默认插槽 --><div>{children}</div></div>);
}
✅ 使用:
<BaseCard title="用户信息"><p>这里是可定制的内容,每个项目可以不一样!</p><button>自定义按钮</button>
</BaseCard>
🔥 优势: 简单直观,适用于大部分“内容区差异化”需求。
✅ 方式 2:通过 props 传入 React 组件 / 元素(更灵活的“具名插槽”)
如果你想实现 “多个可变区域”(比如头部、内容、底部都可能不同),可以通过 props 传入 React 组件 / JSX 元素,模拟 Vue 的 具名插槽(name="xxx")。
🔧 适用场景: 当你的组件有多个可定制区块,比如:
✅ 示例:通用 Card 组件,支持传入 header / footer(类似具名插槽)
// BaseCard.jsx
function BaseCard({ title, children, header, footer }) {return (<div style={{ border: '1px solid #ccc', padding: 16, borderRadius: 8, margin: 16 }}>{header && <div style={{ fontWeight: 'bold', marginBottom: 12 }}>{header}</div>}<h3>{title}</h3><div>{children}</div>{footer && <div style={{ marginTop: 12, fontStyle: 'italic' }}>{footer}</div>}</div>);
}
✅ 使用:
<BaseCardtitle="用户详情"header={<div>🔥 这是自定义头部,可以是任意 React 组件</div>}footer={<button>操作按钮</button>}
><p>这是卡片的主要内容,每个项目可以不一样。</p>
</BaseCard>
🔥 优势:
✅ 四、进阶:结合 children + props,实现更强大的插槽机制
在实际项目中,我们经常 混合使用 children 和 props 传参,来支持:
✅ 更完整的示例:支持 header、footer、children 的通用容器
function AppContainer({ header, footer, children }) {return (<div style={{ border: '2px solid #ddd', borderRadius: 10, padding: 20, margin: 20 }}>{header && <div style={{ borderBottom: '1px solid #aaa', paddingBottom: 10 }}>{header}</div>}<main>{children}</main>{footer && <div style={{ borderTop: '1px solid #aaa', paddingTop: 10 }}>{footer}</div>}</div>);
}
✅ 使用:
<AppContainerheader={<h2>🎉 欢迎使用通用容器</h2>}footer={<button onClick={() => alert('保存!')}>保存</button>}
><p>这里是主要内容,可以是任何 React 组件或 JSX!</p><ul><li>支持高度定制</li><li>每个项目可以传不同的 header / footer</li></ul>
</AppContainer>
✅ 五、总结:React 中如何实现类似插槽(Slot)的差异化功能?
方法 | 说明 | 适用场景 | 类比 Vue |
---|---|---|---|
| 通过组件的 children 传入内容,相当于 Vue 的默认 | 主内容区定制 |
|
通过 props 传入 JSX / 组件(如 header、footer) | 将可变部分通过 props(如 | 头部、底部、操作区等差异化区块 |
|
混合使用 children + props | children 作为主内容,props 传入多个定制区块 | 复杂通用组件(如卡片、弹窗、表单容器) | 多个 |
✅ 六、一句话总结:
在 React 中虽然没有内置的
<slot>
标签,但可以通过children
属性实现类似默认插槽的功能,也可以通过 props 传入 JSX 元素或组件(如 header、footer)来模拟具名插槽,从而让同一个组件在不同项目中灵活定制部分 UI,实现高度复用与差异化需求的完美平衡。
✅ 附加建议:
通过回调函数(如
onChange
、onSubmit
)与父组件通信Prop 设计要合理、类型安全(TypeScript 更佳)
避免暴露过多内部状态
提供合理的默认值(defaultProps / default props)
3.通过插槽,让使用者可以自定义组件的“部分内容”,而不是全部写死在组件内部。
🔧 React: 通过
children
或 render props4.通过 props 控制组件的行为、样式、状态,而不是写死在组件内部
使用 TypeScript 定义清晰的 Prop 接口
提供合理的默认值
对复杂配置可使用 对象形式的 Prop(如 options、config)
6.
头部标题样式 / 内容区布局不同
按钮位置 / 图标不同
自定义 Footer 内容
某一块区域希望使用者自己控制渲染逻辑
header
区域footer
区域actions
按钮区extra
附加内容区
更灵活,可以传入任意 React 组件 / 元素
可以模拟多个具名插槽(header、footer、actions、extra 等)
保持组件接口清晰、可扩展
children → 默认内容区(主内容)
props 如 header / footer / actions / toolbar → 特定区块的定制
推荐使用 TypeScript 为这些插槽 props 定义清晰的类型,比如:
interface AppContainerProps {header?: React.ReactNode;footer?: React.ReactNode;children: React.ReactNode; }
如果你的组件逻辑复杂,还可以将可变部分抽离为 独立的 Render Props 或 Hooks,提供更强的扩展能力