ReactReact-hooks
WaterBoat什么是 hooks?
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
我们先来看一个例子,这是传统的 class 组件的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from "react";
class index extends React.component { state = { msg: "你好", }; updateMsg = () => { this.setState({ msg: "真好", }); }; render() { <div> <span>{this.state.msg}</span> <button onClick={this.updateMsg}>点击修改值</button> </div>; } } export default index;
|
将上面的例子转换成 hooks 的写法
1 2 3 4 5 6 7 8 9 10
| import { useState } from "react"; function index() { let [msg,setMsg] = useState("你好) return ( <div> <span>{msg}</span> <button onClick={()=>updateMsg("真好")}>点击修改值</button> </div> ); }
|
仔细感受一下异同
解决的问题
- 在组件之间复用状态逻辑很难,可能要用到 render props 和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
- 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- 难以理解的 class,包括难以捉摸的
this
注意事项
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
hooks 规则校验插件
eslint-plugin-react-hooks - npm (npmjs.com)
1 2 3 4 5 6 7 8 9 10 11 12
| { "plugins": [ "react-hooks" ], "rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" } }
|
useState 声明状态和设置状态
state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { useState } from "react";
function UseState() { let [count, setCount] = useState(0);
return ( <div className="use-state" style={{ border: "1px solid pink" }}> <h1>这里是 UseState 组件</h1> <div>{count}</div> <button onClick={() => setCount(count + 1)}>点击累加</button> </div> ); }
export default UseState;
|
useReducer 替代 useState
- useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
- 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
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 }; function reducer(state, action) { switch (action.type) { case "add": return { count: state.count + 1 }; case "sub": return { count: state.count - 1 }; default: return { ...state }; } } export default function UseReducer() { const [state, dispatch] = useReducer(reducer, initialState);
return ( <div> <h1>useReducer</h1> <span>{state.count}</span> <br /> <button onClick={() => dispatch({ type: "add" })}>+</button> <button onClick={() => dispatch({ type: "sub" })}>-</button> </div> ); }
|
惰性初始化
{17,1-3}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
| function init() { return initialState; } const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case "add": return { count: state.count + 1 }; case "sub": return { count: state.count - 1 }; default: return { ...state }; } } export default function UseReducer() { const [state, dispatch] = useReducer(reducer, initialState, init);
return ( <div> <h1>useReducer</h1> <span>{state.count}</span> <br /> <button onClick={() => dispatch({ type: "add" })}>+</button> <button onClick={() => dispatch({ type: "sub" })}>-</button> </div> ); }
|
useContext 跨越关系的传值
- 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
- 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
- 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
- useContext(MyContext) 相当于 class 组件中的
static contextType = MyContext
或者 <MyContext.Consumer>
- useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 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
| import React, { useContext } from "react";
const MsgContext = React.createContext("你好"); export default function UseContext() { return ( <div> <h1>useContext</h1> {/* 2 */} <MsgContext.Provider value={"你好"}> <Son></Son> </MsgContext.Provider> </div> ); }
function Son() { const context = useContext(MsgContext); return ( <div> <h1>子组件</h1> {/* 4 */} <span>{context}</span> </div> ); }
|
useEffect 模拟生命周期函数
跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途
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 { useState, useEffect } from "react";
const UseEffect = () => { let [count, setCount] = useState(0);
useEffect(() => { console.log("数据渲染了"); console.log("数据更新了"); return () => { console.log("组件卸载了"); }; }); return ( <div className="use-state" style={{ border: "1px solid pink" }}> <h1>这里是 UseEffect 组件</h1> <div>{count}</div> <button onClick={() => setCount(count + 1)}>点击累加</button> </div> ); };
export default UseEffect;
|
useEffect 会在每次渲染都执行.默认情况下,它在第一次渲染之后和每次更新之后都会执行。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
useEffect 监听 state 的变化
如同 vue 里面的 watch 一样 react hooks 也提供了监听变量变化的功能
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
| import { useState, useEffect } from "react";
const UseEffect = () => { let [count, setCount] = useState(0);
useEffect(() => { console.log("数据渲染了"); console.log("数据更新了"); return () => { console.log("组件卸载了"); }; }); useEffect(() => { console.log("count 更新了", count); }, [count]);
return ( <div className="use-state" style={{ border: "1px solid pink" }}> <h1>这里是 UseEffect 组件</h1> <div>{count}</div> <button onClick={() => setCount(count + 1)}>点击累加</button> </div> ); };
export default UseEffect;
|
useEffect 模拟 componentDidMount
1 2 3 4
| useEffect(() => { console.log("UseEffect.jsx 初始化"); }, []);
|
useEffect 模拟 componentDidUpdate
1 2 3 4 5 6
| useEffect(() => { console.log("UseEffect.jsx conut数据变化了"); }, [count]); useEffect(() => { console.log("UseEffect.jsx 数据变化了"); });
|
useEffect 模拟 componentWillUnmount
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 React, { useState, useEffect } from "react";
export default function UseEffect() { let [count, setCount] = useState(0); let [flag, setFlag] = useState(true);
return ( <div> <h1>useEffect</h1> <div>{count}</div> <button onClick={() => setCount(count + 1)}>点击累加</button> <button onClick={() => setFlag(!flag)}>显示/隐藏</button> {flag && <Son></Son>} </div> ); }
function Son() { useEffect(() => { return () => { console.log("useEffect.jsx 组件卸载了"); }; }, []); return ( <div> <h1>子组件</h1> </div> ); }
|
useLaoutEffect 在视图更新前执行
useLayoutEffect
和 useEffect
两个方法的作用和使用方式都是一样的,都是用来处理副作用代码的,它们之间唯一的区别就是回调函数的执行时机不同。
useEffect
在组件视图更新完成后执行,组件状态发生变化 -> 比较状态差异 -> 视图更新 -> useEffect
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
| import { useLayoutEffect, useRef, useState } from "react";
function LayoutEffect() { const [isShow, setIsShow] = useState(false); const divRef = useRef<HTMLDivElement>(null); useLayoutEffect(() => { if (!divRef.current) return; divRef.current.style.top = "100px"; }, [isShow]); return ( <> <h1>useLayoutEffect</h1> <button onClick={() => setIsShow(!isShow)}>button</button> {isShow ? ( <div ref={divRef} style={{ position: "relative" }}> useLayoutEffect </div> ) : null} </> ); } export default LayoutEffect;
|
useRef 获取 Dom 元素
- useRef 返回一个可变的 ref 对象,其
.current
属性被初始化为传入的参数(initialValue)
- 返回的 ref 对象在组件的整个生命周期内保持不变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { useRef } from "react";
export default function UseRec() { const h1Ref = useRef(); const test = () => { console.dir(h1Ref.current); h1Ref.current.innerText = "useRef 被修改了"; }; return ( <div> <h1 ref={h1Ref}>useRef</h1> <button onClick={test}>获取 h1</button> </div> ); }
|
ref 转发 让父元素可以使用子元素的 ref
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
| import React, { forwardRef, useRef } from "react"; const ForwardRef = () => { return ( <div> <h1>ForwardRef</h1> <Father></Father> </div> ); }; const Father = () => { let inputRef = useRef(); const changeInputValue = () => { inputRef.current.value = "Hello"; }; return ( <div> <button onClick={changeInputValue}>我是 Father </button> <Son ref={inputRef}></Son> </div> ); }; const Son = forwardRef((props, ref) => { const changeInputValue = () => { ref.current.value = "Hello"; }; return ( <div> <input type="text" ref={ref} /> <button onClick={changeInputValue}>我是 Son </button> </div> ); });
export default ForwardRef;
|
memo 优化组件
父组件更新引起了子组件的不必要更新,因为子组件本身是没有任何变化的是没有必要更新的。
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
| import { useEffect, useState, memo } from "react";
function ShowName({ name }: any) { useEffect(() => { console.log("ShowName rendered"); }); return <div>{name}</div>; }
function compare(prev: any, cur: any) { return prev.obj.sex === cur.obj.sex; }
const MemoShowName = memo(ShowName, compare);
function Memo() { const [index, setIndex] = useState(0); const [name] = useState("张三"); useEffect(() => { const timer = setInterval(() => { setIndex((prev) => prev + 1); }, 1000); return () => clearInterval(timer); }, []); return ( <> <h1>Memo</h1> <p>{index}</p> <MemoShowName name={name} obj={{ sex: "男" }} /> </> ); } export default Memo;
|
useMemo 对值进行缓存
通过 useMemo
方法可以对组件中的值进行缓存,就是说在每次组件重新渲染时都返回相同的值,也可以指定哪些状态发生改变时重新计算该值。
useMemo
有助于避免在每个渲染上进行昂贵的计算,提升组件性能。
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 { useEffect, useMemo, useState } from "react";
function App() { const [number, setNumber] = useState(0); const [dark, setDark] = useState(false); const double = useMemo(() => { return slowFunction(number); }, [number]); const styles = useMemo(() => { return { background: dark ? "black" : "white", color: dark ? "white" : "black", }; }, [dark]); useEffect(() => { console.log("styles"); }, [styles]); return ( <div> <input type="number" value={number} onChange={(event) => setNumber(event.target.value)} /> <div style={styles} onClick={() => setDark(!dark)}> {double} </div> </div> ); }
function slowFunction(n) { for (let i = 0; i < 1000000000; i++) {} return n * 2; }
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import { useCallback, useEffect, useState } from "react";
export default function UseCallBack() { const [number, setNumber] = useState(1); const [dark, setDark] = useState(false); const styles = { background: dark ? "black" : "white", color: dark ? "white" : "black", }; const getItems = useCallback(() => { return [number, number + 1, number + 2]; }, [number]); return ( <div style={styles}> <h1>useCallback</h1> <input type="number" value={number} onChange={() => setNumber((prev) => prev + 1)} /> <button onClick={() => setDark((dark) => !dark)}>button</button> <List getItems={getItems} /> </div> ); }
function List({ getItems }) { const [items, setItems] = useState([]);
useEffect(() => { setItems(getItems()); console.log("update items"); }, [getItems]);
return ( <div> {items.map((item) => ( <p key={item}>{item}</p> ))} </div> ); }
|
useImperativeHandle 子向父传值
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 { useRef, forwardRef, useImperativeHandle, useState } from "react";
let Message = forwardRef((props: any, ref: any) => { const [text, setText] = useState(""); const inputRef = useRef<HTMLInputElement>(null); useImperativeHandle(ref, () => { return { getText() { return text; }, input: inputRef.current, }; }); return ( <input ref={inputRef} type="text" value={text} onChange={(event) => setText(event.target.value)} /> ); }); export default function UseImperativeHandle() { const messageRef = useRef<any>(null); const onClickHandler = () => { console.log(messageRef.current.getText()); console.log(messageRef.current.input); }; return ( <> <h1>UseImperativeHandle</h1> <Message ref={messageRef}></Message> <button onClick={onClickHandler}>button</button> </> ); }
|
一些小技巧
手动更新组件
1 2 3 4
| import { useCallback, useState } from "react"; let [, updateState] = useState({}); const forceUpdate = useCallback(() => updateState({}), []); <button onClick={() => forceUpdate}>更新组件</button>;
|