写了三年React,你是不是还在用最原始的方式堆代码?一个组件几百行,state满天飞,props层层传递像俄罗斯套娃?
残酷真相:大部分React开发者都停留在"能跑就行"的阶段。但真正的高手,早就在用架构模式武装自己的代码。
今天,我要拆解9种React架构模式的核心秘密。这不是什么新概念炒冷饭,而是经过无数大厂项目验证的设计哲学。掌握它们,你的组件将从"勉强能用"进化到"无懈可击"。
很多人以为这就是简单的"业务逻辑分离",错了!这是一种职责边界的哲学思考。
// ❌ 99%的人写的"分离"(伪分离)
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(data => {
setUser(data);
setLoading(false);
});
}, []);
// 业务逻辑和UI逻辑混在一起
return (
<div className="profile-wrapper">
{loading ? <div>加载中...</div> : <div>{user?.name}</div>}
</div>
);
}
// ✅ 真正的容器-展示分离(威力巨大)
function UserContainer() {
const { data, isLoading, error } = useQuery('user', fetchUser);
return (
<UserProfile
user={data}
loading={isLoading}
error={error}
/>
);
}
function UserProfile({ user, loading, error }) {
if (loading) return<ProfileSkeleton />;
if (error) return<ErrorBoundary error={error} />;
return (
<div className="profile-card">
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
核心洞察:这不是代码分离,而是思维模式的分离。容器思考"做什么",展示思考"怎么展示"。
网上铺天盖地的文章都在说"受控组件性能差",但真相是:大部分人根本不知道什么时候该用哪种。
// 🤔 什么时候用非受控?(很多人搞错了)
function QuickForm() {
const nameRef = useRef();
const emailRef = useRef();
const handleSubmit = () => {
const formData = {
name: nameRef.current.value,
email: emailRef.current.value
};
// 一次性提交,中间不需要验证
submitForm(formData);
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="" />
<input ref={emailRef} defaultValue="" />
<button type="submit">提交</button>
</form>
);
}
// 💪 什么时候用受控?(威力场景)
function SmartForm() {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const handleChange = (field, value) => {
setValues(prev => ({ ...prev, [field]: value }));
// 实时验证,实时反馈
if (field === 'email' && !isValidEmail(value)) {
setErrors(prev => ({ ...prev, email: '邮箱格式不正确' }));
}
};
return (
<Form>
<Input
value={values.email}
onChange={(v) => handleChange('email', v)}
error={errors.email}
/>
<SubmitButton disabled={Object.keys(errors).length > 0} />
</Form>
);
}
非受控组件适合:表单提交、文件上传、一次性数据收集受控组件适合:实时验证、条件渲染、复杂交互
关键不在于性能,而在于控制粒度的选择。
很多人以为复合组件就是"组件嵌套",实际上这是一种声明式API的设计哲学。
// 🔥 复合组件的威力展示
function Tabs({ defaultValue, children }) {
const [activeTab, setActiveTab] = useState(defaultValue);
const contextValue = {
activeTab,
setActiveTab
};
return (
<TabsContext.Provider value={contextValue}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return<div className="tabs-list">{children}</div>;
};
Tabs.Trigger = function TabsTrigger({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
className={`tab-trigger ${activeTab === value ? 'active' : ''}`}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
};
Tabs.Panel = function TabsPanel({ value, children }) {
const { activeTab } = useContext(TabsContext);
return activeTab === value ? <div className="tab-panel">{children}</div> : null;
};
// 使用时的优雅程度:
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">概览</Tabs.Trigger>
<Tabs.Trigger value="analytics">数据分析</Tabs.Trigger>
<Tabs.Trigger value="reports">报告</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="overview">
<Overview />
</Tabs.Panel>
<Tabs.Panel value="analytics">
<Analytics />
</Tabs.Panel>
</Tabs>
争议观点:有人认为复合组件过于复杂,但我认为复杂度是内聚的,对使用者来说反而更简单。
答案:绝对有!而且在某些场景下比Hooks更优雅。
// 🤯 Render Props在复杂场景下的威力
function DataFetcher({ url, children }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData(url)
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
// 把控制权完全交给用户
return children({ data, loading, error, refetch: () => fetchData(url) });
}
// 使用场景:用户完全控制渲染逻辑
<DataFetcher url="/api/users">
{({ data, loading, error, refetch }) => (
<div>
{loading && <Spinner />}
{error && (
<ErrorPanel
message={error.message}
onRetry={refetch}
/>
)}
{data && <UserList users={data} />}
</div>
)}
</DataFetcher>
场景 | Render Props | Hooks |
|---|---|---|
逻辑复用 | ✅ 完美 | ✅ 更简洁 |
渲染控制 | 🔥 完全控制 | ❌ 受限 |
嵌套地狱 | ⚠️ 可能产生 | ✅ 避免 |
类组件兼容 | ✅ 完美 | ❌ 不支持 |
核心观点:Hooks解决了90%的问题,但Render Props解决的那10%往往是最复杂的。
传统UI库的痛点:样式固化、定制困难。无头组件彻底解决了这个问题。
// 🎯 无头组件的核心思想
function useDropdown() {
const [isOpen, setIsOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const dropdownProps = {
'aria-expanded': isOpen,
'aria-haspopup': 'listbox',
onClick: () => setIsOpen(!isOpen)
};
const menuProps = {
role: 'listbox',
'aria-hidden': !isOpen
};
return {
isOpen,
selectedItem,
setSelectedItem,
toggleOpen: () => setIsOpen(!isOpen),
dropdownProps,
menuProps
};
}
// 使用者完全控制UI
function MyDropdown() {
const { isOpen, dropdownProps, menuProps, toggleOpen } = useDropdown();
return (
<div className="my-custom-dropdown">
<button {...dropdownProps} className="my-trigger">
选择选项 {isOpen ? '▲' : '▼'}
</button>
{isOpen && (
<ul {...menuProps} className="my-menu">
<li onClick={() => console.log('选项1')}>选项1</li>
<li onClick={() => console.log('选项2')}>选项2</li>
</ul>
)}
</div>
);
}
争议思考:无头组件是否增加了使用复杂度?我的观点是:复杂度从消费端转移到了生产端,这是正确的方向。
很多人以为这只是useReducer的高级用法,实际上这是可扩展性设计的体现。
// 🧠 状态缩减器模式的深度应用
const defaultReducer = (state, action) => {
switch (action.type) {
case'TOGGLE':
return { ...state, isOn: !state.isOn };
case'SET_ON':
return { ...state, isOn: true };
case'SET_OFF':
return { ...state, isOn: false };
default:
return state;
}
};
function useAdvancedToggle({
initialState = { isOn: false },
reducer = defaultReducer
} = {}) {
const [state, dispatch] = useReducer(reducer, initialState);
const actions = {
toggle: () => dispatch({ type: 'TOGGLE' }),
setOn: () => dispatch({ type: 'SET_ON' }),
setOff: () => dispatch({ type: 'SET_OFF' })
};
return [state, actions, dispatch];
}
// 🔥 用户可以注入自定义逻辑
const customReducer = (state, action) => {
switch (action.type) {
case'TOGGLE':
// 自定义:切换时记录时间戳
return {
...state,
isOn: !state.isOn,
lastToggled: Date.now()
};
default:
return defaultReducer(state, action);
}
};
function SmartToggle() {
const [state, actions] = useAdvancedToggle({
reducer: customReducer,
initialState: { isOn: false, lastToggled: null }
});
return (
<div>
<button onClick={actions.toggle}>
{state.isOn ? 'ON' : 'OFF'}
</button>
{state.lastToggled && (
<p>上次切换:{new Date(state.lastToggled).toLocaleString()}</p>
)}
</div>
);
}
设计系统中的复杂组件:表格、表单、编辑器等都需要这种可扩展的状态管理。
用户可以在不修改源码的情况下,注入自定义行为逻辑。这是开放-封闭原则在React中的完美体现。
很多人认为这种分离增加了代码复杂度,但我认为这是可维护性和复杂度的完美平衡。
// 🎯 智能组件:处理复杂的业务逻辑
function TodoListContainer() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const [loading, setLoading] = useState(true);
// 复杂的数据处理逻辑
const filteredTodos = useMemo(() => {
switch (filter) {
case'completed':
return todos.filter(todo => todo.completed);
case'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}, [todos, filter]);
const handleToggleTodo = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []);
const handleDeleteTodo = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
useEffect(() => {
fetchTodos().then(data => {
setTodos(data);
setLoading(false);
});
}, []);
return (
<TodoListView
todos={filteredTodos}
filter={filter}
loading={loading}
onToggleTodo={handleToggleTodo}
onDeleteTodo={handleDeleteTodo}
onFilterChange={setFilter}
/>
);
}
// 🎨 愚蠢组件:纯UI呈现
function TodoListView({
todos,
filter,
loading,
onToggleTodo,
onDeleteTodo,
onFilterChange
}) {
if (loading) {
return<TodoListSkeleton />;
}
return (
<div className="todo-app">
<FilterTabs
activeFilter={filter}
onFilterChange={onFilterChange}
/>
<TodoList
todos={todos}
onToggle={onToggleTodo}
onDelete={onDeleteTodo}
/>
</div>
);
}
核心洞察:这不是代码分离,而是关注点分离的哲学体现。
很多团队患有"抽象强迫症",一行相似代码都要提取成Hook。这是错误的!
// ❌ 过度抽象的典型例子
// hooks/useFormState.js
exportfunction useFormState(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// ...50行通用逻辑
}
// components/LoginForm.js
function LoginForm() {
const { values, errors, handleChange, handleSubmit } = useFormState({
email: '',
password: ''
});
// 使用通用Hook,但只用了20%的功能
}
// ✅ 就近原则的正确做法
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const handleEmailChange = (value) => {
setEmail(value);
if (!isValidEmail(value)) {
setEmailError('邮箱格式不正确');
} else {
setEmailError('');
}
};
const handleSubmit = () => {
if (email && password && !emailError) {
login({ email, password });
}
};
return (
<form onSubmit={handleSubmit}>
<Input
value={email}
onChange={handleEmailChange}
error={emailError}
/>
<Input
type="password"
value={password}
onChange={setPassword}
/>
<Button type="submit">登录</Button>
</form>
);
}
不要为了复用而复用,先让代码在当前上下文中完美工作,等到真正需要复用时再抽象。
过度抽象的危害:
金句:好的代码不是没有重复,而是没有不必要的抽象。
这不是技术问题,而是API设计哲学的问题。
// 🎯 配置型API:适合标准化场景
<Card
variant="elevated" // 配置外观
size="large" // 配置尺寸
showBorder={true} // 配置行为
onHover="highlight" // 配置交互
>
卡片内容
</Card>
// 🎨 组合型API:适合灵活场景
<Card>
<Card.Header>
<Card.Title>用户设置</Card.Title>
<Card.Actions>
<IconButton icon="close" />
</Card.Actions>
</Card.Header>
<Card.Body>
<SettingsForm />
</Card.Body>
<Card.Footer>
<Button variant="primary">保存</Button>
<Button variant="secondary">取消</Button>
</Card.Footer>
</Card>
使用配置型API当:
使用组合型API当:
核心洞察:最好的组件库会在这两种风格之间找到平衡,为不同场景提供不同的API风格。
这9种模式不是独立的技巧,而是软件设计原则在React中的体现:
90%的React开发者都停留在"能用"阶段,只有掌握了这些架构模式,你才能写出"工业级"的React代码。
但请记住:模式是工具,不是教条。不要为了用模式而用模式,要为了解决问题而用模式。
下一次写组件时,问自己三个问题:
如果答案不理想,回到这9种模式中找答案。
记住:优秀的React开发者不是写代码最快的,而是写出最容易维护代码的。架构模式就是你的武器库,用好它们,你的组件将从"能跑就行"进化到"无懈可击"。