一桩警、前言
那一天我二十一歲可训,在我一生的黃金時代,我有好多奢望捶枢。我想愛握截,想吃,還想在一瞬間變成天上半明半暗的云柱蟀,我覺得自己會永遠(yuǎn)生猛下去川蒙,什么也錘不了我。
<p align="right">《黃金時代》王小波</p>
二长已、關(guān)于Hooks
Hook 是 React 16.8 的新增特性畜眨。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性昼牛。Hook 的本質(zhì)還是 JavaScript 函數(shù),但是在使用它時需要遵循兩條規(guī)則
只在最頂層使用 Hook
不要在循環(huán)康聂,條件或嵌套函數(shù)中調(diào)用 Hook贰健,確保總是在你的 React 函數(shù)的最頂層以及任何 return 之前調(diào)用他們恬汁。
只在 React 函數(shù)中調(diào)用 Hook
不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook伶椿,你可以:
- 在 React 的函數(shù)組件中調(diào)用 Hook
- 在自定義 Hook 中調(diào)用其他 Hook
三、Hooks API
hooks的api有如下幾個氓侧,按照官方文檔劃分了一下脊另。
useState
這個算是最簡單的 hook 函數(shù)了,它的作用就是初始化一個值约巷,返回一個 和 initialState
相同的 state
偎痛,以及更新 state
的函數(shù) setState
。
const [state, setState] = useState(initialState);
我們來使用一下 useState
import React, { useState } from 'react';
const Index = () => {
// setNumber 更新number函數(shù)
const [number, setNumber] = useState(0);
return (
<>
<div>{number}</div>
<button onClick={() => setNumber(number + 1)}>Add Number</button>
<button onClick={() => setNumber(number - 1)}>Sub Number</button>
</>
);
};
export default Index;
我們可以看到 useState
初始化一個值 number
為 0 之后返回一個數(shù)組独郎,里面包含 number
本身 和 改變 number
值的函數(shù) setNumber
我們通過兩個按鈕來綁定 setNumber
來改變 number
踩麦。實現(xiàn) number
值的加一減一。
useEffect
useEffect
就是用來在函數(shù)組件中模擬類組件中的生命周期函數(shù)的氓癌。接受兩個參數(shù)谓谦,第一個參數(shù)就是一個箭頭函數(shù)。箭頭函數(shù)返回一個函數(shù)贪婉,這個函數(shù)就是組件卸載的時候觸發(fā)反粥。(這里舉個??:我們要監(jiān)聽頁面的 message
的時候,那么就在 render dom 的后面監(jiān)聽谓松,在 dom remove 的時候移除監(jiān)聽就行了)
關(guān)于第二個參數(shù)[deps]
- 沒有第二個參數(shù)的時候星压,當(dāng)
number
發(fā)生變化的時候践剂,組件會 remove 后 render鬼譬,有點像shouldComponentUpdate
生命周期。 - 當(dāng)?shù)诙€參數(shù)為 [] 的時候逊脯,只會執(zhí)行初始化的 render, 這個時候相當(dāng)于生命周期
componentDidMount
- 當(dāng)?shù)诙€參數(shù)不為空的時候优质,只有存在數(shù)組中的參數(shù)發(fā)生改變的時候,就會 remove 后 render军洼,就是更新組件巩螃。
import React, { useEffect, useState } from 'react';
const Index = () => {
// setNumber 更新number函數(shù)
const [number, setNumber] = useState(0);
useEffect(() => {
console.log('render dom');
return () => {
console.log('dom remove');
};
}, [number]);
return (
<>
<div>{number}</div>
<button onClick={() => setNumber(number + 1)}>Add Number</button>
<button onClick={() => setNumber(number - 1)}>Sub Number</button>
</>
);
};
export default Index;
useContext
useContext
接收一個 context 對象(React.createContext
的返回值)并返回該 context 的當(dāng)前值。當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider>
的 value prop 決定匕争。其實這里的 useContext
就和 context.Consumer
的效果差不多了避乏。這里提一下 context.Consumer
的用法
<MyContext.Consumer>
{value => {/* 基于 context 值進(jìn)行渲染 */}}
</MyContext.Consumer>
看個??,定義了一個主題對象甘桑,里面有兩個主題 light 和 dark拍皮,我們用 ThemeContext
接收一個context 對象, 使用 const theme = useContext(ThemeContext);
去訂閱它歹叮,而返回的 theme
值是距離當(dāng)前組件最近的 <ThemeContext.Provider value={themes.dark}>
的value prop。所以下面渲染的theme就是 dark主題铆帽。
import React, { useContext } from 'react';
const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
const ThemeContext = React.createContext();
function Index() {
return (
// 這里傳入light主題
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
export default Index;
useReducer
useReducer
useState 的替代方案,它接收一個形如 (state, action) => newState 的 reducer,(和 redux 一樣)在某些場景下咆耿,useReducer 會比 useState 更適用,例如 state 邏輯較復(fù)雜且包含多個子值以下是用 reducer 重寫 useState 一節(jié)的計數(shù)器示例爹橱。
import React, { useReducer } from 'react';
function reducer(state, action) {
console.log(state, action);
switch (action.type) {
case 'add':
return { count: state.count + 1 };
case 'sub':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Index() {
// return <h1>1</h1>;
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<h1>count:{state.count}</h1>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
<button onClick={() => dispatch({ type: 'sub' })}>-</button>
</>
);
}
export default Index;
useCallback
useCallback(fn, deps)
相當(dāng)于 useMemo(() => fn, deps)
萨螺。可以看出它是用來緩存函數(shù)的愧驱,下面是我mentor在reviewcode的時候提出的優(yōu)化建議 (標(biāo)注cr的)
const TreeDemo = () => {
// cr: 函數(shù)作為子組件的props時慰技,得考慮性能,用useCallback;
// const onSelect = (selectedKeys) => {
// console.log('selected', selectedKeys);
// };
// const onCheck = (checkedKeys) => {
// console.log('onCheck', checkedKeys);
// };
//reviewcode
const onSelect = useCallback(selectedKeys => {
console.log('selected', selectedKeys);
});
const onCheck = useCallback(checkedKeys => {
console.log('onCheck', checkedKeys);
});
// cr: 默認(rèn)值用狀態(tài)去控制组砚,如果有外部傳入的值惹盼,優(yōu)先使用外部的值
return (
<Tree
checkable
//默認(rèn)展開指定的樹節(jié)點
defaultExpandedKeys={['0-0', '0-0-0', '0-0-1']}
//默認(rèn)選中的樹節(jié)點
defaultSelectedKeys={['0-0']}
//默認(rèn)選中復(fù)選框的樹節(jié)點
defaultCheckedKeys={['0-0-1', '0-0-0-1']}
onSelect={onSelect}
onCheck={onCheck}
treeData={treeData}
/>
);
};
useMemo
useMemo
用來緩存數(shù)據(jù),實例如下 sum是個需要計算的參數(shù)惫确,當(dāng)我們沒使用memo做緩存的時候手报,在更新rank的時候也會重新計算 sum的值,這顯然是不合理的改化,我們只需要在更新count的時候才會去重新計算sum的值掩蛤。這種優(yōu)化有助于避免在每次渲染時都進(jìn)行高開銷的計算。
import React, { useMemo, useState } from 'react';
function Index() {
const [count, setCount] = useState(100);
const [rank, setRank] = useState(1);
let sum = useMemo(() => {
console.log('calculate');
let sum = 0;
for (let i = 0; i <= count; i++) {
sum += i;
}
return sum;
}, [count]);
return (
<>
<h1>{rank}</h1>
<button onClick={() => setRank(rank + 1)}>Add Rank</button>
<h5>{sum}</h5>
<button onClick={() => setCount(count + 1)}>Add Count</button>
</>
);
}
export default Index;
useRef
useRef 用來獲取元素DOM節(jié)點陈肛,這里還有其他的方法
- 直接在節(jié)點中獲取ref={node => this.node = node}
- 使用React.createRef()API
import React, { useRef } from 'react';
function Index() {
const node = useRef(null);
const getNode = () => {
// 打印節(jié)點
console.log(node.current);
};
return (
<div>
{/* 注意這里要綁定 */}
<h1 ref={node}>Node</h1>
<button onClick={() => getNode()}>Get Node</button>
</div>
);
}
export default Index;
useImperativeHandle
useImperativeHandle
可以讓你在使用 ref 時自定義暴露給父組件的實例值,下面我們看個父組件調(diào)用子組件的focus函數(shù)
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
function Input(props, ref) {
console.log(props, ref);
const inputRef = useRef(null);
useImperativeHandle(
ref,
() => {
const handleFn = {
focus() {
inputRef.current.focus();
},
};
return handleFn;
},
[]
);
return <input type='text' ref={inputRef} />;
}
const UseInput = forwardRef(Input);
function Index() {
const current = useRef(null);
// console.log(current);
const handleClick = () => {
console.log(current);
const { focus } = current.current;
focus();
};
return (
<div>
<UseInput ref={current} />
<button onClick={handleClick}>獲取焦點</button>
</div>
);
}
export default Index;
在上述的示例中揍鸟,React 會將 <UseInput ref={current}>
元素的 current 作為第二個參數(shù)傳遞給 React.forwardRef
函數(shù)中的渲染函數(shù) Input。Input會將 current 傳遞給 useImperativeHandle
的第一個參數(shù) ref句旱。注意:這里就沒將current傳遞給給<input>
了阳藻,而是用了inputRef
來取<input>
的DOM實例在useImperativeHandle
里面操作,我的理解就是相當(dāng)于官方不推薦讓我們直接操作 input
谈撒。 而是使用了 useImperativeHandle
來代理操作而已腥泥。由 useImperativeHandle
暴露出方法供我們使用。
useLayoutEffect
其函數(shù)簽名與 useEffect 相同啃匿,但它會在所有的 DOM 變更之后同步調(diào)用 effect蛔外。可以使用它來讀取 DOM 布局并同步觸發(fā)重渲染溯乒。在瀏覽器執(zhí)行繪制之前夹厌,useLayoutEffect 內(nèi)部的更新計劃將被同步刷新。也就是說useLayoutEffect回調(diào)執(zhí)行在瀏覽器繪制之前,也就是 useLayoutEffect
回調(diào)函數(shù)代碼可能會阻止瀏覽器渲染裆悄。
import React, { useEffect, useLayoutEffect, useState } from 'react';
function Index() {
const [color, setColor] = useState(0);
useLayoutEffect(() => {
console.log('render');
if (color === 0) {
setColor(color + 1);
}
}, [color]);
const colorStyle = {
color: color ? 'yellow' : 'red',
};
return (
<>
<div style={colorStyle}>color text {color}</div>
<button onClick={() => setColor(0)}>Click</button>
</>
);
}
export default Index;
從例子我們可以看到矛纹,當(dāng)點擊Click按鈕的時候,視圖會一直顯示黃色光稼,因為useLayoutEffect回調(diào)阻塞了瀏覽器的渲染或南。只有當(dāng)回調(diào)代碼執(zhí)行完了之后逻住,也就是setColor(color + 1)執(zhí)行后,瀏覽器才會渲染迎献,看到的視圖也就一直是黃色了瞎访,當(dāng)我們把useLayoutEffect換成useEffect的時候,我們快速點擊Click按鈕的時候就會出現(xiàn)閃爍現(xiàn)象吁恍,這是因為useEffect不會阻塞瀏覽器的渲染扒秸,點擊按鈕執(zhí)行 serColor(0)后,視圖就直接更新了變?yōu)榧t色冀瓦。然后useEffect回調(diào)執(zhí)行setColor(color + 1)之后伴奥,再變?yōu)辄S色,所以就會出現(xiàn)閃爍現(xiàn)象翼闽。
useDebugValue
useDebugValue(value)
useDebugValue
可用于在 React 開發(fā)者工具中顯示自定義 hook 的標(biāo)簽
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在開發(fā)者工具中的這個 Hook 旁邊顯示標(biāo)簽
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
提示
我們不推薦你向每個自定義 Hook 添加 debug 值拾徙。當(dāng)它作為共享庫的一部分時才最有價值。
useTransition
這個API處于試驗階段感局,等正式更新后再打算尼啡。