003-HOC与函数柯里化

深入解析高阶组件(HOC)与柯里化函数实现

一、高阶组件(HOC)详解

1. 核心概念

高阶组件(Higher-Order Component)是React中用于复用组件逻辑的高级技术,本质上是一个函数:

  • 输入:一个组件
  • 输出:一个新的增强组件
1
const EnhancedComponent = higherOrderComponent(WrappedComponent);

2. 核心特征

  • 非侵入性:不修改原组件,通过组合扩展功能
  • 逻辑复用:抽离通用逻辑(如鉴权、日志、数据获取)
  • 渲染劫持:控制被包裹组件的渲染行为
  • Props代理:操作传入组件的props

3. 常见HOC类型

类型 功能 示例
Props代理 增删改props 注入额外属性
继承反转 继承原组件控制渲染 条件渲染控制
状态抽象 管理公共状态 表单状态管理
样式增强 添加样式逻辑 withStyles

二、React中的实际HOC案例

1. Redux的connect

1
2
3
4
const ConnectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Component);
  • 功能:连接React组件与Redux store
  • 原理:通过Context注入store数据

2. React Router的withRouter

1
const ComponentWithRouter = withRouter(Component);
  • 功能:注入路由对象(history, location, match)
  • 使用场景:非路由组件访问路由信息

3. 自定义HOC示例:权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function withAuth(WrappedComponent) {
return class extends React.Component {
state = { hasPermission: false };

async componentDidMount() {
const user = await fetchUser();
this.setState({
hasPermission: user.role === 'admin'
});
}

render() {
return this.state.hasPermission
? <WrappedComponent {...this.props} />
: <div>无访问权限</div>;
}
}
}

// 使用
const AdminPanel = withAuth(PanelComponent);

三、函数柯里化实现:add(1)(2)(3)

1. 柯里化概念

柯里化(Currying)是把多参数函数转换为一系列单参数函数的技术:

1
2
3
4
5
// 普通函数
add(1, 2, 3);

// 柯里化函数
add(1)(2)(3)();

2. 实现方案对比

方案 特点 适用场景
递归存储 闭包保存中间值 基础实现
隐式转换 利用valueOf 简洁调用
参数收集 支持无限链式 最通用

3. 完整实现代码(支持无限链式调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function add(x) {
// 存储所有参数的数组
let sum = x;

// 定义内部函数
function innerAdd(y) {
if (y !== undefined) {
sum += y;
return innerAdd; // 返回自身支持链式调用
}
return sum; // 无参调用时返回结果
}

// 重写valueOf实现隐式转换
innerAdd.valueOf = () => sum;

// 重写toString便于调试
innerAdd.toString = () => `CurriedSum(${sum})`;

return innerAdd;
}

4. 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基础用法
console.log(add(1)(2)(3)()); // 6

// 隐式转换
console.log(+add(1)(2)(3)); // 6 (触发valueOf)
console.log(add(1)(2)(3) + 4); // 10

// 无限链式
const sum = add(1)(2)(3)(4)(5);
console.log(sum()); // 15

// 分段调用
const addTwo = add(1)(1);
console.log(addTwo(3)()); // 5
console.log(addTwo(5)()); // 7

5. 实现原理图解

1
2
3
4
5
6
7
8
9
10
11
graph TD
A[调用add(1)] --> B[创建闭包环境<br>sum=1]
B --> C[返回innerAdd函数]
C --> D[调用(2)]
D --> E[sum=1+2=3]
E --> F[返回innerAdd]
F --> G[调用(3)]
G --> H[sum=3+3=6]
H --> I[返回innerAdd]
I --> J[无参调用]
J --> K[返回sum=6]

四、HOC与柯里化的关联

1. 设计模式相似性

概念 HOC 柯里化
核心思想 组件增强 函数转换
输入输出 组件 → 组件 函数 → 函数
实现方式 闭包保存状态 闭包保存参数

2. React中的结合应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 柯里化风格HOC工厂
const withFeature = (featureName) => (WrappedComponent) => {
return class extends React.Component {
featureMethod() {
console.log(`执行${featureName}功能`);
}

render() {
return <WrappedComponent
{...this.props}
featureMethod={this.featureMethod}
/>;
}
}
}

// 使用
const withLogger = withFeature('日志记录');
const EnhancedComponent = withLogger(MyComponent);

五、HOC最佳实践

  1. 命名规范:使用with前缀(withAuth, withLogger)
  2. 传递无关props:确保原组件props透传
  3. 避免修改原组件:使用组合而非继承
  4. 正确处理ref:使用React.forwardRef
    1
    2
    3
    4
    5
    function withLogging(WrappedComponent) {
    return React.forwardRef((props, ref) => {
    return <WrappedComponent {...props} ref={ref} />;
    });
    }
  5. 组合多个HOC:使用compose函数
    1
    2
    3
    4
    5
    6
    const enhance = compose(
    withAuth,
    withLogger,
    withAnalytics
    );
    const EnhancedComponent = enhance(BaseComponent);

六、HOC的现代替代方案

方案 优势 适用场景
React Hooks 逻辑复用更简洁 函数组件
Render Props 灵活共享代码 组件间逻辑共享
Context API 跨层级数据传递 主题/用户信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用Hooks替代HOC示例
function useAuth() {
const [user, setUser] = useState(null);

useEffect(() => {
fetchUser().then(setUser);
}, []);

return {
isAdmin: user?.role === 'admin'
};
}

// 组件内使用
function AdminPanel() {
const { isAdmin } = useAuth();
return isAdmin ? <Panel /> : <DeniedView />;
}

总结

  1. HOC本质:组件工厂函数,实现逻辑复用
  2. 核心价值:解耦、复用、增强组件功能
  3. 柯里化实现:利用闭包保存中间状态,支持链式调用
  4. 现代演进:Hooks逐渐成为主流替代方案
  5. 适用场景
    • HOC:跨组件复用逻辑(如鉴权、埋点)
    • 柯里化:参数分步传递的场景(如配置初始化)

通过HOC和柯里化的组合使用,可以构建出高度灵活且可复用的React应用架构,同时保持代码的简洁性和可维护性。

004-函数式组件和Class组件

从多个维度对比函数式组件和Class组件的本质区别,包括设计思想、生命周期、状态管理、代码结构等。

设计思想:

  • Class组件:基于面向对象编程(OOP)思想,组件是类的实例,拥有实例属性(state、props)和方法(生命周期方法、自定义方法)。

  • 函数式组件:基于函数式编程(FP)思想,组件是一个纯函数,接收props作为输入,返回JSX作为输出。在引入Hooks之后,函数式组件也能够拥有状态和副作用处理能力。

    生命周期:

  • Class组件:具有明确的生命周期方法,如componentDidMountcomponentDidUpdatecomponentWillUnmount等。

  • 函数式组件:使用Hooks模拟生命周期。例如,useEffect可以处理副作用,通过不同的依赖数组模拟不同生命周期。

    状态管理:

  • Class组件:使用this.statethis.setState管理状态。setState可能是异步的,且状态更新是通过合并对象实现的。

  • 函数式组件:使用useStateuseReducer等Hook管理状态。状态更新是通过替换而不是合并(但也可以实现合并逻辑)。每次渲染都有独立的状态(通过闭包实现),但Hooks会保证在每次渲染中状态的一致性。

    代码结构:

  • Class组件:需要更多的模板代码(如constructor中绑定this、类方法等)。

  • 函数式组件:代码更简洁,没有this绑定问题,逻辑复用更灵活(通过自定义Hook)。

    性能优化:

  • Class组件:通过shouldComponentUpdatePureComponent进行优化。

  • 函数式组件:通过React.memo包裹组件进行浅比较,避免不必要的渲染。在Hooks中,可以使用useMemouseCallback来缓存值和函数。

    逻辑复用:

  • Class组件:使用高阶组件(HOC)或渲染属性(Render Props)实现逻辑复用。

  • 函数式组件:使用自定义Hook实现逻辑复用,更自然且没有嵌套地狱。

    心智模型:

  • Class组件:需要理解OOP的概念,如实例化、继承、生命周期方法。同时,需要处理this的指向问题。

  • 函数式组件:更关注数据流和副作用控制,利用闭包和不可变性。Hooks要求遵守规则(如只在顶层使用Hooks)。

    未来趋势:

  • React团队推荐使用函数式组件,因为Hooks提供了更强大的能力,并且函数式组件更符合React的理念(UI作为状态的函数)。

    错误边界(Error Boundaries):

  • 目前,只有Class组件可以定义为错误边界(通过static getDerivedStateFromErrorcomponentDidCatch)。

  • 函数式组件尚不能直接作为错误边界,但可以通过包裹Class组件实现。

    实例引用:

  • Class组件:可以通过createRef或回调Ref获取组件实例,从而调用实例方法。

  • 函数式组件:不能直接使用ref获取组件实例(因为没有实例),但可以通过forwardRefuseImperativeHandle暴露特定方法。

    副作用处理:

  • Class组件:副作用通常放在生命周期方法中,如数据获取在componentDidMountcomponentDidUpdate中。

  • 函数式组件:使用useEffect处理副作用,将相关的副作用组织在一起(而不是分散在不同的生命周期中)。

    状态更新:

  • Class组件:setState是合并更新,且是异步的(在React事件处理中),但可以通过回调函数获取更新后的状态。

  • 函数式组件:状态更新函数(如setState)是替换更新(对于对象类型,需要手动合并),也是异步的。但可以通过useEffect来响应状态变化。

    代码可读性:

  • 函数式组件通常更简洁,逻辑更集中(相关逻辑可以放在同一个useEffect中,而不是分散在多个生命周期方法中)。

    测试:

  • 函数式组件更容易测试,因为它们是纯函数(输入props,输出JSX)。自定义Hook也更容易独立测试。

    学习曲线:

  • Class组件需要理解OOP和this,而函数式组件需要理解闭包、副作用管理和Hooks的规则。

    内部状态:

  • Class组件:状态存储在实例上,在整个生命周期中持续存在。

  • 函数式组件:状态存储在函数闭包中,每次渲染都有独立的状态值,但React通过Hook队列保证状态的一致性。

    设计模式:

  • Class组件:可以使用继承(但React推荐组合优于继承)。

  • 函数式组件:完全基于组合(通过Hooks组合逻辑)。

    代码拆分:

  • 函数式组件可以更自然地使用动态导入(React.lazy)进行代码拆分。

    上下文(Context):

  • 两者都可以使用Context,但函数式组件使用useContext更简洁。

    渲染优化:

  • Class组件:PureComponent自动实现shouldComponentUpdate进行浅比较。

  • 函数式组件:React.memo是一个高阶组件,对props进行浅比较。

总结:函数式组件是React的未来,它通过Hooks提供了更强大、更灵活的功能,同时减少了模板代码,提高了代码的可读性和可维护性。但在某些特定场景(如错误边界)下,Class组件仍然是必要的。

函数式组件 vs Class 组件:本质区别深度解析

React 组件开发的两种范式有着根本性的区别,理解这些差异对于编写高效、可维护的代码至关重要。以下是两者的全面对比:

核心设计哲学差异

维度 函数式组件 Class 组件
编程范式 函数式编程 (FP) 面向对象编程 (OOP)
核心概念 纯函数、不可变性 类、实例、继承
状态处理 闭包 + Hook 状态 实例属性 (this.state)
组件本质 数据 → UI 的转换函数 具有生命周期状态机
思维模型 “UI 作为数据的函数” “组件作为有状态对象”

技术实现差异

1. 状态管理机制

函数式组件:

1
2
3
4
5
6
7
function Counter() {
// 使用闭包管理状态
const [count, setCount] = useState(0);

// 每次渲染都是独立闭包
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Class 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Counter extends React.Component {
constructor(props) {
super(props);
// 状态存储在实例属性中
this.state = { count: 0 };
}

// 使用类方法更新状态
increment = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return <button onClick={this.increment}>{this.state.count}</button>;
}
}

关键区别:

  • 函数组件每次渲染都有独立的闭包作用域
  • Class 组件状态存储在持久化的实例属性
  • 函数组件状态更新是替换式,Class 组件是合并式

2. 生命周期管理

函数式组件:

1
2
3
4
5
6
7
8
9
useEffect(() => {
// componentDidMount + componentDidUpdate
fetchData();

return () => {
// componentWillUnmount
abortController.abort();
};
}, [dependencies]); // 依赖项控制执行时机

Class 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount() {
fetchData();
}

componentDidUpdate(prevProps) {
if (this.props.id !== prevProps.id) {
refetchData();
}
}

componentWillUnmount() {
abortController.abort();
}

关键区别:

  • 函数组件使用 useEffect 统一管理副作用
  • Class 组件有明确的生命周期方法
  • 函数组件通过依赖数组精细控制更新逻辑

3. 性能优化方式

函数式组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const MemoizedComponent = React.memo(
function MyComponent(props) {
/* 仅在 props 改变时重新渲染 */
},
(prevProps, nextProps) => {
/* 自定义比较函数 */
}
);

function Parent() {
const callback = useCallback(() => {
// 记忆化函数
}, [deps]);

return <Child onClick={callback} />;
}

Class 组件:

1
2
3
4
5
6
7
8
class MyComponent extends React.PureComponent {
// 自动浅比较 props 和 state

shouldComponentUpdate(nextProps, nextState) {
// 手动控制更新逻辑
return this.props.id !== nextProps.id;
}
}

关键区别:

  • 函数组件使用 React.memo + useCallback/useMemo
  • Class 组件使用 PureComponent + shouldComponentUpdate
  • 函数组件的优化更细粒度但需手动管理依赖

4. 逻辑复用机制

函数式组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 自定义 Hook
function useFetch(url) {
const [data, setData] = useState(null);

useEffect(() => {
fetch(url).then(r => r.json()).then(setData);
}, [url]);

return data;
}

// 多个组件复用相同逻辑
function UserProfile() {
const user = useFetch('/api/user');
// ...
}

Class 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 高阶组件模式
const withFetch = (url) => (WrappedComponent) => {
return class extends React.Component {
state = { data: null };

componentDidMount() {
fetch(url).then(r => r.json()).then(data =>
this.setState({ data }));
}

render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}

// 使用高阶组件
class UserProfile extends React.Component {
render() {
const { data } = this.props;
// ...
}
}

export default withFetch('/api/user')(UserProfile);

关键区别:

  • 函数组件使用 自定义 Hook 实现逻辑复用
  • Class 组件使用 高阶组件(HOC)渲染属性(Render Props)
  • 自定义 Hook 避免组件嵌套,提供更自然的组合方式

组件实例差异

特性 函数式组件 Class 组件
实例存在性 无实例概念 有组件实例 (this)
ref 处理 需要 useRef + forwardRef 直接使用 this 访问
方法绑定 无需绑定 需要绑定 this (箭头函数/构造函数绑定)
内部方法 函数内定义 类方法 (可继承)
错误边界 不支持 (需包裹类组件) 支持 (componentDidCatch)

渲染行为差异

1. 闭包陷阱 vs 最新值

函数式组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
const id = setInterval(() => {
// 闭包捕获初始 count 值
setCount(count + 1); // 始终为 1
}, 1000);

return () => clearInterval(id);
}, []); // 空依赖数组
// 正确方式: setCount(c => c + 1)
}

Class 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Timer extends React.Component {
state = { count: 0 };

componentDidMount() {
this.interval = setInterval(() => {
// 总是访问最新 state
this.setState({ count: this.state.count + 1 });
}, 1000);
}

componentWillUnmount() {
clearInterval(this.interval);
}
}

2. Props 变化响应

函数式组件:

1
2
3
4
5
6
function UserProfile({ userId }) {
// 每次 userId 变化都会触发重新获取
const user = useFetch(`/api/users/${userId}`);

// ...
}

Class 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UserProfile extends React.Component {
componentDidMount() {
this.fetchUser(this.props.userId);
}

componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser(this.props.userId);
}
}

fetchUser(id) {
// ...
}
}

未来发展与最佳实践

趋势与建议

  1. React 官方推荐函数式组件:Hooks 已成为 React 的未来
  2. 渐进迁移策略
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 混合使用示例
    class LegacyComponent extends React.Component {
    // ...
    }

    function WrapperComponent() {
    const [state, setState] = useState();
    return <LegacyComponent prop={state} />;
    }
  3. 性能考量
    • 函数组件:更小的打包体积,更快的初始渲染
    • Class 组件:生命周期方法优化复杂更新
  4. 并发模式兼容性:函数组件+ Hooks 更好地支持 Suspense 和并发特性

何时选择 Class 组件

  1. 需要实现错误边界 (componentDidCatch)
  2. 遗留代码库维护
  3. 需要精确控制生命周期的复杂场景
  4. 依赖 getSnapshotBeforeUpdate 等特定生命周期

总结:核心区别矩阵

特性 函数式组件 Class 组件
状态管理 useState + 闭包 this.state + setState
生命周期 useEffect 统一模型 独立生命周期方法
代码组织 逻辑关注点分离 (Hooks) 按生命周期阶段组织
性能优化 React.memo + Hook 依赖 PureComponent + SCU
逻辑复用 自定义 Hooks (组合式) HOC/Render Props (嵌套式)
学习曲线 需要理解闭包和函数式概念 需要理解 OOP 和 this 绑定
未来兼容性 完全支持并发模式 部分生命周期在并发模式受限
组件实例 无实例 (需 useRef) 有实例 (this)
代码简洁性 更简洁 (无模板代码) 更多样板代码
错误边界 不支持 (需包装) 原生支持

最终建议:对于新项目,优先使用函数式组件 + Hooks。对于现有 Class 组件,除非有特定需求,否则逐步迁移到函数式组件。理解两者的本质差异有助于在不同场景做出最佳选择。