在React项目开发中,我们经常会遇到功能相似的组件需要在多个地方使用的情况,比如公共的按钮、表单输入框、卡片容器等。如果每次需要都重新编写组件代码,不仅会增加工作量,还会导致代码冗余,后续维护也会变得困难。因此掌握组件的重复使用与独立定制方法,是React开发者的必备技能。

一、通过props实现基础复用与定制
props是React组件通信的基础方式,也是实现组件复用与定制最直接的方法。我们可以把组件中需要变化的部分通过props传入,在组件内部根据传入的props渲染不同的内容或样式。
比如我们需要一个通用的按钮组件,不同场景下按钮的文本、颜色、点击事件都不一样,就可以通过props来定制:
// 通用按钮组件
function CommonButton(props) {
const {
text = '默认按钮',
type = 'primary',
onClick,
disabled = false
} = props;
// 根据type设置不同的样式
const btnStyle = {
primary: {
backgroundColor: '#1677ff',
color: '#fff',
border: 'none',
padding: '8px 16px',
borderRadius: '4px',
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1
},
danger: {
backgroundColor: '#ff4d4f',
color: '#fff',
border: 'none',
padding: '8px 16px',
borderRadius: '4px',
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1
}
};
return (
<button
style={btnStyle[type] || btnStyle.primary}
onClick={onClick}
disabled={disabled}
>
{text}
</button>
);
}
// 使用组件,不同场景定制不同属性
function App() {
return (
<div>
<CommonButton text="提交" type="primary" onClick={() => alert('提交成功')} />
<CommonButton text="删除" type="danger" disabled />
<CommonButton text="默认按钮" />
</div>
);
}这种方式的优点是简单直观,适合功能单一、定制项较少的组件,但是当组件逻辑复杂、定制项较多时,props会变得越来越臃肿,维护成本也会升高。
二、通过组件组合实现灵活复用
当组件的结构比较复杂,不同场景需要组合不同的子部分时,可以使用组件组合的方式。React的children属性或者自定义插槽props,都可以让我们在复用组件基础结构的同时,灵活定制内部内容。
比如我们需要一个通用的卡片组件,基础结构包含标题和内容区域,不同场景下内容区域可以放不同的子组件:
// 通用卡片组件
function CommonCard(props) {
const { title, children, footer } = props;
return (
<div style={{
border: '1px solid #e8e8e8',
borderRadius: '8px',
padding: '16px',
width: '300px'
}}>
<div style={{
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '12px',
borderBottom: '1px solid #e8e8e8',
paddingBottom: '8px'
}}>
{title}
</div>
<div style={{ marginBottom: '12px' }}>
{children}
</div>
{footer && <div style={{ borderTop: '1px solid #e8e8e8', paddingTop: '8px' }}>{footer}</div>}
</div>
);
}
// 使用组件,组合不同的内容
function App() {
return (
<div style={{ display: 'flex', gap: '16px' }}>
{/* 卡片内容放文本 */}
<CommonCard title="用户信息">
<p>用户名:张三</p>
<p>年龄:25</p>
</CommonCard>
{/* 卡片内容放表单 */}
<CommonCard
title="登录卡片"
footer={<CommonButton text="登录" type="primary" />}
>
<div>
<div style={{ marginBottom: '8px' }}>
<label>账号:</label>
<input type="text" style={{ width: '100%', padding: '4px' }} />
</div>
<div>
<label>密码:</label>
<input type="password" style={{ width: '100%', padding: '4px' }} />
</div>
</div>
</CommonCard>
</div>
);
}三、通过高阶组件实现逻辑复用
高阶组件(HOC)是一个函数,接收一个组件作为参数,返回一个新的增强后的组件,适合用来复用组件的逻辑部分,比如数据请求、权限校验、日志上报等通用逻辑。
比如我们需要给多个组件添加数据请求的能力,就可以封装一个高阶组件:
// 高阶组件:给组件添加数据请求能力
function withDataFetching(WrappedComponent, fetchUrl) {
return function EnhancedComponent(props) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
fetch(fetchUrl)
.then(res => res.json())
.then(result => {
setData(result);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [fetchUrl]);
// 把请求到的数据、加载状态、错误状态传给被包装的组件
return (
<WrappedComponent
{...props}
data={data}
loading={loading}
error={error}
/>
);
};
}
// 普通用户列表组件
function UserList(props) {
const { data, loading, error } = props;
if (loading) return <div>加载中...</div>;
if (error) return <div>请求失败</div>;
return (
<ul>
{data && data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 普通文章列表组件
function ArticleList(props) {
const { data, loading, error } = props;
if (loading) return <div>加载中...</div>;
if (error) return <div>请求失败</div>;
return (
<div>
{data && data.map(article => (
<div key={article.id} style={{ marginBottom: '8px' }}>
<h4>{article.title}</h4>
<p>{article.desc}</p>
</div>
))}
</div>
);
}
// 使用高阶组件包装,复用数据请求逻辑
const FetchUserList = withDataFetching(UserList, 'https://ipipp.com/api/users');
const FetchArticleList = withDataFetching(ArticleList, 'https://ipipp.com/api/articles');
function App() {
return (
<div>
<h2>用户列表</h2>
<FetchUserList />
<h2>文章列表</h2>
<FetchArticleList />
</div>
);
}高阶组件的优点是可以复用逻辑,不会侵入原组件的代码,但是过度使用可能会导致组件层级过深,调试起来不太方便。
四、通过自定义Hook实现逻辑复用
自定义Hook是React 16.8之后推出的逻辑复用方式,可以提取组件的状态逻辑和副作用逻辑,比高阶组件更轻量,也不会增加组件层级。
上面的数据请求逻辑也可以用自定义Hook来实现:
// 自定义Hook:封装数据请求逻辑
function useFetchData(fetchUrl) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
fetch(fetchUrl)
.then(res => res.json())
.then(result => {
setData(result);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [fetchUrl]);
return { data, loading, error };
}
// 用户列表组件,使用自定义Hook获取逻辑
function UserList() {
const { data, loading, error } = useFetchData('https://ipipp.com/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>请求失败</div>;
return (
<ul>
{data && data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 文章列表组件,使用自定义Hook获取逻辑
function ArticleList() {
const { data, loading, error } = useFetchData('https://ipipp.com/api/articles');
if (loading) return <div>加载中...</div>;
if (error) return <div>请求失败</div>;
return (
<div>
{data && data.map(article => (
<div key={article.id} style={{ marginBottom: '8px' }}>
<h4>{article.title}</h4>
<p>{article.desc}</p>
</div>
))}
</div>
);
}
function App() {
return (
<div>
<h2>用户列表</h2>
<UserList />
<h2>文章列表</h2>
<ArticleList />
</div>
);
}自定义Hook的逻辑复用粒度更细,使用起来更灵活,是目前React项目中逻辑复用的主流方式,适合大多数逻辑复用场景。
不同方案的选择建议
我们可以根据组件的实际情况选择合适的复用方案:
- 如果是简单的UI组件,定制项少,优先选择props传参的方式
- 如果组件结构固定,只需要定制内部内容,选择组件组合的方式
- 如果需要复用跨组件的逻辑,且不想增加组件层级,优先选择自定义Hook
- 如果是老项目,或者需要包装类组件的逻辑,可以选择高阶组件
在实际开发中,这些方案也可以结合使用,比如基础UI组件用props和组合实现,逻辑部分用自定义Hook提取,这样可以最大化提升代码的复用性和可维护性。