文章目录
一、React生命周期
1. 挂载阶段(Mounting)
- constructor(props)
- 执行时机:组件创建时调用,是类组件中第一个被调用的方法。
- 作用:
- 初始化组件的
state
,通过this.state
来设置初始状态。例如:this.state = { count: 0 };
初始化一个计数器状态。 - 绑定事件处理函数。由于在JavaScript中,类方法默认不会绑定
this
,所以需要手动绑定。如this.handleClick = this.handleClick.bind(this);
。
- 初始化组件的
- 注意事项:
- 必须先调用
super(props)
,这是JavaScript类继承的要求,用于初始化父类的构造函数,确保组件能够正确访问props
。
- 必须先调用
- componentWillMount(已废弃)
- 执行时机:在组件挂载到DOM之前,
render
方法之前调用。 - 废弃原因:在异步渲染等场景下可能导致问题,并且它的一些功能可以被其他生命周期方法替代,如
componentDidMount
。
- 执行时机:在组件挂载到DOM之前,
- render()
- 执行时机:挂载阶段和更新阶段都会调用。
- 作用:
- 用于描述组件的UI结构,返回一个React元素,这个元素可以是原生DOM元素(如
<div>
、<p>
等)或者是其他自定义组件。例如:return <div>Hello, World!</div>;
。 - 根据组件的
props
和state
生成虚拟DOM(Virtual DOM),React会根据虚拟DOM来更新真实DOM。
- 用于描述组件的UI结构,返回一个React元素,这个元素可以是原生DOM元素(如
- 注意事项:
- 应该是一个纯函数,意味着它不应该修改组件的状态(
this.state
),也不应该有其他副作用,如发送网络请求、修改DOM等。如果在render
中修改状态,可能会导致无限循环的更新。
- 应该是一个纯函数,意味着它不应该修改组件的状态(
- componentDidMount()
- 执行时机:组件挂载到DOM之后立即调用。
- 作用:
- 适合进行需要DOM节点的操作。例如,通过
document.getElementById
等DOM API获取DOM元素并进行操作。 - 发送网络请求获取数据来填充组件内容,因为此时组件已经挂载到DOM,可以安全地更新组件状态。例如:
- 适合进行需要DOM节点的操作。例如,通过
componentDidMount() {
fetch('https://example.com/api/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
- 初始化第三方JavaScript库。比如使用
Chart.js
绘制图表,需要在DOM节点存在后进行初始化。
- 注意事项:- 注意清理在这个方法中创建的资源,如定时器、订阅等。可以在
componentWillUnmount
中进行清理。
- 注意清理在这个方法中创建的资源,如定时器、订阅等。可以在
补充
-
static getDerivedStateFromProps
- 在组件挂载阶段,
static getDerivedStateFromProps
是在constructor
之后、render
之前被调用。这使得它能够在组件首次渲染之前,根据传入的props
对state
进行初始化或者调整。 - 例如,在以下代码中:
class MyComponent extends React.Component { static getDerivedStateFromProps(props, state) { console.log('getDerivedStateFromProps called during mount'); if (!state.initialized) { return { initialized: true, value: props.initialValue }; } return null; } constructor(props) { super(props); console.log('Constructor called'); this.state = { initialized: false }; } render() { console.log('Render called'); return <div>{this.state.value}</div>; } }
- 当组件挂载时,控制台会先打印
Constructor called
,然后是getDerivedStateFromProps called during mount
,最后是Render called
。可以看到getDerivedStateFromProps
在constructor
和render
之间执行,用于根据props
来初始化state
中的value
属性,并设置initialized
为true
。
- 在组件挂载阶段,
-
根据props初始化state
- 应用场景:当组件的初始状态依赖于从父组件传递过来的
props
时,getDerivedStateFromProps
是一个很好的工具。例如,一个显示用户信息的组件,其初始状态可能需要根据父组件传递的用户数据来设置。 - 示例代码:
class UserProfile extends React.Component { static getDerivedStateFromProps(props, state) { if (!state.userData) { return { userData: props.user }; } return null; } constructor(props) { super(props); this.state = {}; } render() { return ( <div> <p>Name: {this.state.userData.name}</p> <p>Age: {this.state.userData.age}</p> </div> ); } }
- 在这个
UserProfile
组件中,state
中的userData
初始值是从props
获取的。getDerivedStateFromProps
检查state
中是否已经有userData
,如果没有(组件首次挂载时),就将props.user
赋值给state.userData
,然后在render
函数中使用这个状态来显示用户的姓名和年龄。
- 应用场景:当组件的初始状态依赖于从父组件传递过来的
-
处理特殊的初始状态设置需求
- 应用场景:有时候,组件可能需要根据
props
进行一些特殊的初始状态设置,比如对props
进行转换或者验证后再存入state
。例如,一个组件接收一个日期字符串格式的props
,但在组件内部需要将其转换为日期对象存储在state
中。 - 示例代码:
class DateDisplay extends React.Component { static getDerivedStateFromProps(props, state) { if (!state.dateObject) { const date = new Date(props.dateString); if (!isNaN(date.getTime())) { return { dateObject: date }; } else { return { dateObject: null, error: 'Invalid date format' }; } } return null; } constructor(props) { super(props); this.state = {}; } render() { if (this.state.error) { return <p>{this.state.error}</p>; } else if (this.state.dateObject) { return <p>{this.state.dateObject.toDateString()}</p>; } return null; } }
- 在
DateDisplay
组件中,getDerivedStateFromProps
检查state
中是否已经有dateObject
。如果没有(首次挂载时),它会尝试将props.dateString
转换为日期对象。如果转换成功,就将日期对象存入state.dateObject
;如果转换失败,就设置state.error
来显示错误信息。然后在render
函数中根据state
的情况来显示日期或者错误信息。
- 应用场景:有时候,组件可能需要根据
2. 更新阶段(Updating)
- componentWillReceiveProps(nextProps)(已废弃)
- 执行时机:在组件接收到新的
props
时被调用,在render
方法之前。 - 废弃原因:可能会导致性能问题和意外行为,特别是在父组件频繁更新
props
的情况下。React团队推荐使用getDerivedStateFromProps
作为替代。
- 执行时机:在组件接收到新的
- shouldComponentUpdate(nextProps, nextState)
- 执行时机:在组件接收到新的
props
或者state
更新之前被调用。 - 作用:
- 用于性能优化。可以根据
nextProps
和nextState
与当前props
和state
的比较,决定组件是否需要重新渲染。例如:
- 用于性能优化。可以根据
- 执行时机:在组件接收到新的
shouldComponentUpdate(nextProps, nextState) {
return nextProps.visible!== this.props.visible;
}
- 只有当返回`true`时,组件才会继续更新流程(调用`render`等后续方法)。
- **注意事项**:
- 需要谨慎使用,因为错误的返回值可能导致组件不更新或者过度更新。
-
componentWillUpdate(nextProps, nextState)(已废弃)
- 执行时机:在组件更新之前被调用,在
shouldComponentUpdate
之后,render
之前。 - 废弃原因:和
componentWillReceiveProps
类似,可能导致难以预测的副作用,在异步渲染场景下会出现问题。
- 执行时机:在组件更新之前被调用,在
-
render()(同挂载阶段的
render
) -
getSnapshotBeforeUpdate(prevProps, prevState)
- 阶段位置
getSnapshotBeforeUpdate
生命周期方法在组件更新阶段被调用,具体是在render
方法之后,componentDidUpdate
之前。这个位置使得它能够获取到组件更新前的最后一个“快照”(DOM状态或其他相关信息),并将其传递给componentDidUpdate
。
- 应用场景
-
保存滚动位置
- 场景描述:在一个可滚动的列表组件中,当列表数据更新(例如添加或删除了列表项)时,为了保持用户的滚动位置,需要在更新前获取滚动位置,然后在更新后恢复滚动位置。
- 示例代码:
class ScrollableList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); this.state = { items: [], scrollTop: 0 }; } componentDidMount() { // 模拟获取初始数据 this.setState({ items: [1, 2, 3, 4, 5] }); } handleAddItem = () => { const newItems = [...this.state.items, this.state.items.length + 1]; this.setState({ items: newItems }); }; getSnapshotBeforeUpdate(prevProps, prevState) { if (this.listRef.current) { return this.listRef.current.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot!== null) { this.listRef.current.scrollTop = snapshot; } } render() { return ( <div ref={this.listRef} style={{ height: '200px', overflow: 'auto' }}> <ul> {this.state.items.map((item) => ( <li key={item}>{item}</li> ))} </ul> <button onClick={this.handleAddItem}>Add Item</button> </div> ); } }
- 解释:在这个
ScrollableList
组件中,getSnapshotBeforeUpdate
用于在列表更新前获取div
元素(通过ref
获取)的滚动位置scrollTop
。这个滚动位置信息作为snapshot
参数传递给componentDidUpdate
,在componentDidUpdate
中恢复滚动位置,从而实现当列表更新时,用户的滚动位置保持不变。
-
记录元素尺寸变化
- 场景描述:当组件中的某个元素尺寸(如宽度、高度)因为状态或属性更新而发生变化时,可以在
getSnapshotBeforeUpdate
中记录旧的尺寸,然后在componentDidUpdate
中比较新旧尺寸,执行相应的操作,例如调整其他元素的布局。 - 示例代码:
class ResizableComponent extends React.Component { constructor(props) { super(props); this.componentRef = React.createRef(); this.state = { width: '100px', height: '100px' }; } handleResize = () => { this.setState((prevState) => ({ width: `${prevState.width.slice(0, -2) * 1.2}px`, height: `${prevState.height.slice(0, -2) * 1.2}px` })); }; getSnapshotBeforeUpdate(prevProps, prevState) { if (this.componentRef.current) { return { prevWidth: this.componentRef.current.offsetWidth, prevHeight: this.componentRef.current.offsetHeight }; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot) { const widthChange = this.componentRef.current.offsetWidth - snapshot.prevWidth; const heightChange = this.componentRef.current.offsetHeight - snapshot.prevHeight; console.log(`Width change: ${widthChange}px, Height change: ${heightChange}px`); } } render() { return ( <div ref={this.componentRef} style={{ width: this.state.width, height: this.state.height, backgroundColor: 'lightblue', cursor: 'pointer' }} onClick={this.handleResize} /> ); } }
- 解释:在
ResizableComponent
组件中,getSnapshotBeforeUpdate
获取组件div
元素(通过ref
获取)更新前的宽度和高度。在componentDidUpdate
中,通过比较更新前后的宽度和高度,计算出尺寸变化,并打印到控制台。这可以用于进一步的布局调整或其他与尺寸变化相关的操作。
- 场景描述:当组件中的某个元素尺寸(如宽度、高度)因为状态或属性更新而发生变化时,可以在
-
componentDidUpdate(prevProps, prevState)
- 执行时机:在组件更新后被调用,在
render
之后。 - 作用:
- 可以根据更新前后的
props
和state
进行操作。例如,比较prevProps
和this.props
来判断某个属性是否改变,然后执行相应的操作。 - 对DOM进行操作,如更新第三方库的配置等。
- 可以根据更新前后的
- 注意事项:
- 注意避免在这个方法中引起无限循环更新。如果在
componentDidUpdate
中更新状态,并且没有正确的条件限制,可能会导致组件不断地重新渲染。
- 注意避免在这个方法中引起无限循环更新。如果在
- 执行时机:在组件更新后被调用,在
3. 卸载阶段(Unmounting)
- componentWillUnmount()
- 执行时机:在组件从DOM中移除之前被调用。
- 作用:
- 用于清理在
componentDidMount
或其他生命周期中创建的副作用。例如:- 清除定时器:
clearInterval(this.timer);
,如果在componentDidMount
中设置了定时器,需要在这里清除。 - 取消网络请求:如果使用了一些没有自动取消机制的网络请求库,需要手动取消请求。
- 取消订阅:如取消对Redux store或者事件总线的订阅。
- 清除定时器:
- 用于清理在
- 注意事项:
- 忘记清理副作用可能会导致内存泄漏、性能下降或者其他意外行为。
二、React组件间的通信交互
1. 父子组件通信
- 父组件向子组件传递数据(通过
props
)- 方式:在父组件的
render
方法中,将数据作为属性传递给子组件。例如,父组件有一个数据message
,传递给子组件ChildComponent
:
- 方式:在父组件的
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello from parent'
};
}
render() {
return <ChildComponent message={this.state.message} />;
}
}
class ChildComponent extends React.Component {
render() {
return <div>{this.props.message}</div>;
}
}
- 子组件向父组件通信(通过回调函数)
- 方式:父组件将一个回调函数作为
props
传递给子组件,子组件在需要的时候调用这个回调函数,并将数据传递给父组件。例如,子组件中有一个按钮,点击按钮后将数据传递给父组件:
- 方式:父组件将一个回调函数作为
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
childData: null
};
}
handleChildData = (data) => {
this.setState({ childData: data });
};
render() {
return (
<div>
<ChildComponent sendDataToParent={this.handleChildData} />
<div>{this.state.childData}</div>
</div>
);
}
}
class ChildComponent extends React.Component {
handleClick = () => {
const data = 'Data from child';
this.props.sendDataToParent(data);
};
render() {
return <button onClick={this.handleClick}>Send Data to Parent</button>;
}
}
2. 兄弟组件通信
- 通过共同的父组件作为中间人
- 方式:兄弟组件A和B,A要给B传递数据。A通过调用父组件传递过来的回调函数将数据传递给父组件,父组件再将数据通过
props
传递给B。例如:
- 方式:兄弟组件A和B,A要给B传递数据。A通过调用父组件传递过来的回调函数将数据传递给父组件,父组件再将数据通过
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
dataFromA: null
};
}
handleDataFromA = (data) => {
this.setState({ dataFromA: data });
};
render() {
return (
<div>
<SiblingA sendDataToParent={this.handleDataFromA} />
<SiblingB dataFromA={this.state.dataFromA} />
</div>
);
}
}
class SiblingA extends React.Component {
handleClick = () => {
const data = 'Data from Sibling A';
this.props.sendDataToParent(data);
};
render() {
return <button onClick={this.handleClick}>Send Data</button>;
}
}
class SiblingB extends React.Component {
render() {
return <div>{this.props.dataFromA}</div>;
}
}
- 使用状态管理库(如Redux、Mobx)
- 方式:
- 以Redux为例,组件通过
connect
函数连接到Redux store,dispatch
操作来触发状态改变,其他组件可以通过订阅store
的状态更新来获取最新数据。 - 首先定义
action
(描述发生的操作),例如:
- 以Redux为例,组件通过
- 方式:
const ADD_ITEM = 'ADD_ITEM';
export const addItem = (item) => ({
type: ADD_ITEM,
payload: item
});
- 然后定义`reducer`(根据`action`更新状态),例如:
const initialState = {
items: []
};
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_ITEM:
return {
...state,
items: [...state.items, action.payload]
};
default:
return state;
}
};
- 在组件中使用:
- 发送数据的组件:
import React from 'react';
import { connect } from 'react-redux';
import { addItem } from './actions';
class ItemSender extends React.Component {
handleSendItem = () => {
const newItem = 'New Item';
this.props.addItem(newItem);
};
render() {
return <button onClick={this.handleSendItem}>Send Item</button>;
}
}
const mapDispatchToProps = (dispatch) => ({
addItem: (item) => dispatch(addItem(item))
});
export default connect(null, mapDispatchToProps)(ItemSender);
- 接收数据的组件:
import React from 'react';
import { connect } from 'react-redux';
class ItemReceiver extends React.Component {
render() {
return (
<div>
{this.props.items.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
items: state.items
});
export default connect(mapStateToProps, null)(ItemReceiver);
三、React页面缓存机制应用
1. 使用React.memo
进行组件缓存
- 原理:
React.memo
是一个高阶组件,它会对组件的props
进行浅比较。如果props
没有改变,组件就不会重新渲染,从而实现缓存效果。 - 应用场景:对于纯展示组件,它们的渲染只依赖于
props
,并且重新渲染成本较高(如组件内部有复杂的计算或者渲染大量子元素)。例如:
const MyComponent = React.memo((props) => {
console.log('MyComponent is rendering');
return <div>{props.text}</div>;
});
- 注意事项:
- 只是对
props
进行浅比较,对于复杂的数据结构(如嵌套对象或数组),可能会出现即使数据内容改变,但浅比较认为没有改变的情况。可以通过自定义比较函数来解决这个问题,React.memo
可以接受第二个参数,一个比较函数。例如:
- 只是对
const areEqual = (prevProps, nextProps) => {
return prevProps.text === nextProps.text;
};
const MyComponent = React.memo((props) => {
console.log('MyComponent is rendering');
return <div>{props.text}</div>;
}, areEqual);
2. 使用shouldComponentUpdate
进行手动缓存控制
- 原理:在组件内部实现
shouldComponentUpdate
生命周期方法,通过比较新的props
和state
与旧的props
和state
,来决定组件是否需要重新渲染。 - 应用场景:当
React.memo
的浅比较不能满足需求,或者需要更精细地控制组件渲染时。例如,一个组件有多个props
,但只有其中一个props
的改变会影响渲染,就可以在shouldComponentUpdate
中进行判断:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.importantValue!== this.props.importantValue;
}
render() {
return <div>{this.props.importantValue}</div>;
}
}
- 注意事项:
- 需要谨慎实现,错误的比较逻辑可能导致组件不更新或者过度更新。
3. 使用第三方库(如react - keep - alive
)
- 原理:这些库通常会在组件卸载时将组件的状态保存起来,当组件再次挂载时恢复状态,实现缓存效果。
- 应用场景:在复杂的单页应用中,对于那些切换频繁但状态需要保留的组件很有用。例如,在一个多标签页的应用中,当切换离开某个标签页(组件卸载),再次切换回来时(组件挂载),组件的状态(如表单输入内容、滚动位置等)能够恢复。
- 注意事项:
- 不同的第三方库有不同的使用方式和限制,需要仔细阅读文档。例如,有些库可能对组件的结构或者状态管理方式有特定的要求。
四、复杂商店应用(以Redux为例)
1. Redux核心概念
- Store
- 定义:它是一个单一的数据源,存储整个应用的状态。通过
createStore
函数(或者在一些高级配置中使用configureStore
等)创建。例如:
- 定义:它是一个单一的数据源,存储整个应用的状态。通过
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
- 注意事项:
- 整个应用应该只有一个
store
,以保证状态的一致性。 store
是不可变的,不能直接修改store
中的状态,只能通过发送action
来触发reducer
更新状态。
- 整个应用应该只有一个
- Reducer
- 定义:它是一个纯函数,用于根据
action
来更新store
中的状态。它接收当前状态和一个action
作为参数,并返回新的状态。例如:
- 定义:它是一个纯函数,用于根据
const initialState = {
count: 0
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
- **注意事项**:
- 必须是纯函数,即对于相同的输入(当前状态和`action`),必须返回相同的输出(新的状态)。
- 不能直接修改传入的`state`参数,应该返回一个新的状态对象,可以使用对象展开运算符(`...`)来创建新的对象。
- Action
- 定义:它是一个包含
type
属性的JavaScript对象,用于描述发生了什么操作。type
属性通常是一个字符串常量,用于在reducer
中识别不同的操作。例如:
- 定义:它是一个包含
const incrementAction = {
type: 'INCREMENT'
};
- 注意事项:
type
字段应该是唯一且具有描述性的,以便在reducer
中能够准确地处理不同的操作。- 除了
type
字段,还可以包含其他数据(通过payload
等字段)来传递操作所需的信息。
2. 在组件中使用Redux
- 通过
connect
函数连接组件和store
(在React - Redux库中)- 方式:
connect
函数用于将组件与Redux的store
连接起来,它接受两个参数:mapStateToProps
和mapDispatchToProps
。mapStateToProps
用于将store
中的状态映射为组件的props
。例如:
- 方式:
const mapStateToProps = (state) => ({
count: state.count
});
mapDispatchToProps
用于将dispatch
函数(用于触发action
)映射为组件的props
。例如:
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
});
- 注意事项:
-mapStateToProps
和mapDispatchToProps
都是可选的。如果组件只需要获取状态,只需要定义mapStateToProps
生命周期钩子函数 getDerivedStateFromProps 的使用场景
- constructor()
- 这是挂载阶段首先执行的函数。它主要用于初始化组件的状态(
this.state
)和绑定事件处理函数。例如:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // 绑定事件处理函数 this.handleClick = this.handleClick.bind(this); } handleClick() { // 处理点击事件,更新状态等操作 this.setState((prevState) => ({ count: prevState.count + 1 })); } //... }
- 这是挂载阶段首先执行的函数。它主要用于初始化组件的状态(
- getDerivedStateFromProps()(如果定义)
- 这个函数在
constructor
之后、render
之前调用。它用于根据props
的变化来更新state
,返回一个对象来更新state
,或者返回null
表示不需要更新。例如:
class MyComponent extends React.Component { static getDerivedStateFromProps(props, state) { if (props.someProp!== state.someProp) { return { someProp: props.someProp }; } return null; } //... }
- 这个函数在
- render()
- 在
getDerivedStateFromProps
(如果有)之后执行。它是必需的方法,用于描述组件的UI结构,返回一个React元素(可以是原生DOM元素或者其他自定义组件)。例如:
class MyComponent extends React.Component { //... render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.handleClick}>Increment</button> </div> ); } }
- 在
- componentDidMount()
- 在组件挂载到DOM后立即执行。这个阶段适合进行一些需要DOM节点的操作,如发送网络请求获取数据填充组件内容、添加订阅、初始化第三方JavaScript库等。例如:
class MyComponent extends React.Component { //... componentDidMount() { // 发送网络请求 fetch('https://example.com/api/data') .then(response => response.json()) .then(data => this.setState({data})); } }
所以,挂载阶段生命周期钩子函数的一般执行顺序是:constructor()
-> getDerivedStateFromProps()
(如果定义) -> render()
-> componentDidMount()
。需要注意的是,如果没有定义getDerivedStateFromProps
,则直接从constructor
跳到render
。
getDerivedStateFromProps
与componentDidMount
的区别
- 执行时机
- getDerivedStateFromProps:在组件实例化(通过构造函数
constructor
创建)之后、render
方法之前调用,并且在组件每次接收到新的props
时也会被调用。这意味着它在组件的初始挂载以及后续props
更新时都会介入。 - componentDidMount:在组件挂载到DOM之后才会被调用,也就是在
render
方法执行完成,且组件对应的真实DOM节点已经插入到文档(Document)之后。它只会在组件初始挂载时执行一次。
- getDerivedStateFromProps:在组件实例化(通过构造函数
- 功能用途
- getDerivedStateFromProps:
- 主要用于根据
props
的值来更新或派生组件的state
。它是一个静态方法,不能访问组件实例(即不能使用this
关键字)。例如,当props
中的某个数据需要同步到state
,以便在组件内部进行进一步处理或者在render
方法中使用更新后的状态来生成UI时,就可以使用这个方法。
class MyComponent extends React.Component { static getDerivedStateFromProps(props, state) { if (props.value!== state.value) { return { value: props.value }; } return null; } constructor(props) { super(props); this.state = { value: props.value }; } //... }
- 也用于在
props
变化时,对state
进行有条件的更新,以确保state
与props
之间的某种同步关系。
- 主要用于根据
- componentDidMount:
- 通常用于执行那些需要DOM节点已经存在才能进行的操作。例如,发送网络请求获取数据来填充组件内容。
class DataFetcher extends React.Component { constructor(props) { super(props); this.state = { data: null }; } componentDidMount() { fetch('https://example.com/api/data') .then(response => response.json()) .then(data => this.setState({ data })); } //... }
- 还用于初始化第三方JavaScript库,比如使用
Chart.js
在页面中绘制图表,或者添加事件监听器等操作,这些操作都依赖于组件已经挂载到DOM上。
- getDerivedStateFromProps:
- 使用限制和注意事项
- getDerivedStateFromProps:
- 由于是静态方法,它不能直接访问组件实例的属性和方法。这意味着不能在其中调用
this.setState
来触发异步操作或者更新其他非派生自props
的状态。 - 必须返回一个对象用于更新
state
或者返回null
表示不需要更新state
。返回值会直接与当前state
进行合并。
- 由于是静态方法,它不能直接访问组件实例的属性和方法。这意味着不能在其中调用
- componentDidMount:
- 因为这个方法在组件挂载后执行,所以在其中进行的操作(如添加事件监听器、创建定时器等)需要在组件卸载时进行清理,以避免内存泄漏等问题。通常在
componentWillUnmount
方法中进行这些清理操作。
- 因为这个方法在组件挂载后执行,所以在其中进行的操作(如添加事件监听器、创建定时器等)需要在组件卸载时进行清理,以避免内存泄漏等问题。通常在
- getDerivedStateFromProps:
React Hook
-
React Hook的种类
- 基础Hook
- useState:用于在函数组件中添加状态。它返回一个数组,其中第一个元素是状态值,第二个元素是更新状态的函数。
- useEffect:用于处理函数组件中的副作用,如数据获取、订阅、手动修改DOM等。它可以模拟类组件中的生命周期方法,如
componentDidMount
、componentDidUpdate
和componentWillUnmount
。 - useContext:用于在函数组件中访问React Context。它使得组件能够订阅React应用中的上下文(Context),并且在上下文的值发生变化时重新渲染。
- 额外的Hook(用于优化等场景)
- useReducer:可以作为
useState
的替代方案,用于管理更复杂的状态逻辑。它接受一个reducer函数和一个初始状态作为参数,返回当前状态和一个dispatch函数,用于触发状态更新。 - useCallback:用于优化性能,返回一个记忆化(memoized)的回调函数。只有在依赖项发生变化时,才会重新计算这个回调函数。
- useMemo:用于缓存计算结果,只有在依赖项改变时才会重新计算返回值,避免在每次组件重新渲染时都进行昂贵的计算。
- useRef:返回一个可变的ref对象,其
.current
属性被初始化为传入的参数。可以用于访问DOM元素或者在组件的多次渲染之间保存一个可变的值。 - useImperativeHandle:用于在使用
ref
时,自定义暴露给父组件的实例值。通常与forwardRef
一起使用,来控制组件内部实例的哪些属性或方法可以被外部访问。 - useLayoutEffect:与
useEffect
类似,但它会在所有的DOM变更之后同步调用,在浏览器进行绘制之前。可以用于读取DOM布局并同步触发重绘。
- useReducer:可以作为
- 基础Hook
-
开发中常用的React Hook及其应用场景
- useState
- 应用场景:用于在函数组件中存储和更新简单的状态。例如,创建一个计数器组件,或者存储表单输入框中的值。
- 示例代码:
import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); };
- useEffect
- 应用场景:
- 数据获取:在组件挂载时获取数据,如从API获取用户列表、文章列表等。
- 事件订阅和取消订阅:订阅外部事件源(如窗口滚动事件、自定义事件等),并在组件卸载时取消订阅,避免内存泄漏。
- 操作DOM(结合
useRef
):对DOM元素进行操作,如聚焦输入框、获取元素尺寸等。
- 示例代码(数据获取):
import React, { useEffect, useState } from 'react'; const UserList = () => { const [users, setUsers] = useState([]); useEffect(() => { fetch('https://example.com/api/users') .then(response => response.json()) .then(data => setUsers(data)); }, []); return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); };
- 应用场景:
- useContext
- 应用场景:用于在组件树中共享数据,避免层层传递
props
。例如,在一个主题切换应用中,共享主题状态(如亮色主题或暗色主题),使得多个组件能够根据主题状态进行渲染。 - 示例代码:
import React, { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(); const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); }; const Button = () => { const { theme, setTheme } = useContext(ThemeContext); const toggleTheme = () => { setTheme(theme === 'light'? 'dark' : 'light'); }; return ( <button onClick={toggleTheme}> {theme === 'light'? 'Switch to Dark Theme' : 'Switch to Light Theme'} </button> ); }; const App = () => { return ( <ThemeProvider> <Button /> </ThemeProvider> ); };
- 应用场景:用于在组件树中共享数据,避免层层传递
- useReducer
- 应用场景:当组件的状态更新逻辑比较复杂,涉及多个子状态或者有复杂的状态转换规则时使用。例如,在一个购物车组件中,管理购物车中商品的添加、删除、数量修改等操作,状态更新逻辑可以通过reducer函数来统一管理。
- 示例代码:
import React, { useReducer } from 'react'; const initialState = { cart: [] }; const reducer = (state, action) => { switch (action.type) { case 'ADD_TO_CART': return { ...state, cart: [...state.cart, action.payload] }; case 'REMOVE_FROM_CART': return { ...state, cart: state.cart.filter(item => item.id!== action.payload.id) }; default: return state; } }; const ShoppingCart = () => { const [state, dispatch] = useReducer(reducer, initialState); const addToCart = (product) => { dispatch({ type: 'ADD_TO_CART', payload: product }); }; const removeFromCart = (product) => { dispatch({ type: 'REMOVE_FROM_CART', payload: product }); }; return ( <div> <button onClick={() => addToCart({ id: 1, name: 'Product 1' })}>Add to Cart</button> <ul> {state.cart.map(item => ( <li key={item.id}> {item.name} <button onClick={() => removeFromCart(item)}>Remove</button> </li> ))} </ul> </div> ); };
- useCallback
- 应用场景:用于优化性能,当把一个回调函数作为
props
传递给子组件,并且这个回调函数在组件的多次渲染过程中不应该被重新创建(除非其依赖项发生变化)时使用。例如,在一个包含大量子组件的列表中,父组件有一个删除按钮的回调函数,使用useCallback
可以避免不必要的子组件重新渲染。 - 示例代码:
import React, { useState, useCallback } from 'react'; const ParentComponent = () => { const [count, setCount] = useState(0); const handleDelete = useCallback(() => { // 执行删除操作 console.log('Delete item'); }, []); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <ChildComponent onDelete={handleDelete} /> </div> ); }; const ChildComponent = ({ onDelete }) => { return ( <button onClick={onDelete}>Delete</button> ); };
- 应用场景:用于优化性能,当把一个回调函数作为
- useMemo
- 应用场景:用于缓存计算结果,当组件中有一些昂贵的计算(如复杂的数据转换、大量数据的过滤等),并且这些计算结果在依赖项没有改变的情况下不需要重新计算时使用。例如,在一个数据表格组件中,对表格数据进行排序和过滤的计算可以使用
useMemo
进行缓存。 - 示例代码:
import React, { useState, useMemo } from 'react'; const DataTable = () => { const [data, setData] = useState([ { id: 1, name: 'John', age: 30 }, { id: 2, name: 'Alice', age: 25 }, { id: 3, name: 'Bob', age: 35 } ]); const [sortBy, setSortBy] = useState('name'); const sortedData = useMemo(() => { if (sortBy === 'name') { return data.sort((a, b) => a.name.localeCompare(b.name)); } else if (sortBy === 'age') { return data.sort((a, b) => a.age - b.age); } return data; }, [data, sortBy]); return ( <div> <select onChange={(e) => setSortBy(e.target.value)}> <option value="name">Sort by Name</option> <option value="age">Sort by Age</option> </select> <table> <thead> <tr> <th>ID</th> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> {sortedData.map(item => ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.age}</td> </tr> ))} </tbody> </table> </div> ); };
- 应用场景:用于缓存计算结果,当组件中有一些昂贵的计算(如复杂的数据转换、大量数据的过滤等),并且这些计算结果在依赖项没有改变的情况下不需要重新计算时使用。例如,在一个数据表格组件中,对表格数据进行排序和过滤的计算可以使用
- useState
useState 和 useReducer 的区别
- 状态管理的复杂度
- useState
- 简单状态管理:
useState
主要用于管理简单的、独立的状态。例如,一个计数器组件中的计数状态,或者一个输入框组件中的输入值状态。 - 示例代码(计数器):
import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); };
- 简单状态管理:
- useReducer
- 复杂状态管理:
useReducer
适用于处理更复杂的状态逻辑,特别是当状态的更新依赖于前一个状态,并且涉及多种不同的操作类型时。例如,在一个表单组件中,管理表单的提交状态、验证状态以及字段值状态等多个相关状态。 - 示例代码(简单的表单验证):
import React, { useReducer } from 'react'; const initialState = { value: '', isTouched: false, isError: false }; const reducer = (state, action) => { switch (action.type) { case 'INPUT_CHANGE': return { ...state, value: action.payload, isError: false }; case 'INPUT_BLUR': return { ...state, isTouched: true, isError: state.value.trim() === '' }; default: return state; } }; const InputForm = () => { const [state, dispatch] = useReducer(reducer, initialState); const { value, isTouched, isError } = state; const onChangeHandler = (e) => { dispatch({ type: 'INPUT_CHANGE', payload: e.target.value }); }; const onBlurHandler = () => { dispatch({ type: 'INPUT_BLUR' }); }; return ( <div> <input type="text" value={value} onChange={onChangeHandler} onBlur={onBlurHandler} /> {isTouched && isError && <p>Input must not be empty.</p>} </div> ); };
- 复杂状态管理:
- useState
- 状态更新方式
- useState
- 直接更新:通过调用
setState
函数(useState
返回的第二个元素)来更新状态。这个函数可以接收新的状态值作为参数,React会自动将新值与旧值进行合并(对于对象类型的状态),或者直接替换(对于基本类型的状态)。 - 示例(更新对象状态):
import React, { useState } from 'react'; const UserProfile = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); const updateName = () => { setUser({ ...user, name: 'Alice' }); }; return ( <div> <p>Name: {user.name}</p> <p>Age: {user.age}</p> <button onClick={updateName}>Update Name</button> </div> ); };
- 直接更新:通过调用
- useReducer
- 基于动作(Action)更新:
useReducer
返回一个包含当前状态和dispatch
函数的数组。通过dispatch
函数发送一个动作(action
)来触发状态更新。action
是一个包含type
属性(通常是一个字符串,用于标识动作类型)和可选的payload
属性(用于传递与动作相关的数据)的对象。reducer
函数根据接收到的action
类型来决定如何更新状态。 - 示例(计数器的另一种实现方式):
import React, { useReducer } from 'react'; const initialState = 0; const reducer = (state, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }; const Counter = () => { const [count, dispatch] = useReducer(reducer, initialState); const increment = () => { dispatch({ type: 'INCREMENT' }); }; const decrement = () => { dispatch({ type: 'DECREMENT' }); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); };
- 基于动作(Action)更新:
- useState
- 可预测性和调试便利性
- useState
- 简单但可能复杂的更新逻辑:对于简单的状态更新,
useState
很直观。但当状态更新逻辑变得复杂,涉及多个依赖于旧状态的操作时,可能会导致代码难以理解和调试。例如,在一个复杂的异步操作后更新多个状态时,很难追踪每个状态更新的顺序和原因。
- 简单但可能复杂的更新逻辑:对于简单的状态更新,
- useReducer
- 更具可预测性的更新流程:
useReducer
的更新逻辑基于reducer
函数,它是一个纯函数,对于给定的当前状态和action
,总是返回相同的新状态。这种确定性使得状态更新的流程更加清晰,便于调试。在大型应用或复杂的组件中,当状态更新依赖于多种条件和操作时,useReducer
有助于保持代码的可维护性和可预测性。
- 更具可预测性的更新流程:
- useState
总结 (react 主要生命周期,常用生命周期,不常用生命周期,特殊环境下的生命周期)
-
主要生命周期(函数组件和类组件都涉及或有替代方式)
- 挂载(Mounting)阶段
- constructor(类组件):用于初始化组件的状态(
this.state
)和绑定事件处理函数。在组件创建时首先被调用,必须先调用super(props)
。 - render:在挂载和更新阶段都会被调用,是React组件中唯一必需的方法。用于描述组件的UI结构,返回一个React元素,应该是一个纯函数。
- componentDidMount(类组件)/ useEffect(函数组件):在组件挂载到DOM后立即被调用(
useEffect
可以通过空依赖数组模拟)。适合进行需要DOM节点的操作,如发送网络请求、初始化第三方库等。
- constructor(类组件):用于初始化组件的状态(
- 更新(Updating)阶段
- shouldComponentUpdate(类组件)/ React.memo(函数组件):用于性能优化,决定组件是否需要重新渲染。
shouldComponentUpdate
在类组件接收新的props
或者state
更新之前被调用,React.memo
是一个高阶组件,用于对函数组件的props
进行浅比较来决定是否重新渲染。 - render(同挂载阶段):更新阶段也会调用
render
来生成新的虚拟DOM。 - componentDidUpdate(类组件)/ useEffect(函数组件):在组件更新后被调用。可以在这里操作DOM,根据更新前后的
props
和state
进行一些额外的操作,useEffect
通过依赖数组来控制在特定状态或属性变化时执行副作用。
- shouldComponentUpdate(类组件)/ React.memo(函数组件):用于性能优化,决定组件是否需要重新渲染。
- 卸载(Unmounting)阶段
- componentWillUnmount(类组件)/ useEffect(函数组件返回清理函数):在组件从DOM中移除之前被调用。用于清理在
componentDidMount
或其他生命周期中创建的副作用,如清除定时器、取消网络请求、取消订阅等。
- componentWillUnmount(类组件)/ useEffect(函数组件返回清理函数):在组件从DOM中移除之前被调用。用于清理在
- 挂载(Mounting)阶段
-
常用生命周期(主要是在类组件中)
- componentDidMount:
- 应用场景广泛,几乎所有需要在组件加载后进行的操作都会用到。例如,在组件挂载后发送网络请求获取数据来填充组件内容。
class DataFetcher extends React.Component { constructor(props) { super(props); this.state = { data: null }; } componentDidMount() { fetch('https://example.com/api/data') .then(response => response.json()) .then(data => this.setState({ data })); } render() { return ( <div> {this.state.data? ( <ul> {this.state.data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ) : ( <p>Loading...</p> )} </div> ); } }
- shouldComponentUpdate:
- 用于性能优化,当组件重新渲染成本较高或者需要避免不必要的渲染时使用。例如,一个组件只有在特定
props
变化时才需要重新渲染。
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.importantValue!== this.props.importantValue; } render() { return <div>{this.props.importantValue}</div>; } }
- 用于性能优化,当组件重新渲染成本较高或者需要避免不必要的渲染时使用。例如,一个组件只有在特定
- componentDidUpdate:
- 当组件更新后需要进行一些额外操作时使用。例如,根据组件更新后的状态来更新第三方库的配置。
class ChartComponent extends React.Component { constructor(props) { super(props); this.state = { data: [] }; } componentDidMount() { // 初始数据加载和图表初始化 this.fetchDataAndUpdateChart(); } componentDidUpdate(prevProps, prevState) { if (prevState.data!== this.state.data) { // 数据变化后更新图表 this.updateChartWithNewData(); } } fetchDataAndUpdateChart = () => { fetch('https://example.com/api/chart-data') .then(response => response.json()) .then(data => this.setState({ data })); }; updateChartWithNewData = () => { // 使用this.state.data更新图表的逻辑 }; render() { return <div id="chart-container"></div>; } }
- componentDidMount:
-
不常用生命周期(类组件)
- componentWillMount(已废弃):
- 因为可能会导致一些难以预测的副作用,并且在异步渲染等场景下可能会出现问题,在React 16.3版本后被标记为不安全的生命周期方法,不建议使用。它在组件挂载之前被调用,且在
render
方法之前。
- 因为可能会导致一些难以预测的副作用,并且在异步渲染等场景下可能会出现问题,在React 16.3版本后被标记为不安全的生命周期方法,不建议使用。它在组件挂载之前被调用,且在
- componentWillReceiveProps(已废弃):
- 会导致性能问题和意外的行为,尤其是在父组件频繁更新传递
props
的情况下。从React 16.3版本后被标记为不安全的生命周期方法,不建议使用。它在组件接收到新的props
时被调用,在render
方法之前。
- 会导致性能问题和意外的行为,尤其是在父组件频繁更新传递
- componentWillUpdate(已废弃):
- 同样可能导致难以预测的副作用,在异步渲染场景下会出现问题。React 16.3版本后被标记为不安全的生命周期方法,不建议使用。在组件更新之前被调用,在
shouldComponentUpdate
之后,render
之前。
- 同样可能导致难以预测的副作用,在异步渲染场景下会出现问题。React 16.3版本后被标记为不安全的生命周期方法,不建议使用。在组件更新之前被调用,在
- componentWillMount(已废弃):
-
特殊环境下的生命周期(类组件)
- getDerivedStateFromProps:
- 应用场景包括根据
props
初始化或更新state
,实现受控组件和非受控组件之间的转换等。它是一个静态方法,在组件实例化之后、render
方法之前调用,并且在组件每次接收到新的props
时也会被调用。
class InputWrapper extends React.Component { static getDerivedStateFromProps(props, state) { if ('value' in props) { return { isControlled: true, value: props.value }; } return { isControlled: false, value: state.value }; } constructor(props) { super(props); this.state = { value: '', isControlled: false }; } handleChange = (e) => { if (!this.state.isControlled) { this.setState({ value: e.target.value }); } }; render() { return ( <input type="text" value={this.state.isControlled? this.state.value : undefined} onChange={this.handleChange} /> ); } }
- 应用场景包括根据
- getSnapshotBeforeUpdate:
- 在组件更新阶段被调用,具体是在
render
方法之后,componentDidUpdate
之前。用于获取组件更新前的最后一个“快照”(如DOM状态或其他相关信息),并将其传递给componentDidUpdate
。例如,用于保存滚动位置或记录元素尺寸变化等场景。
class ScrollableList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); this.state = { items: [], scrollTop: 0 }; } componentDidMount() { // 模拟获取初始数据 this.setState({ items: [1, 2, 3, 4, 5] }); } handleAddItem = () => { const newItems = [...this.state.items, this.state.items.length + 1]; this.setState({ items: newItems }); } getSnapshotBeforeUpdate(prevProps, prevState) { if (this.listRef.current) { return this.listRef.current.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot!== null) { this.listRef.current.scrollTop = snapshot; } } render() { return ( <div ref={this.listRef} style={{ height: '200px', overflow: 'auto' }}> <ul> {this.state.items.map((item) => ( <li key={item}>{item}</li> ))} </ul> <button onClick={this.handleAddItem}>Add Item</button> </div> ); } }
- 在组件更新阶段被调用,具体是在
- getDerivedStateFromProps: