让你的组件既优雅又可复用!设计模式大揭秘
嘿,React 开发者们!你是否曾经:
- 写出一堆难以维护的"巨无霸"组件?
- 复制粘贴同样的代码到不同组件中?
- 为组件间数据共享和通信方式感到困惑?
今天,我们将一探 React 组件设计的精髓,让你的代码架构更合理,组件更易维护和复用!
1. 组件的基本分类:认识不同类型的组件
在设计 React 应用时,清晰地区分不同类型的组件至关重要:
展示型组件(Presentational Components)
// 纯展示组件:只关注UI渲染,不包含业务逻辑
function Button({ text, onClick, variant = "primary", size = "medium" }) {
return (
<button className={`btn btn-${variant} btn-${size}`} onClick={onClick}>
{text}
</button>
);
}
// 使用示例
<Button
text="提交表单"
variant="success"
size="large"
onClick={handleSubmit}
/>;
容器型组件(Container Components)
// 容器组件:负责数据获取和状态管理
function UserProfileContainer({ userId }) {
// 数据获取和状态管理
const {
data: user,
isLoading,
error,
} = useQuery({
queryKey: ["user", userId],
queryFn: () => fetchUserProfile(userId),
});
// 错误和加载状态处理
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
// 渲染展示组件
return <UserProfileView user={user} />;
}
// 纯展示组件
function UserProfileView({ user }) {
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p className="title">{user.title}</p>
<p className="bio">{user.bio}</p>
<div className="stats">
<Stat label="粉丝" value={user.followers} />
<Stat label="关注" value={user.following} />
<Stat label="文章" value={user.articles} />
</div>
</div>
);
}
2. 复合组件模式:灵活而强大的组件设计
复合组件模式允许你创建一组协同工作的组件,同时保持灵活的布局控制:
// 选项卡组件示例
function Tabs({ children, defaultTab, onChange }) {
const [activeTab, setActiveTab] = useState(defaultTab);
// 创建上下文
const TabContext = createContext();
const tabContext = useMemo(
() => ({
activeTab,
setActiveTab: (tab) => {
setActiveTab(tab);
onChange?.(tab);
},
}),
[activeTab, onChange]
);
return (
<TabContext.Provider value={tabContext}>
<div className="tabs-container">{children}</div>
</TabContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ id, children }) {
const { activeTab, setActiveTab } = useContext(TabContext);
const isActive = activeTab === id;
return (
<button
className={`tab ${isActive ? "active" : ""}`}
onClick={() => setActiveTab(id)}
>
{children}
</button>
);
}
function TabPanel({ id, children }) {
const { activeTab } = useContext(TabContext);
if (activeTab !== id) return null;
return <div className="tab-panel">{children}</div>;
}
// 组合使用
function ProfileTabs() {
return (
<Tabs defaultTab="posts" onChange={console.log}>
<TabList>
<Tab id="posts">文章</Tab>
<Tab id="projects">项目</Tab>
<Tab id="about">关于</Tab>
</TabList>
<TabPanel id="posts">
<UserPosts />
</TabPanel>
<TabPanel id="projects">
<UserProjects />
</TabPanel>
<TabPanel id="about">
<UserAbout />
</TabPanel>
</Tabs>
);
}
3. 渲染属性模式(Render Props):灵活的组件复用技术
渲染属性模式允许组件间共享逻辑,同时让父组件控制如何渲染:
// 鼠标跟踪组件
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMouseMove(event) {
setPosition({
x: event.clientX,
y: event.clientY,
});
}
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
// 调用render函数,传入当前状态
return render(position);
}
// 使用示例1:自定义鼠标指针
function CustomCursor() {
return (
<MouseTracker
render={({ x, y }) => (
<div
style={{
position: "fixed",
left: x,
top: y,
width: "20px",
height: "20px",
borderRadius: "50%",
backgroundColor: "red",
transform: "translate(-50%, -50%)",
pointerEvents: "none",
zIndex: 9999,
}}
/>
)}
/>
);
}
// 使用示例2:坐标显示
function CoordinatesDisplay() {
return (
<MouseTracker
render={({ x, y }) => (
<div className="coordinates">
当前坐标: {x}, {y}
</div>
)}
/>
);
}
4. 自定义 Hook:逻辑复用的现代方案
自定义 Hook 是 React 中复用逻辑的最佳方式,比渲染属性和高阶组件更加简洁:
// 表单字段Hook
function useField(initialValue = "") {
const [value, setValue] = useState(initialValue);
const [touched, setTouched] = useState(false);
const [error, setError] = useState("");
const handleChange = useCallback((e) => {
setValue(e.target.value);
}, []);
const handleBlur = useCallback(() => {
setTouched(true);
}, []);
const validate = useCallback(
(validator) => {
const errorMessage = validator(value);
setError(errorMessage);
return !errorMessage;
},
[value]
);
const reset = useCallback(() => {
setValue(initialValue);
setTouched(false);
setError("");
}, [initialValue]);
return {
value,
touched,
error,
handleChange,
handleBlur,
validate,
reset,
props: {
// 方便直接传递给input
value,
onChange: handleChange,
onBlur: handleBlur,
},
};
}
// 使用示例
function SignupForm() {
const email = useField("");
const password = useField("");
const handleSubmit = (e) => {
e.preventDefault();
const isEmailValid = email.validate((value) => {
if (!value) return "邮箱不能为空";
if (!/\S+@\S+\.\S+/.test(value)) return "邮箱格式不正确";
return "";
});
const isPasswordValid = password.validate((value) => {
if (!value) return "密码不能为空";
if (value.length < 8) return "密码长度至少8位";
return "";
});
if (isEmailValid && isPasswordValid) {
// 提交表单
console.log("表单提交", { email: email.value, password: password.value });
// 重置表单
email.reset();
password.reset();
}
};
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>邮箱</label>
<input
type="email"
{...email.props}
className={email.touched && email.error ? "error" : ""}
/>
{email.touched && email.error && (
<div className="error-message">{email.error}</div>
)}
</div>
<div className="form-group">
<label>密码</label>
<input
type="password"
{...password.props}
className={password.touched && password.error ? "error" : ""}
/>
{password.touched && password.error && (
<div className="error-message">{password.error}</div>
)}
</div>
<button type="submit">注册</button>
</form>
);
}
下一篇预告:《【React 实战项目】从零构建企业级应用》
在下一篇文章中,我们将把所有学到的知识整合起来,带你从零开始构建一个完整的企业级应用:
- 项目结构和代码组织
- 身份认证与权限控制
- 数据获取与状态管理实战
- 组件库设计与样式解决方案
- 国际化与主题切换
准备好了吗?让我们一起成为真正的 React 专家!下期见!
关于作者
Hi,我是 hyy,一位热爱技术的全栈开发者:
- 🚀 专注 TypeScript 全栈开发,偏前端技术栈
- 💼 多元工作背景(跨国企业、技术外包、创业公司)
- 📝 掘金活跃技术作者
- 🎵 电子音乐爱好者
- 🎮 游戏玩家
- 💻 技术分享达人
加入我们
欢迎加入前端技术交流圈,与 10000+开发者一起:
- 探讨前端最新技术趋势
- 解决开发难题
- 分享职场经验
- 获取优质学习资源
添加方式:掘金摸鱼沸点 👈 扫码进群