4、useReducer处理更为复杂state结构
语法
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer 接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
我们可以使用 useReducer 来重新写我们开篇计数器的demo:
Example:
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(); } } export default () => { // 使用 useReducer 函数创建状态 state 以及更新状态的 dispatch 函数 const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <br /> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
优化:延迟初始化
还可以懒惰地创建初始状态。为此,您可以将init函数作为第三个参数传递。初始状态将设置为 init(initialArg)
。
它允许您提取用于计算 reducer
外部的初始状态的逻辑。这对于稍后重置状态以响应操作也很方便:
Example.js
import React, { useReducer } from 'react'; function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } export default ({initialCount = 0}) => { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <br /> <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
与 useState 的区别
- 当
state
状态值结构比较复杂时,使用useReducer
更有优势。 - 使用
useState
获取的setState
方法更新数据时是异步的;而使用useReducer
获取的dispatch
方法更新数据是同步的。
针对第二点区别,我们可以演示一下:在上面 useState
用法的例子中,我们新增一个 button
:
useState 中的 Example.js
import React, { useState } from 'react'; function Example() { // 声明一个名为“count”的新状态变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> <button onClick={() => { setCount(count + 1); setCount(count + 1); }}> 测试能否连加两次 </button> </div> ); } export default Example;
点击 测试能否连加两次 按钮,会发现,点击一次, count
还是只增加了 1,由此可见,useState
确实是 异步 更新数据;
在上面 useReducer
用法的例子中,我们新增一个 button
:useReducer 中的
Example.js
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(); } } export default () => { // 使用 useReducer 函数创建状态 state 以及更新状态的 dispatch 函数 const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <br /> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => { dispatch({type: 'increment'}); dispatch({type: 'increment'}); }}> 测试能否连加两次 </button> </> ); }
点击 测试能否连加两次 按钮,会发现,点击一次, count
增加了 2,由此可见,每次dispatch 一个 action 就会更新一次数据,useReducer
确实是 同步 更新数据;
对于 useReducer 和 useState的区别主要是以下两点:
- 当 state 状态值结构比较复杂时,使用 useReducer 更有优势。
- 使用 useState 获取的 setState 方法更新数据时是异步的;而使用 useReducer 获取的 dispatch 方法更新数据是同步的。
5、useMemo性能优化
语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个memoized值。传递“创建”函数和依赖项数组。useMemo只会在其中一个依赖项发生更改时重新计算memoized值。此优化有助于避免在每个渲染上进行昂贵的计算。
useMemo在渲染过程中传递的函数会运行。不要做那些在渲染时通常不会做的事情。例如,副作用属于useEffect,而不是useMemo。
用法
useMemo
可以帮助我们优化子组件的渲染,比如这种场景:在 A 组件中有两个子组件 B 和 C,当 A 组件中传给 B 的 props
发生变化时,A 组件状态会改变,重新渲染。此时 B 和 C 也都会重新渲染。其实这种情况是比较浪费资源的,现在我们就可以使用 useMemo
进行优化,B 组件用到的 props 变化时,只有 B 发生改变,而 C 却不会重新渲染。
例子:
ExampleA.js
import React from 'react'; export default ({ text }) => { console.log('Example A:', 'render'); return <div>Example A 组件:{ text }</div> }
ExampleB.js
import React from 'react'; export default ({ text }) => { console.log('Example B:', 'render'); return <div>Example B 组件:{ text }</div> }
App.js
import React, { useState } from 'react'; import ExampleA from './ExampleA'; import ExampleB from './ExampleB'; import './App.css'; export default () => { const [a, setA] = useState('ExampleA'); const [b, setB] = useState('ExampleB'); return ( <div> <ExampleA text={ a } /> <ExampleB text={ b } /> <br /> <button onClick={ () => setA('修改后的 ExampleA') }>修改传给 ExampleA 的属性</button> <button onClick={ () => setB('修改后的 ExampleB') }>修改传给 ExampleB 的属性</button> </div> ) }
此时我们点击上面任意一个按钮,都会看到控制台打印了两条输出, A 和 B 组件都会被重新渲染。
现在我们使用 useMemo
进行优化
App.js
import React, { useState, useMemo } from 'react'; import ExampleA from './ExampleA'; import ExampleB from './ExampleB'; import './App.css'; export default () => { const [a, setA] = useState('ExampleA'); const [b, setB] = useState('ExampleB'); + const exampleA = useMemo(() => <ExampleA />, [a]); + const exampleB = useMemo(() => <ExampleB />, [b]); return ( <div> + {/* <ExampleA text={ a } /> + <ExampleB text={ b } /> */} + { exampleA } + { exampleB } <br /> <button onClick={ () => setA('修改后的 ExampleA') }>修改传给 ExampleA 的属性</button> <button onClick={ () => setB('修改后的 ExampleB') }>修改传给 ExampleB 的属性</button> </div> ) }
此时我们点击不同的按钮,控制台都只会打印一条输出,改变 a 或者 b,A 和 B 组件都只有一个会重新渲染。
6、useCallback优化函数式组件性能
语法:
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
返回值 memoizedCallback
是一个 memoized
回调。传递内联回调和一系列依赖项。useCallback将返回一个回忆的memoized版本,该版本仅在其中一个依赖项发生更改时才会更改。当将回调传递给依赖于引用相等性的优化子组件以防止不必要的渲染(例如shouldComponentUpdate)时,这非常有用。
这个 Hook 的 API 不能够一两句解释的清楚,建议看一下这篇文章:useHooks 第一期:聊聊 hooks 中的 useCallback。里面介绍的比较详细。
7、useRef获取dom
语法:
const refContainer = useRef(initialValue);
类组件、React 元素用 React.createRef,如:remindRef: any = React.createRef();通过 this.remindRef.current获取
函数组件使用 useRef,如let globalToolRef: any = useRef(null);通过globalToolRef.current获取
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数(initialValue)。返回的对象将存留在整个组件的生命周期中。
- 从本质上讲,useRef就像一个“盒子”,可以在其.current财产中保持一个可变的价值。
- useRef() Hooks 不仅适用于 DOM 引用。“ref” 对象是一个通用容器,其 current 属性是可变的,可以保存任何值(可以是元素、对象、基本类型、甚至函数),类似于类上的实例属性。
注意:useRef() 比 ref 属性更有用。与在类中使用 instance(实例) 字段的方式类似,它可以 方便地保留任何可变值。
注意,内容更改时useRef 不会通知您。变异.current属性不会导致重新渲染。如果要在React将引用附加或分离到DOM节点时运行某些代码,则可能需要使用回调引用。
使用
下面这个例子中展示了可以在 useRef()
生成的 ref
的 current
中存入元素、字符串
Example.js
import React, { useRef, useState, useEffect } from 'react'; export default () => { // 使用 useRef 创建 inputEl const inputEl = useRef(null); const [text, updateText] = useState(''); // 使用 useRef 创建 textRef const textRef = useRef(); useEffect(() => { // 将 text 值存入 textRef.current 中 textRef.current = text; console.log('textRef.current:', textRef.current); }); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.value = "Hello, useRef"; }; return ( <> {/* 保存 input 的 ref 到 inputEl */} <input ref={ inputEl } type="text" /> <button onClick={ onButtonClick }>在 input 上展示文字</button> <br /> <br /> <input value={text} onChange={e => updateText(e.target.value)} /> </> ); }
点击 在 input 上展示文字 按钮,就可以看到第一个 input 上出现 Hello, useRef
;在第二个 input 中输入内容,可以看到控制台打印出对应的内容。