共计 2301 个字符,预计需要花费 6 分钟才能阅读完成。
从 Props Drilling 困局说起
最近接手一个遗留项目时,发现这样的组件调用链:

<Page>
<Header user={user} />
<Content
user={user}
onLogout={() => {...}}
/>
<Footer
user={user}
links={[...]}
/>
</Page>
- 用户数据需要穿透 3 层无关组件
- 任何用户字段变更都会导致全树重渲染
- 单元测试需要构造完整 props 链
三大解决方案横向对比
方案 1:Context API
const UserContext = createContext<{
user: User;
onLogout: () => void;} | null>(null);
// 使用时需要层层校验空值
const {user} = useContext(UserContext)!; // 危险的非空断言
- 优点:React 原生支持
- 缺点:类型安全薄弱,provider 嵌套问题
方案 2:Redux Toolkit
// store/userSlice.ts
const userSlice = createSlice({initialState: { data: null as User | null},
reducers: {logout: (state) => {...}
}
});
- 优点:时间旅行调试
- 缺点:样板代码多,类型推导复杂
方案 3:设计模式组合(推荐)
复合组件模式实战
// UserProvider.tsx
type UserProviderProps = {
children: ReactNode;
initialUser: User;
};
const UserProvider = ({children, initialUser}: UserProviderProps) => {const [user, setUser] = useState(initialUser);
const contextValue = useMemo(() => ({
user,
logout: () => setUser(null!)
}), [user]);
return (<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
};
// 搭配 Hook 消费
const useUser = () => {const ctx = useContext(UserContext);
if (!ctx) throw new Error('Missing UserProvider');
return ctx;
};
自定义 Hook 进阶封装
// useFetchUser.ts
export function useFetchUser<T extends User>(userId: string) {const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const id = useId(); // React 18 新特性
useEffect(() => {const abortCtrl = new AbortController();
const fetchData = async () => {
try {const res = await fetch(`/api/users/${userId}`, {signal: abortCtrl.signal});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json() as T;
setData(json);
} catch (err) {if (!abortCtrl.signal.aborted) {setError(err as Error);
}
}
};
fetchData();
return () => abortCtrl.abort();
}, [userId]);
return {data, error, requestId: id};
}
安全防护要点
XSS 防护
// 错误示范
<div dangerouslySetInnerHTML={{__html: userBio}} />;
// 正确做法
<div>{escapeHtml(userBio)}</div>;
类型守卫
// api/types.ts
function isApiResponse<T>(obj: unknown): obj is ApiResponse<T> {
return (
obj != null &&
typeof obj === 'object' &&
'data' in obj &&
'success' in obj
);
}
// 使用时
if (isApiResponse<User[]>(res)) {// 类型安全访问 res.data}
生产环境检查清单
- 单元测试 :
- 核心业务逻辑覆盖率≥80%
-
自定义 Hook 必须测试 cleanup 效果
-
Storybook:
// .storybook/preview.js export const parameters = {controls: { expanded: true}, actions: {argTypesRegex: "^on.*"} }; -
性能埋点 :
// 使用 Web Vitals API import {getCLS, getFID, getLCP} from 'web-vitals'; [getCLS, getFID, getLCP].forEach(metric => {metric(console.log); });
思考题:复杂度与速度的平衡
在最近的项目中,我们采用了设计模式组合方案后:
– 新成员上手时间增加了 2 天
– 但需求变更响应速度提升了 40%
你的团队会如何选择?欢迎在评论区分享经验
正文完
