【React】957- 一文總結(jié) React Hooks 常用場景

作者:超級(jí)英雄|原文地址: https://juejin.cn/post/6918896729366462471
前言
文章雖然比較長,但是可以說是全網(wǎng)最全最有用的總結(jié)了,學(xué)會(huì)的記得分享、點(diǎn)贊、收藏、謝謝支持
React 在 v16.8 的版本中推出了 React Hooks 新特性。在我看來,使用 React Hooks 相比于從前的類組件有以下幾點(diǎn)好處:
代碼可讀性更強(qiáng),原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數(shù)中,容易使開發(fā)者不利于維護(hù)和迭代,通過 React Hooks 可以將功能代碼聚合,方便閱讀維護(hù);
組件樹層級(jí)變淺,在原本的代碼中,我們經(jīng)常使用 HOC/render props 等方式來復(fù)用組件的狀態(tài),增強(qiáng)功能等,無疑增加了組件樹層數(shù)及渲染,而在 React Hooks 中,這些功能都可以通過強(qiáng)大的自定義的 Hooks 來實(shí)現(xiàn);
一、State Hook
1、基礎(chǔ)用法
function State(){
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
2、更新
// 直接更新
setState(newCount);
// 函數(shù)式更新
setState(prevCount => prevCount - 1);
3、實(shí)現(xiàn)合并
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
4、惰性初始化 state
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
5、一些重點(diǎn)
(1)不像 class 中的 this.setState ,Hook 更新 state 變量總是替換它而不是合并它; (2)推薦使用多個(gè) state 變量,而不是單個(gè) state 變量,因?yàn)?state 的替換邏輯而不是合并邏輯,并且利于后續(xù)的相關(guān) state 邏輯抽離; (3)調(diào)用 State Hook 的更新函數(shù)并傳入當(dāng)前的 state 時(shí),React 將跳過子組件的渲染及 effect 的執(zhí)行。(React 使用 Object.is 比較算法 來比較 state。)
二、Effect Hook
1、基礎(chǔ)用法
function Effect(){
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
2、清除操作
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除訂閱
subscription.unsubscribe();
};
});
3、執(zhí)行時(shí)期
4、性能優(yōu)化
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時(shí)更新
5、模擬 componentDidMount
useEffect(() => {
.....
}, []);
6、最佳實(shí)踐
// bad,不推薦
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // ?? 這樣不安全(它調(diào)用的 `doSomething` 函數(shù)使用了 `someProp`)
}
// good,推薦
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ? 安全(我們的 effect 僅用到了 `someProp`)
}
// 1、安裝插件
npm i eslint-plugin-react-hooks --save-dev
// 2、eslint 配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
7、一些重點(diǎn)
(1)可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate和 componentWillUnmount這三個(gè)函數(shù)的組合; (2)在 React 的 class 組件中,render 函數(shù)是不應(yīng)該有任何副作用的;一般來說,在這里執(zhí)行操作太早了,我們基本上都希望在 React 更新 DOM 之后才執(zhí)行我們的操作。
三、useContext
import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ContextComponent1 from './ContextComponent1';
function ContextPage () {
const [count, setCount] = useState(1);
return (
<div className="App">
<ThemeContext.Provider value={count}>
<ContextComponent1 />
</ThemeContext.Provider>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default ContextPage;
import React from 'react';
import ContextComponent2 from './ContextComponent2';
function ContextComponent () {
return (
<ContextComponent2 />
);
}
export default ContextComponent;
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ContextComponent () {
const value = useContext(ThemeContext);
return (
<div>useContext:{value}</div>
);
}
export default ContextComponent;
四、useReducer
1、基礎(chǔ)用法
import React, { useReducer } from 'react';
interface stateType {
count: number
}
interface actionType {
type: string
}
const initialState = { count: 0 };
const reducer = (state:stateType, action:actionType) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer 減少</button>
<button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer 增加</button>
</div>
);
};
export default UseReducer;
2、惰性初始化 state
interface stateType {
count: number
}
interface actionType {
type: string,
paylod?: number
}
const initCount =0
const init = (initCount:number)=>{
return {count:initCount}
}
const reducer = (state:stateType, action:actionType)=>{
switch(action.type){
case 'increment':
return {count: state.count + 1}
case 'decrement':
return {count: state.count - 1}
case 'reset':
return init(action.paylod || 0)
default:
throw new Error();
}
}
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer,initCount,init)
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={()=>{dispatch({type:'decrement'})}}>useReducer 減少</button>
<button onClick={()=>{dispatch({type:'increment'})}}>useReducer 增加</button>
<button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer 增加</button>
</div>
);
}
export default UseReducer;
五、Memo
import React, { memo, useState } from 'react';
// 子組件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
// 父組件
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
<ChildComp/>
</div>
);
};
export default Parent;
// 子組件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
六、useMemo
import React, { memo, useState } from 'react';
// 子組件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父組件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = { name, age };
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
分析原因:
解決:
使用 useMemo 將對(duì)象屬性包一層,useMemo 有兩個(gè)參數(shù):
第一個(gè)參數(shù)是個(gè)函數(shù),返回的對(duì)象指向同一個(gè)引用,不會(huì)創(chuàng)建新對(duì)象; 第二個(gè)參數(shù)是個(gè)數(shù)組,只有數(shù)組中的變量改變時(shí),第一個(gè)參數(shù)的函數(shù)才會(huì)返回一個(gè)新的對(duì)象。
import React, { memo, useMemo, useState } from 'react';
// 子組件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父組件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
// 使用 useMemo 將對(duì)象屬性包一層
const info = useMemo(() => ({ name, age }), [name, age]);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
七 、useCallback
import React, { memo, useMemo, useState } from 'react';
// 子組件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父組件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = () => {
console.log('輸出名稱...');
};
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
分析下原因:
解決:
import React, { memo, useCallback, useMemo, useState } from 'react';
// 子組件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父組件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = useCallback(() => {
console.log('輸出名稱...');
}, []);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>點(diǎn)擊增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
八、useRef
1、指向 dom 元素
import React, { useRef, useEffect } from 'react';
const Page1 = () => {
const myRef = useRef<HTMLInputElement>(null);
useEffect(() => {
myRef?.current?.focus();
});
return (
<div>
<span>UseRef:</span>
<input ref={myRef} type="text"/>
</div>
);
};
export default Page1;
2、存放變量
import React, { useRef, useEffect, useState } from 'react';
const Page1 = () => {
const myRef2 = useRef(0);
const [count, setCount] = useState(0)
useEffect(()=>{
myRef2.current = count;
});
function handleClick(){
setTimeout(()=>{
console.log(count); // 3
console.log(myRef2.current); // 6
},3000)
}
return (
<div>
<div onClick={()=> setCount(count+1)}>點(diǎn)擊count</div>
<div onClick={()=> handleClick()}>查看</div>
</div>
);
}
export default Page1;
九、useImperativeHandle
十、useLayoutEffect
總結(jié)

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章
評(píng)論
圖片
表情
