React Hooks 溫故知新

概念

  • 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 組件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途贬墩,只不過(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")
);

官方文檔:https://react.docschina.org/docs/hooks-intro.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弓乙,一起剝皮案震驚了整個(gè)濱河市末融,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暇韧,老刑警劉巖勾习,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異懈玻,居然都是意外死亡巧婶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)涂乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艺栈,“玉大人,你說(shuō)我怎么就攤上這事湾盒⊙勐耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵历涝,是天一觀的道長(zhǎng)诅需。 經(jīng)常有香客問(wèn)我漾唉,道長(zhǎng),這世上最難降的妖魔是什么堰塌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任赵刑,我火速辦了婚禮,結(jié)果婚禮上场刑,老公的妹妹穿的比我還像新娘般此。我一直安慰自己,他們只是感情好牵现,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布铐懊。 她就那樣靜靜地躺著,像睡著了一般瞎疼。 火紅的嫁衣襯著肌膚如雪科乎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天贼急,我揣著相機(jī)與錄音茅茂,去河邊找鬼。 笑死太抓,一個(gè)胖子當(dāng)著我的面吹牛空闲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播走敌,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼碴倾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了掉丽?” 一聲冷哼從身側(cè)響起影斑,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎机打,沒(méi)想到半個(gè)月后矫户,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡残邀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年皆辽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芥挣。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驱闷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出空免,到底是詐尸還是另有隱情空另,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布蹋砚,位于F島的核電站扼菠,受9級(jí)特大地震影響摄杂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜循榆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一析恢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秧饮,春花似錦映挂、人聲如沸完域。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)现诀。三九已至泼各,卻和暖如春鞍时,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背历恐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留专筷,地道東北人弱贼。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像磷蛹,于是被迫代替她去往敵國(guó)和親吮旅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容