react 16.8 以后加上了 react hook童本,它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性捏题。16.8之前卸伞,react組件可以分為類組件和函數(shù)組件撬腾。
- 函數(shù)組件一定是無狀態(tài)組件,展示型組件(渲染組件)一般是無狀態(tài)組件;
- 類組件既可以是有狀態(tài)組件脾拆,又可以是無狀態(tài)組件夜惭。類組件也可以叫容器組件,一般有交互邏輯和業(yè)務(wù)邏輯啸胧,而容器型組件一般是有狀態(tài)組件赶站。
我們?yōu)槭裁匆獡肀eact hook幔虏?由于類組件存在以下幾點(diǎn)問題:
- 組件變得復(fù)雜和難以維護(hù),業(yè)務(wù)變得復(fù)雜之后贝椿,組件之間共享狀態(tài)變得頻繁想括,此時組件將變得非常難以理解和維護(hù),復(fù)用狀態(tài)邏輯更是難上加難烙博。
- 滿天class導(dǎo)致的熱重載和性能問題瑟蜈,class自生具有的復(fù)雜度和組件嵌套過深props層級傳遞。
- 函數(shù)式組件沒有狀態(tài)
下面逐一介紹官方提供的hook API渣窜。
1.useState()
作用:返回一個狀態(tài)以及能修改這個狀態(tài)的setter铺根,在其他語言稱為元組(tuple),一旦mount之后只能通過這個setter修改這個狀態(tài)乔宿。
2.useEffect(callback, arr)
作用:處理函數(shù)組件中的副作用位迂,如異步操作、延遲操作等详瑞。useEffect有兩個參數(shù)掂林,callback和數(shù)組依賴項,無arr時相當(dāng)于componentDidMount生命周期坝橡,有arr時相當(dāng)componentDidMount和componentDidUpdata生命周期泻帮。如果callback中有return,則相當(dāng)于componentWillUnmount驳庭。
3.useContext
作用:跨組件共享數(shù)據(jù)鉤子刑顺,使用可分為三步:
- 首先使用React.createContext API創(chuàng)建Context,由于支持在組件外部調(diào)用饲常,因此可以實現(xiàn)狀態(tài)共享
export const MyContext = React.createContext(null);
- 使用Context.Provider API在上層組件掛載狀態(tài)
<MyContext.Provider value={value}>
<childComponent />
</MyContext.Provider>
- 獲取上層組件中距離當(dāng)前組件最近的<MyContext.Provider> 的 value
const value = useContext(MyContext) // MyContext 為 context 對象(React.createContext 的返回值)
useContext和傳統(tǒng)的props傳參分別適用于那些場景
useContext的應(yīng)用場景:
1.全局狀態(tài)的定義蹲堂,即可以被不同層級的組件所需要。
2.多個組件之間傳參(他們之間可能是跨多層級即祖孫關(guān)系傳參)時贝淤。
傳統(tǒng)props的應(yīng)用場景:
如果普通的父子組件之間傳參柒竞,即父子組只有單純的一層時,用props傳參更省事
4.useReducer
語法:const [state, dispatch] = useReducer(reducer, initialArg, init);
作用:用于管理復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(useState一般用于管理扁平結(jié)構(gòu)的狀態(tài))播聪,基本實現(xiàn)了redux的核心功能朽基。useState
的替代方案。它接收一個形如 (state, action) => newState
的 reducer离陶,并返回當(dāng)前的 state
以及與其配套的 dispatch
方法稼虎。
const initialState = {count: 0};
const reducer = (state, action)=> {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
5.useMemo、useCallback
這倆個Api與性能優(yōu)化有關(guān)招刨。react中霎俩,性能的優(yōu)化點(diǎn)在于:
- 調(diào)用setState,就會觸發(fā)組件的重新渲染,無論前后的state是否不同
- 父組件更新打却,子組件也會自動的更新
基于上面的兩點(diǎn)杉适,我們通常的解決方案是:使用immutable進(jìn)行比較,在不相等的時候調(diào)用setState柳击;在shouldComponentUpdate中判斷前后的props和state猿推,如果沒有變化,則返回false來阻止更新捌肴。
在hooks出來之后蹬叭,我們能夠使用function的形式來創(chuàng)建包含內(nèi)部state的組件。但是哭靖,使用function的形式具垫,失去了上面的shouldComponentUpdate,我們無法通過判斷前后狀態(tài)來決定是否更新试幽。而且筝蚕,在函數(shù)組件中,react不再區(qū)分mount和update兩個狀態(tài)铺坞,這意味著函數(shù)組件的每一次調(diào)用都會執(zhí)行其內(nèi)部的所有邏輯起宽,那么會帶來較大的性能損耗。因此useMemo 和useCallback就是解決性能問題的殺手锏济榨。
useCallback和useMemo的參數(shù)跟useEffect一致坯沪。useMemo和useCallback都會在組件第一次渲染的時候執(zhí)行,之后會在其依賴的變量發(fā)生改變時再次執(zhí)行擒滑;并且這兩個hooks都返回緩存的值腐晾,useMemo返回緩存的變量,useCallback返回緩存的函數(shù)丐一。
語法:
useMemo:const A = useCallback(fnB, [a])調(diào)用fnB函數(shù)并返回其結(jié)果
useCallback:const fnA = useCallback(fnB, [a])返回fnB函數(shù)
通過一個例子來看useMemo的作用:
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = () => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return <>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</>;
不使用useMemo藻糖,無論count還是val改變都會執(zhí)行expensive()
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count])
return <>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</>;
使用useMemo,只有在count發(fā)生改變使才會會執(zhí)行expensive()
useCallback跟useMemo比較類似库车,但是使用場景不同:比如有一個父組件巨柒,其中包含子組件,子組件接收一個函數(shù)作為props柠衍;通常而言洋满,如果父組件更新了,子組件也會執(zhí)行更新珍坊;但是大多數(shù)場景下牺勾,更新是沒有必要的,我們可以借助useCallback來返回函數(shù)阵漏,然后把這個函數(shù)作為props傳遞給子組件禽最;這樣腺怯,子組件就能避免不必要的更新袱饭。
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
console.log('callback執(zhí)行');
return count;
}, [count]);
return <>
<h4>{count}</h4>
<Child callback={callback} />
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)} />
</div>
</>
const Child = (props: any) => {
const [count, setCount] = useState(() => props.callback());
useEffect(() => {
setCount(props.callback());
}, [props.callback]);
return <div>{count}</div>;
};
6.useRef
語法:const refContainer = useRef(initialValue);
useRef是一個方法川无,返回一個可變的 ref 對象;其 .current 屬性被初始化為傳入的參數(shù)(initialValue)虑乖;可以保存任何類型的值:dom懦趋、對象等任何可變值;返回的 ref 對象在組件的整個生命周期內(nèi)保持不變疹味;修改 ref 的值是不會引發(fā)組件的重新 render 仅叫。
useRef非常常用的一個操作,訪問DOM節(jié)點(diǎn)糙捺,對DOM進(jìn)行操作诫咱,監(jiān)聽事件等等,如下:
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
除了傳統(tǒng)的用法之外,它還可以“跨渲染周期”保存數(shù)據(jù)洪灯。
const likeRef = useRef(1);
const [like, setLike] = useState(1);
const onButtonClick = () => {
setTimeout(() => {
console.log(likeRef.current); //11
console.log(like); // 1
}, 2000);
};
return (
<>
<button
onClick={() => {
likeRef.current++;
setLike(like + 1);
}}>
{like}
</button>
<button onClick={onButtonClick}>打印like</button>
</>
)
在上面的例子中坎缭,點(diǎn)擊了打印like
按鈕后,連續(xù)點(diǎn)擊數(shù)字按鈕签钩,會發(fā)現(xiàn)2s后likeRef.current
打印出11掏呼,而like
打印出1。
因為铅檩,在任意一次渲染中憎夷,props和 state 是始終保持不變的,如果props和state在任意不同渲染中是相互獨(dú)立的話昧旨,那么使用到他們的任何值也是獨(dú)立的拾给。所以onButtonClick時拿到的時未點(diǎn)擊數(shù)字按鈕時的like
值。
而ref 在所有 render 都保持著唯一的引用兔沃,因此所有的對 ref 的賦值或者取值拿到的都是一個最終的狀態(tài)蒋得,而不會存在隔離。
7.useImperativeHandle
語法:useImperativeHandle(ref, createHandle, [deps])
當(dāng)userRef用于一個組件時粘拾,useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值窄锅;如果不使用,父組件的ref(chidlRef)訪問不到任何值(childRef.current==null)缰雇;且useImperativeHandle
應(yīng)當(dāng)與 forwardRef
一起使用入偷。
const RefDemo= ()=>{
const childRef = useRef<any>(null);
const onButtonClick = () => {
childRef.current?.say();
};
return (
<div>
<Child ref={childRef} />
<button onClick={onButtonClick}>say123</button>
</div>
)
};
export default RefDemo;
const Child = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
say: () => {
console.log('123');
},
}));
return (
<>
<h4>子組件</h4>
</>
);
});
- React.forwardRef會創(chuàng)建一個React組件,這個組件能夠?qū)⑵浣邮艿膔ef屬性轉(zhuǎn)發(fā)到其組件樹下的另一個組件中械哟。
- React.forwardRef接受渲染函數(shù)作為參數(shù)疏之,React將使用prop和ref作為參數(shù)來調(diào)用此函數(shù)。
8.useLayoutEffect
用法與useEffect 相同暇咆,但它會在所有的 DOM 變更之后同步調(diào)用(立即執(zhí)行)锋爪”铮可以使用它來讀取 DOM 布局并同步觸發(fā)重渲染。在瀏覽器執(zhí)行繪制之前其骄,useLayoutEffect 內(nèi)部的更新計劃將被同步刷新亏镰。由于會在瀏覽器進(jìn)行任何繪制之前運(yùn)行完成,阻塞了瀏覽器的繪制拯爽。
使用場景:當(dāng)useEffect里面的操作需要處理DOM,并且會改變頁面的樣式,就需要用這個,否則可能會出現(xiàn)閃屏問題索抓。
9.useDebugValue
useDebugValue 用于在 React 開發(fā)者工具中顯示 自定義 Hook 的標(biāo)簽。
useDebugValue 接受一個格式化函數(shù)作為可選的第二個參數(shù)毯炮。該函數(shù)只有在 Hook 被檢查時才會被調(diào)用逼肯。它接受 debug 值作為參數(shù),并且會返回一個格式化的顯示值桃煎。