高阶组件
在 React 中, 逻辑复用 和 组件组合 是核心设计模式。高阶组件 ( HOC )、 Render Props 和组合模式是三种主流实现方式。
信息
随着 Hooks 的出现,许多 HOC 和 Render Props 的场景已被更简洁的 自定义 Hook 取代。
一、高阶组件(HOC,Higher-Order Component)
高阶组件是一个 函数 ,接收一个组件,返回一个新的增强的组件,用于复用逻辑(如权限校验、日志记录)。它通过 包装原组件 来注入额外逻辑或属性。
1. 携带日志功能的高级组件
携带日志功能的高级组件
function withLogging(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log(`组件 ${WrappedComponent.name} 已挂载`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
2. withAuth 实现(权限控制)
- 使用 React Context 管理全局认证状态(如用户信息)
- HOC 内部消费 AuthContext ,获取用户认证状态
- 根据认证状态决定渲染原组件或重定向
// 定义 Auth Context (用于全局状态管理)
const AuthContext = React.createContext<{
isAuthenticated: boolean;
loading: boolean;
}>({
isAuthenticated: false,
loading: true,
});
// 实现 withAuth HOC
function withAuth<P extends object>(WrappedComponent: React.ComponentType<P>) {
return function (props: P) {
// 从 AuthContext 获取认证状态
const { isAuthenticated, loading } = useContext(AuthContext);
// 加载后显式占位符(可选)
if (loading) {
return <div>loading</div>;
}
// 未认证时重定向至登录页
if (!isAuthenticated) {
// 假设使用 react-router-dom 的 Navigate
return <Navigate to="/login" replace />;
}
// 已认证的渲染原组件,并传递所有的 props
return <WrappedComponent {...props} />;
};
}
// 使用示例
const ProtectedPage = () => <div>这是受保护的内页</div>;
// 用 withAuth 包装组件,获得权限控制能力
export default withAuth(ProtectedPage);
二、渲染属性(Render Props)
Render Props 是一种 通过函数类型的 Props 控制渲染内容 的模式,实现逻辑复用。组件通过一个名为 render (或 children )的函数 Props ,将内部数据传递给外部,由外部决定如何渲染。
- 组件内部管理状态(如数据、加载状态)
- 通过
render函数(或children作为函数)暴露状态给外部 - 外部组件完全控制渲染逻辑
1. 用户示例
用户信息示例
interface User {
id: number;
name: string;
}
interface UserDataProps {
render: (user: User | null, loading: boolean) => React.ReactNode;
}
const UserData: Rect.FC<UserDataProps> = ({ render }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<true>;
// 模拟获取用户数据
useEffect(() => {
fetchUser().then(data => {
setUser(data);
setLoading(false);
});
}, []);
// 通过 render 函数将数据传递给外部
return <>render(user, loading)</>;
};
// 使用示例
const UserProfile = () => (
<UserData
render={(user, loading) => {
if (loading) return <div>加载用户信息中</div>;
return user ? <div>你好, {user.name}</div> : <div>当前用户不存在</div>;
}}
/>
);
2. 请求示例
简单的请求示例
class DataFetcher extends React.Component {
state = { data: null };
componentDidMount() {
fetch('http://api.lmssee.com/data')
.then(res => res.json())
.then(data => this.setState({ data }));
}
render() {
return this.props.render(this.state.data); // 通过 render 函数传递数据
}
}
// 使用
<DataFetcher
render={data => (data ? <div>{data.name}</div> : <p>加载中</p>)}
/>;
三、自定义 Hooks (Function Component 专属)
提取可复用的状态逻辑,避免重复代码(代替 HOC 和 Render Props)。它强调“组合优于继承”。
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
}
// 使用自定义 Hook
function MyComponent() {
const { data, loading } = useFetch('https://lmssee.com/data');
return loading ? <p>加载中。。。</p> : <div>{data.name}</div>;
}
四、 HOC 与 Render Props 得优缺点对比
| 维度 | 高阶组件 ( HOC ) | Render Props |
|---|---|---|
| 逻辑复用 | ✅ 独立于组件树,逻辑集中管理(如 withAuth 可复用于多个组件) | ✅ 逻辑与渲染解耦,通过函数 prop 灵活传递 |
| 组件树结构 | ❌ 可能导致嵌套地狱(多层 HOC 包装) | ❌ 可能因 render 函数嵌套问题导致 JSX 层级过深(但比 HOC 浅) |
| Props 冲突 | ❌ 可能覆盖原组件的 Props (需谨慎合并 props ) | ✅ 无 props 冲突(外部通过函数参数获取数据,不影响原组件 Props) |
| 调试难度 | ❌ 调试时组件层级复杂 ( React DevTools 显示多层包转组件) | ✅ 组件树更扁平( Render Props 通常在同一层级渲染) |
| 类型支持 | ⚠️ 需要泛型定义,增加配置复杂度 | ✅ 类型更佳直观(函数参数明确输入输出) |
| 学习成本 | ⚠️ 需理解 Context、 组件包装机制 | ✅ 符合 React 基础(函数 Prop ),学习成本低 |
| 使用场景 | 跨组件无渲染逻辑(如权限、路由、日志) | 需要细颗粒度控制渲染逻辑(如数据获取后自定义 UI) |