快速掌握 React 基础入门: 一个完整的指南(二)https://developer.aliyun.com/article/1426133
C. Updating 阶段
Updating 阶段是指一个已经挂载的组件在更新时的过程,更新可以由组件自身的 setState
方法、forceUpdate
方法,或父组件的重新渲染触发。
在 Updating 阶段,React 会自动调用以下生命周期函数(按照调用顺序):
static getDerivedStateFromProps(props, state)
:静态函数,该函数会在虚拟 DOM 处理之前被调用,用于更新组件的状态。与 Mounting 阶段的调用方式相同。shouldComponentUpdate(nextProps, nextState)
:更新函数,用于决定组件是否需要更新。返回一个布尔值,如果返回true
,则执行更新操作,否则不进行更新。默认返回true
,即总是更新。render()
:渲染函数,返回组件的虚拟 DOM,用于显示在页面上。getSnapshotBeforeUpdate(prevProps, prevState)
:静态函数,用于获取组件更新前的 DOM 状态。可以在此函数中保存一些 DOM 状态,并在componentDidUpdate
中恢复这些状态。componentDidUpdate(prevProps, prevState, snapshot)
:更新函数,用于在组件更新后进行一些额外的操作,例如重新计算 DOM 元素的位置、滚动条的位置等。
示例代码:
import React, { Component } from 'react'; class Example extends Component { constructor(props) { super(props); this.state = { value: 'Hello World' }; } static getDerivedStateFromProps(props, state) { console.log('getDerivedStateFromProps', props, state); return null; } shouldComponentUpdate(nextProps, nextState) { console.log('shouldComponentUpdate', nextProps, nextState); return true; } getSnapshotBeforeUpdate(prevProps, prevState) { console.log('getSnapshotBeforeUpdate', prevProps, prevState); return 'snapshot'; } componentDidUpdate(prevProps, prevState, snapshot) { console.log('componentDidUpdate', prevProps, prevState, snapshot); } handleClick = () => { this.setState({ value: 'Hello React' }); }; render() { console.log('render'); return ( <div> <p>{this.state.value}</p> <button onClick={this.handleClick}>Click me</button> </div> ); } } export default Example;
在这个例子中,我们为 Example
组件添加了一个按钮,当用户点击按钮时,会调用 handleClick
方法,将组件的 value
状态更新为 'Hello React'
。此时,React 会自动调用 Updating 阶段的生命周期函数。
当用户点击按钮时,React 首先会调用 getDerivedStateFromProps
函数,返回 null
。然后,React 会调用 shouldComponentUpdate
函数,返回 true
。接着,React 调用 render
函数,更新组件的虚拟 DOM,并显示在页面上。在 render
函数执行完毕后,React 调用 getSnapshotBeforeUpdate
函数,返回字符串 'snapshot'
。最后,React 调用 componentDidUpdate
函数,更新完成后,进行一些清理工作和其他操作,例如重新计算 DOM 元素的位置等。
D. Unmounting 阶段
Unmounting 阶段是指组件从页面中被卸载的过程,不再被渲染到页面上。卸载可能是由父组件的重新渲染、组件自身的 setState
方法、forceUpdate
方法,或使用 React 的 ReactDOM.unmountComponentAtNode
方法触发。
在 Unmounting 阶段,React 会自动调用以下生命周期函数(按照调用顺序):
componentWillUnmount()
:卸载函数,用于在组件被卸载前进行清理操作,例如取消订阅、清除定时器等。
示例代码:
import React, { Component } from 'react'; class Example extends Component { constructor(props) { super(props); this.state = { value: 'Hello World' }; } componentWillUnmount() { console.log('componentWillUnmount'); } render() { console.log('render'); return ( <div> <p>{this.state.value}</p> </div> ); } } export default Example;
在这个例子中,我们只实现了 componentWillUnmount
函数,并在组件卸载前输出了一条信息。当组件被卸载时,React 会自动调用该函数,进行清理操作。
需要注意的是,在卸载阶段,组件不会再被更新渲染到 DOM 中,因此在卸载阶段的生命周期函数中不能通过 setState
方法更新组件的状态,这样会导致 React 报错。
VIII. React 中的列表和 keys
在 React 中渲染列表时,可以使用 Array.map
方法进行遍历渲染。有时候,我们需要在渲染列表的时候给每个列表项分配一个唯一的标识,这个标识称为“key”。
React中使用key来标记列表项的唯一性,它可以帮助 React 快速判断出哪些列表项需要更新,从而提高操作性能。
在 React 中使用 key
的方式很简单,例如:
const listItems = items.map(item => ( <li key={item.id}> {item.text} </li> )); return ( <ul> {listItems} </ul> );
在上述代码中,我们使用了数组的 map
方法将 items
数组转成了一个列表,并给每个列表项分配了一个唯一的 key
,React 会根据列表项的 key
来判断哪些列表项需要更新,哪些可以复用。这种方式避免了不必要的 DOM 操作,提高了操作性能。
需要注意的是,key 必须是唯一、稳定且可预测的,不能使用数组的索引作为 key,因为当数组顺序发生变化时,会导致 key 不再唯一,从而使 React 在渲染时出现问题。
另外,key 只是在兄弟节点之间必须是唯一的,它们不需要在全局唯一。如果列表项的 id 是全局唯一的,那么可以使用 id 作为 key。如果没有全局唯一的标识,可以使用组合的方式,例如使用 [item.id, index]
作为 key。
总结一下:
- 在 React 中使用
key
来标记列表项的唯一性,可以提高操作性能。 key
必须是唯一、稳定且可预测的,不能使用数组的索引作为 key。key
只需要在兄弟节点之间是唯一的,不需要在全局唯一。
IX. 条件渲染
在 React 中,可以使用条件渲染来根据组件的状态来显示或隐藏组件。条件渲染是指根据条件来决定是否渲染组件或组件的一部分。
React 有两种方式来实现条件渲染:
1. 使用条件运算符
在组件的 render
函数中,可以使用条件运算符 ? :
来根据条件渲染组件或组件的一部分,例如:
class Example extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; } render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? ( <UserGreeting /> ) : ( <GuestGreeting /> )} </div> ); } } function UserGreeting(props) { return <h1>Welcome back!</h1>; } function GuestGreeting(props) { return <h1>Please sign up.</h1>; }
在上述代码中,根据 isLoggedIn
状态的值来渲染 UserGreeting
或 GuestGreeting
。
2. 使用 &&
运算符
在组件的 render
函数中,可以使用 &&
运算符来根据条件渲染组件或组件的一部分,例如:
class Example extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; } render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn && <UserGreeting />} {!isLoggedIn && <GuestGreeting />} </div> ); } }
在上述代码中,根据 isLoggedIn
状态的值来渲染 UserGreeting
或 GuestGreeting
,这里使用了 &&
和 !
运算符。
总结一下:
- 条件渲染是指根据条件来决定是否渲染组件或组件的一部分。
- React 可以使用条件运算符
? :
或&&
运算符来实现条件渲染。 - 条件渲染可以根据组件的状态来显示或隐藏组件,可以提高组件的动态性和灵活性。
X. React Hooks
A. useEffect
useEffect
是 React 16.8 之后新增的 Hook,它可以让函数组件具有类似于类组件的生命周期钩子函数。useEffect
接收一个回调函数和一个依赖数组作为参数,每当组件渲染时,React 会根据依赖数组中的值来决定是否执行回调函数。
useEffect
的使用方式如下:
import React, { useEffect } from 'react'; function Example() { useEffect(() => { // 在组件挂载和更新时会被调用 console.log('componentDidMount and componentDidUpdate'); return () => { // 在组件卸载和更新时会被调用 console.log('componentWillUnmount and componentDidUpdate'); }; }, [props.value]); // 仅在 props.value 发生变化时执行回调函数 return <div>{props.value}</div>; }
在这个例子中,我们定义了一个函数组件 Example
,并使用了 useEffect
Hook。useEffect
接收一个回调函数和一个依赖数组作为参数,函数组件每次渲染时都会执行回调函数。当依赖数组中的值发生变化时,React 会重新执行回调函数。如果依赖数组为空,则函数组件只会在挂载和卸载时执行回调函数。
回调函数可以返回一个清理函数,用于在组件卸载前执行一些清理操作,例如取消订阅、关闭定时器等等。
需要注意的是,由于 useEffect
是在组件函数内部定义的,因此回调函数可以调用父组件中的 state 和 props,而不需要使用类组件中的生命周期函数来访问它们。
总结一下:
useEffect
是 React 16.8 之后新增的 Hook。useEffect
接收一个回调函数和一个依赖数组作为参数,函数组件每次渲染时都会执行回调函数。- 当依赖数组中的值发生变化时,React 会重新执行回调函数。
- 回调函数可以返回一个清理函数,用于在组件卸载前执行一些清理操作。
- 由于
useEffect
是在组件函数内部定义的,因此回调函数可以调用父组件中的 state 和 props。
B. useState
useState
是 React 16.8.0 之后推出的一个 Hook 函数,它可以让函数组件具有状态(state)。
useState
的使用方式如下:
import { useState } from 'react'; function Example() { // 在函数组件中使用 useState Hook 来定义状态 const [count, setCount] = useState(0); function handleClick() { // 使用 setCount 函数来更新状态 setCount(count + 1); } return ( <div> <p>You clicked {count} times.</p> <button onClick={handleClick}>Click Me</button> </div> ); }
在这个例子中,我们定义了一个函数组件 Example
,并使用了 useState
Hook 来定义状态。useState
接收一个状态的初始值作为参数,并返回一个数组,第一个元素是当前状态的值,第二个元素是用于更新状态的函数。每当 setCount
函数被调用时,React 会自动重新渲染函数组件,并把新的状态值传递给组件。在更新状态时,不需要手动去合并旧状态和新状态,React 会自动处理。
需要注意的是,由于 useState
是在组件函数内部定义的,因此可以在同一个组件函数中多次使用 useState
来定义多个状态。
总结一下:
useState
是 React 16.8 之后推出的 Hook 函数,可以让函数组件具有状态(state)。useState
接收一个状态的初始值作为参数,并返回一个数组,第一个元素是当前状态的值,第二个元素是用于更新状态的函数。- 在更新状态时,不需要手动去合并旧状态和新状态,React 会自动处理。
useState
是在组件函数内部定义的,因此可以在同一个组件函数中多次使用useState
来定义多个状态。
C. useContext
useContext
是 React 16.8.0 之后推出的 Hook 函数,它可以让函数组件消费 Context
。
在 React 中,Context
可以让我们在组件树中共享数据,无需一级一级手动传递数据。当应用程序规模较大时,Context
可以非常有用,帮助我们简化数据的传递和管理。
使用 useContext
可以让我们更轻松地从 Context
中读取数据。通常,我们需要首先创建一个 Context
对象,然后将其传递给使用它的组件,使用 useContext
函数从 Context 中读取数据。
接下来给出一个使用 useContext
函数的例子:
import React, { useContext } from 'react'; const UserContext = React.createContext({ name: 'Guest' }); function Example() { const user = useContext(UserContext); return ( <div> <p>Hello, {user.name}!</p> </div> ); }
在上述例子中,我们创建了一个名为 UserContext
的 Context 对象,并提供一个默认值 { name: 'Guest' }
。然后,在 Example
组件中使用 useContext
函数从 UserContext
中读取数据,因此在组件中可以直接访问 user
对象,不需要通过 props 层层传递数据。
需要注意的是,在使用 useContext
时,需要将 UserContext
对象作为参数传递给 useContext
函数,例如:
const user = useContext(UserContext);
总结一下:
useContext
是 React 16.8 之后推出的Hook
函数,可以让函数组件消费Context
。- Context 可以让我们在组件树中共享数据,无需一级一级手动传递数据。
- 使用
useContext
函数可以更轻松地从Context
中读取数据。 - 在使用
useContext
时,需要将Context
对象作为参数传递给useContext
函数。
D. useReducer
useReducer
是 React 16.8.0 之后推出的 Hook 函数,可以方便地管理组件的状态。useReducer
和 useState
类似,都可以让函数组件管理状态,但 useReducer
更适用于复杂的状态逻辑。
useReducer
接收一个“reducer”函数和一个初始状态作为参数,返回一个数组,第一个元素是当前状态值,第二个元素是用于更新状态的 dispatch 函数。
下面是一个使用 useReducer
的例子:
import React, { useReducer } from 'react'; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function Example() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }
在这个例子中,我们定义了一个初始状态 { count: 0 }
和一个“reducer”函数 reducer
,然后在 Example
组件中使用了 useReducer
来定义状态。useReducer
的第一个参数是 reducer
函数,第二个参数是初始状态。
在组件中可以通过 dispatch
函数来触发状态的更新,通过传入一个 action
对象来描述状态的变化。在 reducer
函数中根据 action.type
的值来更新状态,并返回一个新的状态对象。
需要注意的是,在使用 useReducer
时,尽量避免直接修改原有状态对象,应该返回一个新的状态对象。
总结一下:
useReducer
是 React 16.8 之后推出的 Hook 函数,可以方便地管理组件的状态。useReducer
接收一个“reducer”函数和一个初始状态作为参数,返回一个数组。第一个元素是当前状态值,第二个元素是用于更新状态的 dispatch 函数。- 在
useReducer
中,通过dispatch
函数来触发状态的更新,在reducer
函数中根据action.type
的值来更新状态,并返回一个新的状态对象。