0%

React原理-组件&Context

组件

函数组件与类组件

对比维度 函数组件 类组件
写法 简洁 冗长(需要构造函数、this 绑定等)
状态管理 useState this.statethis.setState
生命周期 useEffect 等 Hook 组件生命周期函数如 componentDidMount
this 使用 不使用 必须绑定
代码复用 自定义 Hook 简单灵活 高阶组件和 render props 较繁琐
推荐程度 ✅ 推荐用于新项目 ⚠️ 支持,但不推荐新开发使用

函数组件

在 React 函数组件中,并没有类组件那样的生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount),但我们可以使用 Hooks(主要是 useEffect 来模拟这些生命周期行为。


🧠 类组件生命周期 与 函数组件 Hook 的对照表

类组件生命周期 函数组件中的写法(Hook)
componentDidMount useEffect(() => { ... }, [])
componentDidUpdate useEffect(() => { ... }, [依赖项])
componentWillUnmount useEffect(() => { return () => { ... }; }, [])
所有更新后触发(类似 render 后) useEffect(() => { ... })(无第二参数)

🎯 总结

目的 useEffect 写法
初始化时执行一次 useEffect(() => { ... }, [])
某值变化时执行 useEffect(() => { ... }, [依赖项])
卸载前执行清理函数 useEffect(() => { return () => { ... } }, [])
每次渲染都执行 useEffect(() => { ... })

✅ 示例:如何用 useEffect 模拟生命周期

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
26
27
28
29
import React, { useEffect, useState } from 'react';

function LifeCycleDemo() {
const [count, setCount] = useState(0);

// ✅ 模拟 componentDidMount
useEffect(() => {
console.log('组件挂载完成');
}, []);

// ✅ 模拟 componentDidUpdate(监听 count)
useEffect(() => {
console.log('count 更新为:', count);
}, [count]);

// ✅ 模拟 componentWillUnmount
useEffect(() => {
return () => {
console.log('组件即将卸载');
};
}, []);

return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

📌 多个 useEffect 可以组合使用

React 支持一个组件中使用多个 useEffect 来分别处理不同的逻辑,这比类组件中将所有逻辑写在一个生命周期函数里更清晰:

1
2
3
4
5
6
7
useEffect(() => {
// 处理计时器
}, []);

useEffect(() => {
// 监听窗口大小变化
}, []);

分别实现一个简单的计数器

✅ 一、函数组件版(使用 Hook)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState } from 'react';

function CounterFunction() {
const [count, setCount] = useState(0);

return (
<div>
<h2>函数组件计数器</h2>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}

export default CounterFunction;

✅ 二、类组件版

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
26
27
28
29
30
31
32
33
34
35
36
import React, { Component } from 'react';

class CounterClass extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}

handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};

handleDecrement = () => {
this.setState({ count: this.state.count - 1 });
};

handleReset = () => {
this.setState({ count: 0 });
};

render() {
return (
<div>
<h2>类组件计数器</h2>
<p>当前计数:{this.state.count}</p>
<button onClick={this.handleIncrement}>增加</button>
<button onClick={this.handleDecrement}>减少</button>
<button onClick={this.handleReset}>重置</button>
</div>
);
}
}

export default CounterClass;

如何实现组件的清理

组件清理是指在组件卸载或副作用更新之前,取消定时器、事件监听、网络请求、订阅等操作,避免内存泄漏和不必要的副作用。


✅ 一、函数组件中的清理(使用 useEffect

通过在 useEffect 中返回一个清理函数来完成:

📌 示例:清理定时器

1
2
3
4
5
6
7
useEffect(() => {
const timer = setInterval(() => {
console.log('运行中...');
}, 1000);

return () => clearInterval(timer); // 清理
}, []);

📌 示例:清理事件监听器

1
2
3
4
5
6
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);

return () => window.removeEventListener('resize', handleResize);
}, []);

📌 示例:清理网络请求(推荐使用 AbortController)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useEffect(() => {
const controller = new AbortController();

fetch('https://api.example.com/data', { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已取消');
}
});

return () => controller.abort(); // 取消请求
}, []);

✅ 二、类组件中的清理(使用 componentWillUnmount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyComponent extends React.Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('计时...');
}, 1000);
}

componentWillUnmount() {
clearInterval(this.timer); // 清理定时器
}

render() {
return <div>类组件</div>;
}
}

✅ 三、常见需要清理的副作用类型

副作用类型 清理方法
定时器 clearInterval / clearTimeout
DOM 事件监听器 removeEventListener
WebSocket / Channel .close()
自定义订阅(如 RxJS) .unsubscribe()
异步请求 AbortController.abort()

⚠️ 为什么必须清理副作用?

  • 🧠 避免内存泄漏;
  • ⛔ 避免卸载后仍尝试更新组件;
  • 🔁 防止重复绑定或重复执行;
  • 🛠 提高性能,避免潜在 bug。

Context API

Context API 是 React 提供的用来实现跨层级组件共享状态的解决方案,主要目的是避免在组件层层嵌套时,通过一层层 props 传递数据,非常适合全局或者半全局状态的管理。

Context API 的使用场景和详细解释

1. 传统的 props 传递痛点

假设你有一个组件树:

1
2
3
4
5
6
7
<App>
<Header>
<UserMenu>
<UserProfile />
</UserMenu>
</Header>
</App>

如果 UserProfile 需要显示当前登录用户的信息(如用户名、头像),传统做法是:

  • 先在 App 组件拿到用户信息(比如从 API 或 Redux)
  • 通过 props 一层层传递给 Header,再传给 UserMenu,再传给 UserProfile

问题

  • 当层级很深时,props 传递非常繁琐且臃肿(“props drilling”,props 钻取问题)
  • 中间组件只是传递数据,并不真正用到这些数据,导致代码冗余且不易维护

2. Context API 解决方案

Context 允许你:

  • 在某个顶层组件创建一个“上下文”(Context 对象),里面保存共享的数据
  • 在任意深度的后代组件中通过 useContext(函数组件)或者 Context.Consumer(类组件)直接获取数据
  • 这样就避免了层层传 props,简化了数据流转

3. 使用流程

① 创建 Context
1
2
3
import React from 'react';

const UserContext = React.createContext(null);
② 在顶层提供数据(Provider)
1
2
3
4
5
6
7
8
9
function App() {
const user = { name: 'Alice', avatar: 'alice.png' };

return (
<UserContext.Provider value={user}>
<Header />
</UserContext.Provider>
);
}
③ 在任意深层子组件使用数据
  • 函数组件中使用 useContext
1
2
3
4
5
6
7
import React, { useContext } from 'react';

function UserProfile() {
const user = useContext(UserContext);

return <div>欢迎,{user.name}</div>;
}
  • 类组件中使用 UserContext.Consumer
1
2
3
<UserContext.Consumer>
{user => <div>欢迎,{user.name}</div>}
</UserContext.Consumer>

4. 适用场景举例

  • 主题切换(深层组件需要访问主题色、字体大小等配置)
  • 用户身份信息(登录用户信息,权限控制等)
  • 多语言国际化(当前语言包)
  • 全局配置项(API 地址、界面配置等)

5. 注意事项

  • Context 适合共享“全局”或“跨多层”状态,不建议用它做频繁变更的本地状态(如表单输入)
  • Context 里的数据变更,会导致所有消费组件重新渲染,注意性能优化
  • 结合 React.memouseMemo 可以降低无效渲染