概念
- React Hook 是 React 16.8 的新增特性究抓。它可以讓你在不編寫(xiě) class 的情況下使用 state 以及其他的 React 特性缚陷;
- 以前在編寫(xiě)函數(shù)式組件,組件需要自己的 state 的時(shí)候贤重,通常我們會(huì)轉(zhuǎn)化成 class 組件來(lái)做〈》希現(xiàn)在可以在函數(shù)組件中使用 Hook 來(lái)實(shí)現(xiàn)冗茸;
解決的問(wèn)題
- 組件之間復(fù)用狀態(tài)邏輯很難,可能要用到 render props 和高階組件榴捡,React 需要為共享狀態(tài)邏輯提供更好的原生途徑杈女,Hook 可以在無(wú)需修改組件結(jié)構(gòu)的情況下復(fù)用狀態(tài)邏輯;
- 復(fù)雜組件難以理解吊圾,Hook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請(qǐng)求數(shù)據(jù))达椰;
- 難以理解的 class 以及捉摸不透的 this;
遵循的規(guī)則
- 只能在函數(shù)最外層調(diào)用 Hook项乒,不要在循環(huán)啰劲、條件判斷或者子函數(shù)中調(diào)用;
- 只能在 React 的函數(shù)組件中調(diào)用 Hook板丽,不要在其他 JavaScript 函數(shù)中調(diào)用呈枉;
Hooks API
下面開(kāi)始使用一下經(jīng)常用到的 Hooks。新建一個(gè)項(xiàng)目埃碱,用來(lái)寫(xiě)例子猖辫。
npx create-react-app react-hooks
cd react-hooks
yarn start
刪掉src下多余的文件,只保留 index.js砚殿。
useState
- 給函數(shù)組件添加內(nèi)部 state啃憎,React 會(huì)在重復(fù)渲染時(shí)保留這個(gè) state;
- useState 返回一對(duì)值:當(dāng)前狀態(tài)和更新?tīng)顟B(tài)的函數(shù)似炎,在事件處理函數(shù)中或其他地方調(diào)用這個(gè)函數(shù)辛萍。它類(lèi)似 class 組件的 this.setState,但是它不會(huì)將新舊 state進(jìn)行合并羡藐;
- 在初始渲染期間贩毕,返回的狀態(tài) (state) 與傳入的第一個(gè)參數(shù) (initialState) 值相同
setState 函數(shù)用于更新 state。它接收一個(gè)新的 state 值并將組件的一次重新渲染加入隊(duì)列
const [state, setState] = useState(initialState);
1:對(duì)比類(lèi)的寫(xiě)法和函數(shù)組件
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
class Counter1 extends React.Component {
constructor() {
super();
this.state = {
number: 0
}
}
add = () => {
const { number } = this.state
this.setState({
number: number + 1
})
}
render() {
const { number } = this.state
return (
<div className="counter">
<p>counter: {number}</p>
<button onClick={this.add}>add +</button>
</div>
)
}
}
function Counter() {
const [number, setNum] = useState(0)
return (
<div className="counter">
<p>counter: {number}</p>
<button onClick={() => setNum(number + 1)}>add +</button>
</div>
)
}
ReactDOM.render(
// <Counter />,
<Counter1 />,
document.getElementById('root')
);
2.每次的渲染都是獨(dú)立的閉包
- 每一次渲染都有它自己的 Props and State
- 每一次渲染都有它自己的事件處理函數(shù)
- alert會(huì)“捕獲”點(diǎn)擊按鈕時(shí)候的狀態(tài)
- 函數(shù)組件每次渲染都會(huì)被調(diào)用仆嗦,但是每一次調(diào)用中 number 值都是常量辉阶,并且它被賦予了當(dāng)前渲染中的狀態(tài)值
- 在單次渲染的范圍內(nèi),props和state始終保持不變
- 這個(gè)鏈接里面的案例making-setinterval-declarative-with-react-hooks可以拿出來(lái)試試。
function Counter2(){
const [number,setNumber] = useState(0);
function alertNumber(){
setTimeout(()=>{
alert(number);
},3000);
}
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
這里 alert 出來(lái)的是點(diǎn)擊時(shí)候的 number 值谆甜,如果一直點(diǎn)垃僚,并不是最新的值。
3.函數(shù)式更新
- 如果新的 state 需要通過(guò)使用先前的 state 計(jì)算得出规辱,那么可以將函數(shù)傳遞給 setState谆棺。該函數(shù)將接收先前的 state,并返回一個(gè)更新后的值罕袋。這樣每次拿到都是最新的狀態(tài)值改淑。
運(yùn)行如下代碼:
// 函數(shù)式更新
function Counter2(){
const [number, setNum] = useState(0);
const lazyAdd = () => {
setTimeout(() => {
setNum(number + 1)
}, 3000)
}
const lazyFunction = () => {
setTimeout(() => {
setNum(number => number + 1)
}, 3000);
}
return (
<div className="counter">
<p>counter: {number}</p>
<button onClick={() => setNum(number + 1)}>add +</button>
<button onClick={lazyAdd}>lazy add</button>
<button onClick={lazyFunction}>lazy function</button>
</div>
)
}
setState 更新?tīng)顟B(tài)的函數(shù)參數(shù)可以是一個(gè)函數(shù),返回新?tīng)顟B(tài):
setNum(number => number + 1)
每次都返回最新的狀態(tài)炫贤,然后再加1溅固。
4.惰性初始 state
- initialState 參數(shù)只會(huì)在組件的初始渲染中起作用,后續(xù)渲染時(shí)會(huì)被忽略
- 如果初始 state 需要通過(guò)復(fù)雜計(jì)算獲得兰珍,則可以傳入一個(gè)函數(shù)侍郭,在函數(shù)中計(jì)算并返回初始的 state,此函數(shù)只在初始渲染時(shí)被調(diào)用
- 與 class 組件中的 setState 方法不同掠河,useState 不會(huì)自動(dòng)合并更新對(duì)象亮元。可以用函數(shù)式的 setState 結(jié)合展開(kāi)運(yùn)算符來(lái)達(dá)到合并更新對(duì)象的效果
function Counter3(){
const [userInfo, setUserInfo] = useState(() => {
return {
name: 'mxcz',
age: 18
}
});
return (
<div className="counter">
<p>{userInfo.name}: {userInfo.age}</p>
<button onClick={() => setUserInfo({age: userInfo.age + 1})}>add +</button>
<button onClick={() => setUserInfo({...userInfo, age: userInfo.age + 1})}>更新要寫(xiě)完整</button>
</div>
)
}
5.性能優(yōu)化
5.1 Object.is()
- 調(diào)用 State Hook 的更新函數(shù)并傳入當(dāng)前的 state 時(shí)唠摹,React 將跳過(guò)子組件的渲染及 effect 的執(zhí)行爆捞。(React 使用 Object.is 比較算法 來(lái)比較 state。)
function Counter4(){
const [counter,setCounter] = useState({name:'計(jì)數(shù)器',number:0});
console.log('render Counter')
return (
<>
<p>{counter.name}:{counter.number}</p>
<button onClick={()=>setCounter({...counter,number:counter.number+1})}>+</button>
<button onClick={()=>setCounter(counter)}>-</button>
</>
)
}
增加數(shù)值之后勾拉,在減煮甥,不會(huì)引起組件的重新渲染,因?yàn)镺bject.is(Object.is()
方法判斷兩個(gè)值是否為同一個(gè)值藕赞。) 比較算法表示state沒(méi)有改變成肘。
5.2 減少渲染次數(shù)
- 把內(nèi)聯(lián)回調(diào)函數(shù)及依賴(lài)項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized (記憶)版本斧蜕,該回調(diào)函數(shù)僅在某個(gè)依賴(lài)項(xiàng)改變時(shí)才會(huì)更新双霍;
- 把創(chuàng)建函數(shù)和依賴(lài)項(xiàng)數(shù)組作為參數(shù)傳入 useMemo,它僅會(huì)在某個(gè)依賴(lài)項(xiàng)改變時(shí)才重新計(jì)算 memoized 值批销。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷(xiāo)的計(jì)算洒闸;
function Child({onButtonClick,data}){
console.log('Child render');
return (
<button onClick={onButtonClick} >{data.number}</button>
)
}
function App(){
const [number,setNumber] = useState(0);
const [name,setName] = useState('mxcz');
const addClick = () => setNumber(number+1)
const data = { number }
return (
<div>
<input type="text" value={name} onChange={e=>setName(e.target.value)}/>
<Child onButtonClick={addClick} data={data}/>
</div>
)
}
可以看到不優(yōu)化的情況下,點(diǎn)擊按鈕和改變輸入框的值都會(huì)引起子組件的重新渲染均芽,但是子組件依賴(lài)的數(shù)據(jù)只有數(shù)字改變而已丘逸;
現(xiàn)在來(lái)改造一下,給子組件加上
Child = memo(Child);
返回一個(gè)記憶組件掀宋,此時(shí)再點(diǎn)擊按鈕和改變輸入框深纲,依然會(huì)重新渲染子組件羞反,這里的原因是子組件調(diào)用了父組件傳遞來(lái)的會(huì)調(diào)函數(shù),這個(gè)函數(shù)在父組件渲染時(shí)囤萤,都會(huì)重新建立新的函數(shù)引用,下面來(lái)驗(yàn)證一下:
let oldClick;
function App(){
const [number,setNumber] = useState(0);
const [name,setName] = useState('mxcz');
const addClick = () => setNumber(number+1)
console.log('oldClick === addClick', oldClick === addClick)
oldClick = addClick
const data = { number }
return (
<div>
<input type="text" value={name} onChange={e=>setName(e.target.value)}/>
<Child onButtonClick={addClick} data={data}/>
</div>
)
}
每次渲染都重新返回了false是趴,表示每次都是新的函數(shù)涛舍,現(xiàn)在改造一下,
const addClick = useCallback(()=>setNumber(number+1),[number]);
可以看到唆途,給回調(diào)函數(shù)加上useCallback富雅,點(diǎn)擊按鈕每次都是false,說(shuō)明都是依賴(lài)的number改變了肛搬,函數(shù)是新的函數(shù)没佑,而改變輸入框的值,返回true温赔,說(shuō)明函數(shù)被緩存起來(lái)了蛤奢,并沒(méi)有重新創(chuàng)建函數(shù)。而此時(shí)chid組件依然被渲染了陶贼,因?yàn)閐ata 改變了啤贩,現(xiàn)在將data用useMemo包起來(lái)
const data = useMemo(()=>({number}),[number]);
,再來(lái)運(yùn)行一遍:此時(shí)的子組件在輸入框改變時(shí)并沒(méi)有被重新渲染“菅恚現(xiàn)在子組件不用memo包裝痹屹,可以看到子組件還是在輸入框改變值的時(shí)候被重新渲染了。
例子的完整代碼如下:
function Child({onButtonClick,data}){
console.log('Child render');
return (
<button onClick={onButtonClick} >{data.number}</button>
)
}
Child = memo(Child);
let oldClick;
function App(){
const [number,setNumber] = useState(0);
const [name,setName] = useState('mxcz');
const addClick = useCallback(()=>setNumber(number+1),[number]);
const data = useMemo(()=>({number}),[number]);
// const addClick = () => setNumber(number+1)
console.log('oldClick === addClick', oldClick === addClick)
oldClick = addClick
// const data = { number }
return (
<div>
<input type="text" value={name} onChange={e=>setName(e.target.value)}/>
<Child onButtonClick={addClick} data={data}/>
</div>
)
}
6.注意事項(xiàng)
- 只能在函數(shù)最外層調(diào)用 Hook枉氮。不要在循環(huán)志衍、條件判斷或者子函數(shù)中調(diào)用。
function App2() {
const [number, setNumber] = useState(0);
const [visible, setVisible] = useState(false);
if (number % 2 == 0) {
useEffect(() => {
setVisible(true);
}, [number]);
} else {
useEffect(() => {
setVisible(false);
}, [number]);
}
return (
<div>
<p>{number}</p>
<div>{visible && <div>visible</div>}</div>
<button onClick={() => setNumber(number + 1)}>+</button>
</div>
)
}
可以看到報(bào)錯(cuò)了:
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
報(bào)錯(cuò)說(shuō):useEffect 在條件語(yǔ)句中被調(diào)用聊替,在每次的組件渲染中楼肪,必須要以完全相同的順序調(diào)用 React Hooks。條件不同佃牛,每次渲染的順序不同淹辞,這就會(huì)亂了,應(yīng)該是跟鏈表的結(jié)構(gòu)相關(guān)吧俘侠,總之要遵循 React Hooks的使用原則象缀。
useReducer
- useState 的替代方案。它接收一個(gè)形如 (state, action) => newState 的 reducer爷速,并返回當(dāng)前的 state 以及與其配套的 dispatch 方法央星;
const [state, dispatch] = useReducer(reducer, initialArg, init);
- 在某些場(chǎng)景下,useReducer 會(huì)比 useState 更適用惫东,例如 state 邏輯較復(fù)雜且包含多個(gè)子值莉给,或者下一個(gè) state 依賴(lài)于之前的 state 等毙石;
useReducer 用法和 Redux 用法是一樣。
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function init(initialState){
return {number: initialState};
}
function App3(){
const [state, dispatch] = useReducer(reducer, initialState, init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
useContext
- 接收一個(gè) context 對(duì)象(React.createContext 的返回值)并返回該 context 的當(dāng)前值颓遏;
- 當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider> 的 value prop 決定徐矩;
- 當(dāng)組件上層最近的 <MyContext.Provider> 更新時(shí),該 Hook 會(huì)觸發(fā)重渲染叁幢,并使用最新傳遞給 MyContext provider 的 context value 值
- useContext(MyContext) 相當(dāng)于 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>
- useContext(MyContext) 只是更方便的讀取 context 的值以及訂閱 context 的變化滤灯。還是需要在上層組件樹(shù)中使用 <MyContext.Provider> 來(lái)為下層組件提供 context
const CounterContext = React.createContext();
function reducer2(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function Counter5(){
let {state,dispatch} = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
function App4(){
const [state, dispatch] = useReducer(reducer2, {number:0});
return (
<CounterContext.Provider value={{state,dispatch}}>
<Counter5 />
</CounterContext.Provider>
)
}
effect
- 在函數(shù)組件主體內(nèi)(即在 React 渲染階段)改變 DOM、添加訂閱曼玩、設(shè)置定時(shí)器鳞骤、記錄日志以及執(zhí)行其他包含副作用的操作都是不被允許的,因?yàn)檫@可能會(huì)產(chǎn)生莫名其妙的 bug 并破壞 UI 的一致性黍判;
- 使用 useEffect 完成副作用操作豫尽。賦值給 useEffect 的函數(shù)會(huì)在組件渲染到屏幕之后執(zhí)行;
- useEffect 就是一個(gè) Effect Hook顷帖,給函數(shù)組件增加了操作副作用的能力美旧。它跟 class 組件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途贬墩,只不過(guò)被合并成了一個(gè) API陈症; - 該 Hook 接收一個(gè)包含命令式、且可能有副作用代碼的函數(shù)
useEffect(didUpdate)
1.修改document的標(biāo)題震糖,class的實(shí)現(xiàn)方式
class Title extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
componentDidMount() {
document.title = `點(diǎn)擊了${this.state.number}次`;
}
componentDidUpdate() {
document.title = `點(diǎn)擊了${this.state.number}次`;
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
在這個(gè) class 中录肯,需要在兩個(gè)生命周期函數(shù)中編寫(xiě)重復(fù)的代碼,這是因?yàn)楹芏嗲闆r下,我們希望在組件加載和更新時(shí)執(zhí)行同樣的操作吊说。我們希望它在每次渲染之后執(zhí)行论咏,但 React 的 class 組件沒(méi)有提供這樣的方法。即使我們提取出一個(gè)方法颁井,我們還是要在兩個(gè)地方調(diào)用它厅贪。useEffect會(huì)在第一次渲染之后和每次更新之后都會(huì)執(zhí)行。
下面是函數(shù)組件雅宾,使用useEffect的方式:
function Title2(){
const [number,setNumber] = useState(0);
// 相當(dāng)于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
document.title = `你點(diǎn)擊了${number}次`;
});
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
</>
)
}
每次組件重新渲染养涮,都會(huì)生成新的 effect,替換掉之前的眉抬。某種意義上講贯吓,effect 更像是渲染結(jié)果的一部分 —— 每個(gè) effect 屬于一次特定的渲染。
2.跳過(guò) Effect 進(jìn)行性能優(yōu)化
- 如果某些特定值在兩次重渲染之間沒(méi)有發(fā)生變化蜀变,你可以通知 React 跳過(guò)對(duì) effect 的調(diào)用悄谐,只要傳遞數(shù)組作為 useEffect 的第二個(gè)可選參數(shù)即可
- 如果想執(zhí)行只運(yùn)行一次的 effect(僅在組件掛載和卸載時(shí)執(zhí)行),可以傳遞一個(gè)空數(shù)組(
[]
)作為第二個(gè)參數(shù)库北。這就告訴 React 這個(gè) effect 不依賴(lài)于 props 或 state 中的任何值爬舰,所以它永遠(yuǎn)都不需要重復(fù)執(zhí)行
function Counter6(){
const [number,setNumber] = useState(0);
useEffect(() => {
console.log('開(kāi)啟一個(gè)新的定時(shí)器')
const $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[]);
return (
<p>{number}</p>
)
}
3.清除副作用
- 副作用函數(shù)還可以通過(guò)返回一個(gè)函數(shù)來(lái)指定如何清除副作用
- 為防止內(nèi)存泄漏们陆,清除函數(shù)會(huì)在組件卸載前執(zhí)行。另外情屹,如果組件多次渲染坪仇,則在執(zhí)行下一個(gè) effect 之前,上一個(gè) effect 就已被清除
function Counter7() {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log('開(kāi)啟一個(gè)新的定時(shí)器')
const $timer = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
return () => {
console.log('銷(xiāo)毀老的定時(shí)器');
clearInterval($timer);
}
});
return (
<p>{number}</p>
)
}
function App5() {
let [visible, setVisible] = useState(true);
return (
<div>
{visible && <Counter7 />}
<button onClick={() => setVisible(false)}>stop</button>
</div>
)
}
useRef
- useRef 返回一個(gè)可變的 ref 對(duì)象垃你,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)
- 返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變
const refContainer = useRef(initialValue);
function Parent() {
let [number, setNumber] = useState(0);
return (
<>
<Child2 />
<button onClick={() => setNumber({ number: number + 1 })}>+</button>
</>
)
}
let input;
function Child2() {
const inputRef = useRef();
console.log('input===inputRef', input === inputRef);
input = inputRef;
function getFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={getFocus}>獲得焦點(diǎn)</button>
</>
)
}
forwardRef
- 將ref從父組件中轉(zhuǎn)發(fā)到子組件中的dom元素上
- 子組件接受 props 和 ref 作為參數(shù)
function Child3(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child3 = forwardRef(Child3);
function Parent2(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child3 ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>獲得焦點(diǎn)</button>
</>
)
}
useImperativeHandle
- useImperativeHandle 可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值
- 在大多數(shù)情況下烟很,應(yīng)當(dāng)避免使用 ref 這樣的命令式代碼。useImperativeHandle 應(yīng)當(dāng)與 forwardRef 一起使用
如下官網(wǎng)的很經(jīng)典的例子:
function Child4(props,ref){
const inputRef = useRef();
useImperativeHandle(ref,()=>(
{
focus(){
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef}/>
)
}
Child4 = forwardRef(Child4);
function Parent3(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child4 ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>獲得焦點(diǎn)</button>
</>
)
}
這樣父組件中只可以操作子組件暴露給父組件的方法蜡镶。
useLayoutEffect
- 其函數(shù)簽名與 useEffect 相同,但它會(huì)在所有的 DOM 變更之后同步調(diào)用 effect
- 可以使用它來(lái)讀取 DOM 布局并同步觸發(fā)重新渲染
- 在瀏覽器執(zhí)行繪制之前useLayoutEffect內(nèi)部的更新計(jì)劃將被同步刷新
- 盡可能使用標(biāo)準(zhǔn)的 useEffect 以避免阻塞視圖更新
useLayoutEffect 會(huì)在 useEffect 之前執(zhí)行恤筛。
function LayoutEffect() {
const [color, setColor] = useState('red');
useLayoutEffect(() => {
console.log(color);
});
useEffect(() => {
console.log('color', color);
});
return (
<>
<div id="myDiv" style={{ background: color }}>顏色</div>
<button onClick={() => setColor('red')}>紅</button>
<button onClick={() => setColor('yellow')}>黃</button>
<button onClick={() => setColor('blue')}>藍(lán)</button>
</>
);
}
自定義 Hook
- 有時(shí)候我們會(huì)想要在組件之間重用一些狀態(tài)邏輯官还;
- 自定義 Hook 可以讓你在不增加組件的情況下達(dá)到同樣的目的;
- Hook 是一種復(fù)用狀態(tài)邏輯的方式毒坛,它不復(fù)用 state 本身望伦;
- 事實(shí)上 Hook 的每次調(diào)用都有一個(gè)完全獨(dú)立的 state;
- 自定義 Hook 更像是一種約定煎殷,而不是一種功能屯伞。如果函數(shù)的名字以 use 開(kāi)頭,并且調(diào)用了其他的 Hook豪直,則就稱(chēng)其為一個(gè)自定義 Hook劣摇;
1.自定義一個(gè)計(jì)數(shù)器
function useNumber(initNumber){
const [number, setNumber] = useState(initNumber || 0)
useEffect(() => {
const $timer = setInterval(() => {
setNumber(number => number + 1)
}, 1000)
return () => {
clearInterval($timer)
}
}, [number])
return number
}
function App6(){
const number = useNumber(4)
return(
<p>{ number }</p>
)
}
2. 日志中間件
const initState = 0;
function countReducer(state, action) {
switch (action.type) {
case 'increment':
return { number: state.number + 1 };
case 'decrement':
return { number: state.number - 1 };
default:
throw new Error();
}
}
function initFun(initState) {
return { number: initState };
}
function useLogger(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchWithLogger = (action) => {
console.log('老狀態(tài)', state);
dispatch(action);
}
useEffect(function () {
console.log('新?tīng)顟B(tài)', state);
}, [state]);
return [state, dispatchWithLogger];
}
function Counter8() {
const [state, dispatch] = useLogger(countReducer, initState, initFun);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
)
}
路由hook
- useParams:獲取路由中的params
- useLocation:查看當(dāng)前路由
- useHistory:返回上一個(gè)路由
- useRouteMatch:嘗試以與Route相同的方式匹配當(dāng)前URL,在無(wú)需實(shí)際呈現(xiàn)Route的情況下訪問(wèn)匹配數(shù)據(jù)最有用
import { BrowserRouter as Router, Route, Switch, useParams, useLocation, useHistory } from "react-router-dom";
function Post() {
let { title } = useParams();
const location = useLocation();
let history = useHistory();
return <div>
{title}<hr />{JSON.stringify(location)}
<button type="button" onClick={() => history.goBack()}>回去</button>
</div>;
}
ReactDOM.render(
<Router>
<div>
<Switch>
<Route path="/post/:title" component={Post} />
</Switch>
</div>
</Router>,
document.getElementById("root")
);
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, useRouteMatch } from 'react-router-dom';
function NotFound() {
return <div>Not Found</div>
}
function Post(props) {
return (
<div>{props.match.params.title}</div>
)
}
function App() {
let match = useRouteMatch({
path: '/post/:title',
strict: true,
sensitive: true
})
console.log(match);
return (
<div>
{match ? <Post match={match} /> : <NotFound />}
</div>
)
}
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);