生命周期
在 React 中,组件的生命周期函数可以帮助我们管理组件在不同阶段的行为。React 16.3 之前的版本和 React 16.3 之后有所不同。以下是 React 组件生命周期函数的详细列表:
旧的生命周期(React 16.3 之前)
componentWillMount()
- 在组件即将被挂载到 DOM 之前立刻调用。
componentDidMount()
- 组件已经被挂载到 DOM 后立刻调用。
componentWillReceiveProps(nextProps)
- 在组件接收到新的 props 之前立刻调用。此方法在初始化渲染时不会被调用。
shouldComponentUpdate(nextProps, nextState)
- 在组件接收到新的 props 或 state 时,判断组件是否需要重新渲染。默认返回
true
,除非你有特定的优化需要。
- 在组件接收到新的 props 或 state 时,判断组件是否需要重新渲染。默认返回
componentWillUpdate(nextProps, nextState
)- 在组件开始重新渲染之前立刻调用。
componentDidUpdate(prevProps, prevState)
- 组件重新渲染并将更改应用到 DOM 后立刻调用。
componentWillUnmount()
- 在组件被卸载和销毁之前立刻调用。通常在此方法中执行一些清理工作。
componentDidCatch(error, info)
- 在子组件抛出错误后被调用。仅在开发模式下以及在生产模式下使用 React 16 时才会被触发。
新的生命周期(React 16.3 之后)
从 React 16.3 开始,旧的生命周期方法已被标记为不推荐使用,并被官方废弃。而新的生命周期主要围绕三个主要阶段:
- 挂载
(Mounting)
constructor()
static getDerivedStateFromProps(props, state)
render()
componentDidMount()
- 更新
(Updating)
static getDerivedStateFromProps(props, state)
shouldComponentUpdate(nextProps, nextState)
render()
getSnapshotBeforeUpdate(prevProps, prevState)
componentDidUpdate(prevProps, prevState, snapshot)
- 卸载
(Unmounting)
componentWillUnmount()
- 错误处理
(Error Handling)
static getDerivedStateFromError(error)
componentDidCatch(error, info)
这是 React 组件生命周期函数的概览。随着 React 的版本迭代,某些生命周期函数可能会被更改或添加,因此建议查阅官方文档以获取最新信息。
setState
同步还是异步 (react <= 17 版本)
- 同步表现有:自定义的 DOM 事件, setState 是同步的;异步setTimeout中 setState同步
- 直接使用 setState 是异步表现
setState
所谓同步还是异步原理
setState
无所谓异步还是同步- 看是否命中
batchUpdate
机制 - 判断
isBatchingUpdates
setState
本质是同步
setState
是同步,只不过让React
做成了异步的样子- 因力要考虑性能,多次 state 修改,只进行一次 DOM 渲染
哪些能命中 batchUpdate
机制
- 生命周期(和它调用的函数)
- React 中注册的事件(和它调用的函数)
- React 可以”管理“的入口
哪些不能命中 batchUpdate
机制
setTimeout
setInterval
等(和它调用的函数)- 自定义的
DOM
事件(和它调用的函数) React
“管不到”的入口
总结:
setState
同步与异步的体现主要在于是否命中batchUpdate
机制,在 React入口注册的方法都将会命中batchUpdate
机制。非 React 入口的函数或者自定义事件,都不会命中batchUpdate
机制. 结论:如果setState
是在batchUpdate
机制中那么就是异步执行,否则就是同步执行
setState
(React 18)
React
组件事件:异步更新 + 合并state
DOM
事件,setTimeout
:异步更新 + 合并state
(react 18版版本后修改的)Automatic Batching
自动批处理
React <=17
与React18
总结:
React <= 17
:只有 React 组件事件才批处理React18
:所有事件都自动批处理 Automatic Batching
React18
:操作一致,更加简单,降低了用户的心智负担
事件event
evnet打印出的并非是对应事件的DOM
event
是SyntheticEvent
,模拟出来的 DOM 事件所有能力event.nativeEvent
是原生事件对象- 所有的事件都被挂载到
document
上react16
事件是绑定到document
react17
事件绑定到root
组件(有利于多个React版本并存,例如微前端)
- 和 dom 事件不一样,与vue事件也不一样(vue是直接挂载到对应事件的dom上)
SyntheticEvent
为何要用‘合成事件机制’
- 更好的兼容性和跨平台
- 转载到
root/document
上,减少内存消耗,避免频繁解绑 - 方便事件的统一管理(如事务机制)
Portals
使用场景:
- overflow:hidden(BFC)
- 父组件 z-index 值太小
- fixed 需要放到 body 第一层
使用代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return ReactDOM.createPortal(
<div>{this.props.children}</div>,
document.body // 要渲染的节点
)
}
}
state 不可变值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 不可变值(函数式编程,纯函数)- 数组
const list5copy = this.state.list5.slice()
list5Copy.splice(2,0,'a')// 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.List2, 1001], // 追加
list3: this.state.list3.slice(O, 3), // 截取
list4: this.state.list4.filter(item => item > 100) // 筛选
list5: list5Copy
})
// 注意:直接对 this.state.list 数据进行 push pop splice 等,这样违反不可变值的
// 不可变值 - 对象
this.setState({
obj1: object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
// 注意:不能直接 this.state.obj 进行属性设置,这样是违反不可变值
异步组件
import
React.lazy
React.Suspense
使用代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<p>引入一个动态组件</p>
<React.Suspense fallback={<div>loading</div>}>
<ContextDemo></ContextDemo>
</React.Suspense>
</div>
// 强制刷新,可看到 loding,异步加载组件加载过程会先有 loading 展示
)
}
}
高阶组件 HOC
使用示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
// 高阶组件不是一种功能,而是一种模式
const HOCFactory = (Component) => {
class HOC extends React.Component {
// 在此定义多个组件的公共逻辑
render() {
return <Component {...this.props}/>
}
}
return HOC
}
const EnhancedComponent1 = HOCFactory (WrappedComponent1)
const EnhancedComponent2 = HOCFactory (WrappedComponent2)
Render Props
- 核心思想:通过一个函数将
class
组件的state
作为props
传递给纯函数组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
clss Factoory extends React.Component {
constructor() {
this.state = {
/**多个组件公共逻辑的数据 */
}
}
render() {
return <div>{this.prop.render(this.state)}</div>
}
}
const App = () => {
/** render 是一个函数组件 */
<Factory render={
(props) => <p>{props.a} {props.b}</p>
}/>
}
HOC vs Render Props
- HOC:模式简单,但会增加组件层级
- Render Props:代码简洁,学习成本高
immutable.js
- 彻底拥抱不可变值
- 基于共享数据(不是深拷贝),速度好
- 有一定学习和迁移成本,按需使用
使用示例:
1
2
3
4
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set ('b', 50)
map1.get ('b') // 2
map2.get ('b') // 50
为什么需要SCU(shouldComponentUpdate)
- React 默认:父组件有更新,子组件无条件也更新
- 性能优化对于 React 很重要
SCU
一定要每次都用吗? - 需要时才优化
总结:
- 必须配合
“不可变值”
一起使用- 可先不用
SCU
,有性能问题时再考虑使用SCU
默认返回true,
即React
默认重新渲染所有的子组件
PureComponent 和 React.memo
PureComponent
,SCU
中实现了浅比较memo
, 函数组件中的PureComponent
- 浅比较已适用大部分情况(尽量不要做深比较)
PureComponent
使用示例:
1
2
3
4
5
6
7
8
9
10
class App extends React.PureComponent {
constructor(props) {
super(props)
}
// 组件使用 `PureComponent` 等同于组件自动注入 `shouldComponentUpdate`钩子函数进行的浅比较
// shouldComponentUpdate(nextProps, nextState) {
// /**浅比较 */
// }
}
React.memo
使用示例
1
2
3
4
5
6
7
8
9
10
function MyComponent(props) {
/** 使用props渲染 */
}
function areEqual(prevProps, nextProps) {
/**
* 如果把 nextProps 传入 render 方法的返回结果与将 prevProps 传入 render 方法的返回结果一致则返回 true,
* 否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
redux
- 单项数据流(流程)
- 1、
dispathc(action)
- 2、
reducer
->newState
- 3、
subscribe
触发通知
- 1、
redux 在组件中如何使用,connect
与消费组件进行连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 将 Redux state 映射到组件的 props
const mapStateToProps = (state: any) => {
console.log('结构', state);
return {
loginReducer: {
counter: state.loginReducer.counter
}
}
}
// 将 Redux actions 映射到组件的 props
const mapDispatchToProps = (dispatch: any) => ({
increment: () => dispatch({ type: 'counter/incremented' }),
decrement: () => dispatch({ type: 'counter/decremented' }),
});
/**
* 使用 connect 连接组件与 Redux store
* connect 是高阶组件,将 state dispatch 注入组件 props 中
* */
export default connect(mapStateToProps, mapDispatchToProps)(ClassRedux);
异步 action
使用异步 action 时要先在 createStore 时引入中间件,如下代码:
1
2
3
4
5
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '…/reducers/index';
// 创建 store时,作为中间传引入 redux-thunk
const store = createStore(rootReducer, applyMiddleware(thunk));
下面的是同步 action
与异步 action
的示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 同步 action
export const addTodo = text => {
// 返回 action 对象
return {
type: 'ADD_TODO',
id: nextTodoId++,
teext
}
}
// 异步 action
export const addTodoAsync = text => {
// 返回函数,其中有 dispatch 参数
return (dispatch) => {
// ajax 异步获取数据
fetch(url).then(res => {
// 执行异步 action
dispatch(addTodo(res.text))
})
}
}
异步 action 常用的中间件
- redux-thunk
- redux-promise
- redux-sage
Hooks
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useRef, useEffect } from 'react'
function App() {
const btnRef = useRef(null) // null 为初始值
useEffect(() => {
// 打印 button 的 dom 节点
console.log(btnRef.current)
}, [])
return (
<div>
<button ref={btnRef}>click</button>
</div>
)
}
export default App
useContext
useContext
在函数组件中,接收Context
传入的数据值
代码示例:
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
37
38
import React, { useContext } from 'react'
// 主题色
const themes = {
light: {
background: '#eee'
}
}
// 创建 Context
const ThemeContext = React.createContext(themes.light)
function ThemeButton() {
const theme = useContext(ThemeContext)
return (
<button style={{background: theme.background}}>
这是一个可以改变颜色的按钮
</button>
)
}
function Toolbar() {
return (
<>
<ThemeButton></ThemeButton>
</>
)
}
function App() {
return (
<ThemeContext.provider value={themes.light}>
<Toolbar></Toolbar>
</ThemeContext.provider>
)
}
export default App
useReducer
useReducer
是useState
的代替方案,用于state
复杂变化useReducer
是单个组件状态管理,组件通讯还需要props
(无法跨组件管理状态,与redux
不同)
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
import React, { useReducer } from 'react'
const initialState = { count: 0 }
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
}
}
function App() {
// useReducer 方法接受两个参数,第一参数:reducer方法,第二个参数:初始值 initialState
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
count: {state.count}
<button onclick={() => dispatch({type:'increment'})}>increment</button>
<button onclick={() => dispatch({type:'decrement'})}>decrement</button>
</>
)
}
export default App
useMemo
useMemo
数据缓存,如果依赖项没有变化返回缓存数据(属于 Hooks 性能优化)
使用代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, {useMemo, memo, useState} from "react";
function App() {
const [name, setName] = useState('饼干')
// useMemo 缓存数据, 接受两个参数 一个是回调方法,第二个参数是一个数组 依赖项
const userInfo = useMemo(() => {
return {name: '饼干', age: 18}
}, [name]) // 依赖项 name 一旦发生变化,将返回新的值
return (
<>
<div>名字:{userInfo.name} 年龄: {userInfo.age}</div>
</>
)
}
useCallback
useCallback
缓存函数useMemo
缓存数据,useCallback
缓存函数,两者都是React Hooks
的常见优化策略, 当父组件传递给子组件props
(state 和方法)时,父组件可以使用useCallback
方法缓存 和useMemo
数据缓存,避免子组件重新渲染减少方法重新创建。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, {useMemo, memo, useState, useCallback} from "react";
function App() {
const [name, setName] = useState('饼干')
// useMemo 缓存数据, 接受两个参数 一个是回调方法,第二个参数是一个数组 依赖项
const userInfo = useMemo(() => {
return {name: '饼干', age: 18}
}, [name]) // 依赖项 name 一旦发生变化,将返回新的值
// useCallback 缓存函数,连个个参数,第一个回调函数,第二个参数依懒项。 针对依赖项如何没有变化避免重新创建函数,提高性能
const onChange = useCallback(e => {
console.log(e.target.value)
}, [])
return (
<>
<Children onChange={onChange} userInfo={userInfo}></Children>
</>
)
}
useEffect 模拟生命周期
useEffect
让纯函数有了副作用, 函数组件在父组件加载和数据更新时都会直接重新渲染子组件,同时 useEffect
也会调用 useEEffect 默认相当于生命周期 componentDidMount
componentDidUpdate
这两个生命周期。
接下来使用 useEffect
模拟componentDidMount
componentDidUpdate
componentWillUnMount
这三个生命周期
useEffect 模拟 componentDidMount
- 模拟
componentDidMount
-useEffect
依赖[]
示例代码:1 2 3 4
// 模拟 class 组件的 DidMount useEffect(() => { console.log('加载完成') }, []) // 第二个参数是 [](不依赖于任何 state)
useEffect 模拟 componentDidUpdate
- 模拟
componentDidUpdate
-useEffect
无依赖,或者依赖[a, b]
1 2 3 4
// 模拟 class 组件的 DidUpdate useEffect(() => { console.log('数据更新') }, [a, b]) // 第二个参数是依赖的 state
useEffect 模拟 componentWillUnMount
- 模拟
componentWillUnMount
-useEffect
中返回一个函数
1
2
3
4
5
6
7
8
// 模拟 class 组件的 WillUnMount
useEffect(() => {
// 返回一个函数,函数体内处理组件销毁时执行的逻辑
return () => {
console.log('组件销毁')
/**组件销毁时要执行的代码逻辑 */
}
}, []) // 第二个参数是 [](不依赖于任何 state)
useEffect 模拟 componentWillUnMount 不完全等于 componentWillUnMount
useEffect
在重复执行渲染时,返回函数执行销毁逻辑,会在下一次useEffect
执行之前被执行,所以它模拟销毁状态时,并不完全等同于componentWillUnMount
执行机制。1 2 3 4 5 6 7 8
useEffect(() => { // 【特别注意】 此处并不完全等同于 WillUnMount // 准确的说:返回的函数,会在下一次 effect 执行之前被执行 return () => { console.log('组件销毁') /**组件销毁时要执行的代码逻辑 */ } })