useReducer
基础使用
作用: 让 React 管理多个相对关联的状态数据
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
| import { useReducer } from 'react'
function reducer(state, action) { switch (action.type) { case 'INC': return state + 1 case 'DEC': return state - 1 default: return state } }
function App() { const [state, dispatch] = useReducer(reducer, 0) return ( <> {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */} <button onClick={() => dispatch({ type: 'DEC' })}>-</button> {state} <button onClick={() => dispatch({ type: 'INC' })}>+</button> </> ) }
export default App
|
更新流程

分派action传参
做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数
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 { useReducer } from 'react'
function reducer(state, action) { console.log('reducer执行了') switch (action.type) { case 'INC': return state + 1 case 'DEC': return state - 1 case 'UPDATE': return state + action.payload default: return state } }
function App() { const [state, dispatch] = useReducer(reducer, 0) return ( <> {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */} <button onClick={() => dispatch({ type: 'DEC' })}>-</button> {state} <button onClick={() => dispatch({ type: 'INC' })}>+</button> <button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}> update to 100 </button> </> ) }
export default App
|
useMemo
作用:它在每次重新渲染的时候能够缓存计算的结果
看个场景
下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费
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 { useState } from 'react'
function factorialOf(n) { console.log('斐波那契函数执行了') return n <= 0 ? 1 : n * factorialOf(n - 1) }
function App() { const [count, setCount] = useState(0) const sumByCount = factorialOf(count)
const [num, setNum] = useState(0)
return ( <> {sum} <button onClick={() => setCount(count + 1)}>+count:{count}</button> <button onClick={() => setNum(num + 1)}>+num:{num}</button> </> ) }
export default App
|
useMemo缓存计算结果
思路: 只有count发生变化时才重新进行计算
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 { useMemo, useState } from 'react'
function fib (n) { console.log('计算函数执行了') if (n < 3) return 1 return fib(n - 2) + fib(n - 1) }
function App() { const [count, setCount] = useState(0) const sum = useMemo(() => { return fib(count) }, [count])
const [num, setNum] = useState(0)
return ( <> {sum} <button onClick={() => setCount(count + 1)}>+count:{count}</button> <button onClick={() => setNum(num + 1)}>+num:{num}</button> </> ) }
export default App
|
React.memo
作用:允许组件在props没有改变的情况下跳过重新渲染
组件默认的渲染机制
默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
import { useState } from 'react'
function Son() { console.log('子组件被重新渲染了') return <div>this is son</div> }
function App() { const [, forceUpdate] = useState() console.log('父组件重新渲染了') return ( <> <Son /> <button onClick={() => forceUpdate(Math.random())}>update</button> </> ) }
export default App
|
使用React.memo优化
机制:只有props发生变化时才重新渲染
下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { useState } from 'react'
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> })
function App() { const [, forceUpdate] = useState() console.log('父组件重新渲染了') return ( <> <MemoSon /> <button onClick={() => forceUpdate(Math.random())}>update</button> </> ) }
export default App
|
props变化重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { useState } from 'react'
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> })
function App() { console.log('父组件重新渲染了')
const [count, setCount] = useState(0) return ( <> <MemoSon count={count} /> <button onClick={() => setCount(count + 1)}>+{count}</button> </> ) }
export default App
|
props的比较机制
对于props的比较,进行的是‘浅比较’,底层使用 Object.is
进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React, { useState } from 'react'
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> })
function App() { const [list, setList] = useState([1, 2, 3]) return ( <> <MemoSon list={list} /> <button onClick={() => setList([1, 2, 3])}> {JSON.stringify(list)} </button> </> ) }
export default App
|
说明:虽然俩次的list状态都是 [1,2,3]
, 但是因为组件App俩次渲染生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染
自定义比较函数
如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
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
| import React, { useState } from 'react'
function arePropsEqual(oldProps, newProps) { console.log(oldProps, newProps) return ( oldProps.list.length === newProps.list.length && oldProps.list.every((oldItem, index) => { const newItem = newProps.list[index] console.log(newItem, oldItem) return oldItem === newItem }) ) }
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> }, arePropsEqual)
function App() { console.log('父组件重新渲染了') const [list, setList] = useState([1, 2, 3]) return ( <> <MemoSon list={list} /> <button onClick={() => setList([1, 2, 3])}> 内容一样{JSON.stringify(list)} </button> <button onClick={() => setList([4, 5, 6])}> 内容不一样{JSON.stringify(list)} </button> </> ) }
export default App
|
useCallback
看个场景
上一小节我们说到,当给子组件传递一个引用类型
prop的时候,即使我们使用了memo
函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback
缓存回调函数
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
|
import { memo, useState } from 'react'
const MemoSon = memo(function Son() { console.log('Son组件渲染了') return <div>this is son</div> })
function App() { const [, forceUpate] = useState() console.log('父组件重新渲染了') const onGetSonMessage = (message) => { console.log(message) }
return ( <div> <MemoSon onGetSonMessage={onGetSonMessage} /> <button onClick={() => forceUpate(Math.random())}>update</button> </div> ) }
export default App
|
useCallback缓存函数
useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
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
|
import { memo, useCallback, useState } from 'react'
const MemoSon = memo(function Son() { console.log('Son组件渲染了') return <div>this is son</div> })
function App() { const [, forceUpate] = useState() console.log('父组件重新渲染了') const onGetSonMessage = useCallback((message) => { console.log(message) }, [])
return ( <div> <MemoSon onGetSonMessage={onGetSonMessage} /> <button onClick={() => forceUpate(Math.random())}>update</button> </div> ) }
export default App
|
forwardRef
作用:允许组件使用ref将一个DOM节点暴露给父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { forwardRef, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) { return <input {...props} type="text" ref={ref} /> }, [])
function App() { const ref = useRef(null)
const focusHandle = () => { console.log(ref.current.focus()) }
return ( <div> <MyInput ref={ref} /> <button onClick={focusHandle}>focus</button> </div> ) }
export default App
|
useImperativeHandle
作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法
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
| import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) { const inputRef = useRef(null) const focus = () => inputRef.current.focus()
useImperativeHandle(ref, () => { return { focus, } })
return <input {...props} ref={inputRef} type="text" /> })
function App() { const ref = useRef(null)
const focusHandle = () => ref.current.focus()
return ( <div> <MyInput ref={ref} /> <button onClick={focusHandle}>focus</button> </div> ) }
export default App
|
Class API(旧版)
顾名思义,Class API就是使用ES6支持的原生Class API来编写React组件.新版本不推荐这种写法,旧版本可以了解下
基础体验
通过一个简单的 Counter 自增组件看一下组件的基础编写结构
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
| import { Component } from 'react'
class Counter extends Component { state = { count: 0, }
clickHandler = () => { this.setState({ count: this.state.count + 1, }) }
render() { return <button onClick={this.clickHandler}>+{this.state.count}</button> } }
function App() { return ( <div> <Counter /> </div> ) }
export default App
|
组件生命周期
组件通信
父传子
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
| import { Component } from 'react'
class Son extends Component { render() { const { count } = this.props return <div>this is Son, {count}</div> } }
class App extends Component { state = { count: 0, }
setCount = () => { this.setState({ count: this.state.count + 1, }) }
render() { return ( <> <Son count={this.state.count} /> <button onClick={this.setCount}>+</button> </> ) } }
export default App
|
子传父
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 { Component } from 'react'
class Son extends Component { render() { const { msg, onGetSonMsg } = this.props return ( <> <div>this is Son, {msg}</div> <button onClick={() => onGetSonMsg('this is son msg')}> changeMsg </button> </> ) } }
class App extends Component { state = { msg: 'this is initail app msg', }
onGetSonMsg = (msg) => { this.setState({ msg }) }
render() { return ( <> <Son msg={this.state.msg} onGetSonMsg={this.onGetSonMsg} /> </> ) } }
export default App
|
更多阅读
Component – React 中文文档