这是一篇关于本人 React Hooks
学习的总结文章,因为有一定 Vue 3
的编写经验,所以学起来不是那么吃力,下面一些总结仅代表个人看法。
React Hooks
对应 API
- useState
- useReducer
- useContext
- useEffect、useLayoutEffect
- useMemo、useCallback
- useRef、forwardRef、useImperativeHandle
- 自定义hook
- stale-closure
1.useState
1.1useState基础用法
用于变量声明,形式大致如下:
jsx1 2 3 4 5 6 7 8 9
| import React from 'react'
const [s, setS] = React.useState('') const [n, setN] = React.useState(0) const [b, setB] = React.useState(false)
const [user, setUser] = React.useState({ name: 'xxx' }) const [array, setArray] = React.useState([1, 2, 3])
|
1.2useState不可局部更新
如果state是一个对象,能否部分setState?答案是不行的,,演示如下 : useState-DEMO1
具体代码如下 :
jsx1 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 React, { useState } from "react"; import ReactDOM from "react-dom";
function App() { const [user, setUser] = useState({ name: 'Howard', age: 18 }) console.log(user) const onClick = () => { setUser({ name: 'John' }) } return ( <div className="App"> <h1>{user.name}</h1> <h2>{user.age}</h2> <button onClick={onClick}>Click</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
1.3useState地址需要变换
如果state是一个对象,setState(state)如果state的地址不变,react就会认为数据没有变换,因此页面不会重新渲染对应值,演示如下 : useState-DEMO2
具体代码如下 :
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, { useState } from "react"; import ReactDOM from "react-dom";
function App() { const [user, setUser] = useState({ name: 'Howard', age: 18 }) const onClick = () => { user.name = 'xxx' user.age = 11 console.log(user) setUser(user) } return ( <div className="App"> <h1>{user.name}</h1> <h2>{user.age}</h2> <button onClick={onClick}>Click</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
1.4useState可以接受函数
useState 是可以接受如下写法的,演示如下 : useState-DEMO3
具体代码如下 :
jsx1 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 React, { useState } from "react"; import ReactDOM from "react-dom";
const intial = { name: "Howard", age: 18 };
function App() { const [user, setUser] = useState(() => intial); const onClick = () => { setUser({ ...user, hobby: "play video game" }); }; return ( <div className="App"> <h1>name : {user.name}</h1> <h2>age : {user.age}</h2> <h2>hobby : {user.hobby || "click show result"}</h2> <button onClick={onClick}>Click</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
1.5**set
**State可以接受函数
如果想对同一个值操作两次,可以使用如下方法,演示如下 : setState-DEMO1
具体代码如下 :
jsx1 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, { useState } from "react"; import ReactDOM from "react-dom";
function App() { const [n, setN] = useState(0); const onClick = () => { setN(n + 1) setN(n + 1) }; return ( <div className="App"> <h1>n: {n}</h1> <button onClick={onClick}>+2</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
2.useReducer
总的来说,useReducer 是 useState 的复杂版,演示如下 : useReducer-DEMO1
具体代码如下 :
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 44 45 46 47 48 49 50
| import React from "react"; import ReactDOM from "react-dom";
const initial = { name: 'lzy', age: 18 };
const reducer = (state, action) => { switch (action.type) { case 'add': return { ...state, age: state.age + action.number }; case 'multi': return { ...state, age: state.age * action.number }; default: throw new Error('unknown type') } };
function App() { const [state, dispatch] = React.useReducer(reducer, initial); const { age, name } = state; const onClick = () => { dispatch({ type: "add", number: 1 }); }; const onClick2 = () => { dispatch({ type: "add", number: 2 }); }; const onClick3 = () => { dispatch({ type: "multi", number: 2 }); }; return ( <div className="App"> <h1>name: {name}</h1> <h1>age: {age}</h1> <button onClick={onClick}>+1</button> <button onClick={onClick2}>+2</button> <button onClick={onClick3}>*2</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
3.useContext
useContext 就是 上下文,注意:useContext 不是响应式的在一个模块将 Context内 的值改变,另一个模块不会感知变化。具体演示如下 : useContext-DEMO1
比较类似 vue 的 provide/inject ,通过 provide 一个对象/组件,在任意后代组件内部 inject 即可拿到provide对象的实例。
具体代码如下 :
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 44 45 46
| import React, { createContext, useState, useContext } from "react"; import ReactDOM from "react-dom";
const Context = createContext(null);
const App = () => { console.log("App 执行了"); const [n, setN] = useState(0); return ( <Context.Provider value={{ n, setN }}> <div className="App"> <Parent/> </div> </Context.Provider> ); }
const Parent = () => { const { n } = useContext(Context); return ( <div> 我是爸爸 n: {n} <Child/> </div> ); }
const Child = () => { const { n, setN } = useContext(Context); const onClick = () => { setN((i) => i + 1); }; return ( <div> 我是儿子 我得到的 n: {n} <button onClick={onClick}>+1</button> </div> ); }
const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement);
|
4.useEffect、useLayoutEffect
原来 React 的 Class 写法比较类似 Vue 的 Optional 写法,会将各个生命周期编写在Class组件内,而改用 Hook 写法后,可以用 useEffect 去模拟各个生命周期,useEffect的执行时机是在浏览器渲染完成之后,每次render后执行。
而 useLayoutEffect 则是在 浏览器渲染完成之前执行,就比较类似 Vue 的 beforeMount/onBeforeMount(Vue 3 setup中的钩子) 钩子。
4.1useEffect作为componentDidMount使用
1 2 3 4 5 6 7 8 9 10
| import React from "react";
const App = () => { const [x, setX] = React.useState(0) React.useEffect(() => { console.log('执行了') }, []) }
|
4.2useEffect作为componentDidUpdate使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from "react";
const App = () => { const [x, setX] = React.useState(0) React.useEffect(() => { console.log('执行了') }, [x]) return ( <div> {/*每次点击按钮 就会执行一次 useEffecrt*/} <button onClick={() => set(x + 1)}>+1</button> </div> ) }
|
4.3useEffect作为componentWillUnmount使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from "react";
const App = () => { const [x, setX] = React.useState(null) React.useEffect(() => { let n = setInterval(() => { console.log('hi') }, 1000) return () => { window.clearInterval(n) } }, []) return ( <div> {/*每次点击按钮 就会执行一次 useEffecrt*/} <button onClick={() => set(x + 1)}>+1</button> </div> ) }
|
总结 :
- 组件内存在多个
useEffect
的时候,是按照顺序执行的。
- 尽量避免使用
useLayoutEffect
,因为该 api 将渲染提前。
useLayoutEffect
总是比 useEffect
先执行。
useEffect
和 useLayoutEffect
中的 deps
如果是一个对象,对象的地址没变,是不会执行回调的。
5.useMemo、useCallback
useMemo
和 useCallback
的前置知识需要先理解 React.memo
,memo的作用是缓存一个组件,因为react默认有多余的render,在 props 不变的情况下,没有必要执行一个函数组件。
5.1未经过React.memo处理的demo
未经处理的demo,在点击1或2的时候,都会引起另一个child的re-render。具体演示如下 : memo、useCallback-DEMO1
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 { render } from "react-dom"; import { useState } from 'react'
const Child1 = props => { console.log('child1 渲染了') const { click, n } = props return ( <button onClick={click}>{n}</button> ) }
const Child2 = props => { console.log('child2 渲染了') const { click, m } = props return ( <button onClick={click}>{m}</button> ) }
const App = () => { const [n, setN] = useState(0) const [m, setM] = useState(0) const click1 = () => setN(n + 1) const click2 = () => setM(m + 1) return ( <div className="App"> <Child1 n={n} click={click1}/> <Child2 m={m} click={click2}/> </div> ); }
|
5.2经过React.memo搭配useCallback处理后的demo
经过 memo 处理后,在点击1或2的时候,仅仅只引起自身的的re-render。具体演示如下 : memo、useCallback-DEMO2
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 { render } from "react-dom"; import React, { useState } from "react";
const Child1 = React.memo((props) => { console.log("child1 渲染了"); const { click, n } = props; return <button onClick={click}>{n}</button>; });
const Child2 = React.memo((props) => { console.log("child2 渲染了"); const { click, m } = props; return <button onClick={click}>{m}</button>; });
const App = () => { const [n, setN] = useState(0); const [m, setM] = useState(0); // useCallback 对每个函数进行缓存 , 是useMemo的简化版 const click1 = React.useCallback(() => setN(n + 1), [n]); // useMemo 与 useCallback基本一致 , 只是函数返回一个函数 const click2 = React.useMemo(() => () => setM(m + 1), [m]); return ( <div className="App"> <Child1 n={n} click={click1}/> <Child2 m={m} click={click2}/> <div> {n},{m} </div> </div> ); };
|
总结:
- useMemo能做的事情更多,类似vue的 computed
功能,useMemo能接受两个参数,第一个参数是一个函数,并通过计算得出一个state,第二个参数是这个state的依赖,例如:const num = useMemo(()=> m + n,[m ,n])
- 一般情况下,现需要用 React.memo 对组件进行包裹,内部再搭配使用
useCallback
或useMemo
,才会生效。
- 在实际项目中,尽量减少使用这三个API,因为你并不知道优化后到底是提升了性能还是损失了性能,详见When to useMemo and useCallback,该篇博客仔细分析了何时使用
useMemo
和 useCallback
。
6.useRef、forwardRef、useImperativeHandle
7.自定义hook
8.stale-closure
__END__
文章作者:o0Chivas0o
文章出处:
React Hooks 学习&总结
作者签名:Rich ? DoSomethingLike() : DoSomethingNeed()
版权声明:文章除特别声明外,均采用
BY-NC-SA 许可协议,转载请注明出处