6.React Hook 概述(開發中遇到的問題與解決)


這里的所有內容均摘自網上優秀的文章,或再加工,只供自己學習用,如有侵權,請聯系。會在以后的使用過程中不斷補充,修改。

React Hook 概述

什么是 Hook:

  Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

這篇文章有什么:

  這里不過多闡述使用 Hook 的動機,網上都有,如果一定要用 Hook ,這片文章將收集,初次使用 Hook ,所需要知道的干貨。

Hook 知識點:

  State Hook | Effect Hook | useContext | useReducer | useCallback | useMemo | useRef | useHistory

 

State Hook

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 const [state, setState] = useState(initialState); 

useState:主要是為了給函數添加狀態的。最好(必須)在函數的最外層申明使用,不要在if或者else里面使用。

initialState:是 state 的初始值,不限制數據類型,不寫默認 undefined。

setState:是改變 state 的方法,類似於原來的 setState({state:false }),區別是沒有鈎子函數,也就是不能這樣- ->  setState({state:false }, () => { console.log(state) })  操作。 setState 函數用於更新 state,它接收一個新的 state 值並將組件的一次重新渲染加入隊列(注意:可以在 render 中使用 setState ,不會重復觸發 render)。

在后續的重新渲染中,useState 返回的第一個值將始終是更新后最新的 state。

 

Effect Hook

useEffect的作用是依賴變化的時候,執行函數(第一個參數),其中第二個參數為依賴。

  第二個參數的作用:

    雖然 useEffect 會在瀏覽器繪制后延遲執行,但會保證在任何新的渲染前執行。React 將在組件更新前刷新上一輪渲染的 effect。

    不寫“依賴”,可能會導致沒必要的刷新,甚至無限刷新。

    寫上第二個參數,effect 會監測“依賴”是否變化,當“依賴”變化時才會刷新。

    若依賴是一個空數組,effect 會判定依賴沒有更新,所以只會執行一次。

  effect 第一次被執行的生命周期:第一次 render(創建 DOM),執行 effect ,第二次 render,...

例子1:

function App() {
  const [width,setWidth] = useState(window.innerWidth)
useEffect(()
=>{ const handleResize = () => setWidth(window.innerWidth) window.addEventListener('resize',handleResize) return ()=>{ // 清除訂閱、定時器、等 window.removeEventListener('resize',handleResize) } }, [])
return ( <div> <p>{width}</p> </div> ) }

 

useContext

之前我們使用context (上下文)來解決多層嵌套傳props,分三步

  1. createContext創建Context
  2. 使用Context.Provider組件提供數據
  3. Context.Provider的所有后代組件,都可以通過Context.Consumer使用數據數據
const ColorContext = React.createContext('black')

function Button(props){
    return (
      <ColorContext.Consumer>
      {color=>
        <button style={{ background: color }}>
          {color,props.children}
        </button>}
      </ColorContext.Consumer>
    );
}

function MiddleWare(){
  return (
    <Button>我來了</Button>
  )
}

function App() {
  return (
    <div>
      <ColorContext.Provider value='yellow'> 
        <MiddleWare></MiddleWare>
      </ColorContext.Provider>
    </div>
  );
}

useContext 例子:

如果需要在組件之間共享狀態(比如兩個有共同父級的子組件之間),可以使用useContext()。

一層的數據傳遞,感覺沒有必用 useContext,如果是多層嵌套的話,父傳子的方式就顯得很不好了,此時應該用到它。

需要用到狀態的子組件:

import React, { useContext } from "react";
function
Button2(props){ const color = useContext(ColorContext) return <button style={{ background: color }}>{(color, props.children)}</button> }

提供狀態的父組件

import React from "react";
const TestContext= React.createContext({});
function
MiddleWare(props) { return <ColorContext.Provider value="yellow"> <Button2>很多個子組件的其中一個</Button2> </ColorContext.Provider> }

 

useReducer

讓我們來回憶一下 使用redux使用reducer

//1.首先創建一個store index.store.js
export default function configStore(){
    const store = createStore(rootReducer,applyMiddleware(...middlewares))
    return store
}

//2.引入store app.js
  render() {
    return (
      <Provider store={store}>
        <Index />
      </Provider>
    )
  }

//3.定義action和創建reducder index.action.js index.reducer.js
export const ADD = 'ADD'
export const DELETE = 'DELETE'
function todos(state = INITAL_STATE, action) {
  switch action.type{
    case ADD:{...}
    case DELETE:{...}
  }
}

//4.頁面中使用reducer  component.js
export default connect(mapStateToProps, mapDispatchToProps)(Component);

太復雜了有沒有,(使用dva可以簡化寫法)
而使用useReducer可以省略很多代碼:

//index.js
  const { state, dispatch } = useContext(reducerContext);
  return (
    <div className="App">
      <>
        Count: {state.count}
        <button
          onClick={() => dispatch({ type: "reset", payload: { count: 0 } })}
        >Reset</button>
        <button onClick={() => dispatch({ type: "increment" })}>+</button>
        <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        <reducerContext.Provider value={{ state, dispatch }}>
          <ChangeCount />
        </reducerContext.Provider>
      </>
    </div>
  );
不過 useReducer 不支持共享數據,推薦使用 redux-react-hook ,同樣是通過 context 實現的 redux
但是不知是否有像查看 store 的瀏覽器插件,或者 redux-logger 這樣的中間件幫助我們查看狀態的變化,redux 的生態還是更好一點的
 

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一個 memoized 回調函數。

把內聯回調函數及依賴項數組作為參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新。當你把回調函數傳遞給經過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。

useCallback(fn, deps) 相當於  useMemo(() => fn, deps)

注意:

依賴項數組不會作為參數傳給回調函數。雖然從概念上來說它表現為:所有回調函數中引用的值都應該出現在依賴項數組中。未來編譯器會更加智能,屆時自動創建數組將成為可能。

我們推薦啟用 eslint-plugin-react-hooks 中的 exhaustive-deps 規則。此規則會在添加錯誤依賴時發出警告並給出修復建議。

 

useMemo

 工作中的經驗:memo 可以提高性能,沒有上網查過具體信息。
使用:
import React, {memo, useMemo} from "react";

//...不清楚 useMemo 怎么用,以后補充

export default memo(xxxx) //導出的時候包一下

 

useRef

寫一個 useOutsideClick 函數

function useOutsideClick(ref, fnc) {
  useEffect(() => { const handleClickOutside = e => { if (e.target !== ref.current && !ref.current.contains(e.target)) { fnc(); } }; window.addEventListener("click", handleClickOutside); return () => { window.removeEventListener("click", handleClickOutside); }; }, [ref, fnc]); }

使用

function App() {
  const ref = useRef(null); useOutsideClick(ref, () => { console.log('OUTSIDE CLICKED'); }); return ( <div ref={ref} style={{ width: 200, height: 200, background: 'red', }} />  ); }

 

useHistory

react.js-Hooks 路由跳轉
useHistory 鈎子允許您訪問可能用於導航的歷史實例。
import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

 

開發中遇到的問題與解決

錯誤代碼:
import BtnGroup from "componten/BtnGroup/BtnGroup";

function Father() {
  const [defaultValue] = useState([
    {name: "全部", key: 0, isAll: true, disabled: false},
    {name: "中國人", key: 1, disabled: false},
    {name: "米國人", key: 2, disabled: false}
  ]);
  const click = () => {
    //...
  }
  return (
    <div>
      <BtnGroup>
        defaultValue={[defaultValue[0]]}
        handleClick={click}
      </BtnGroup>
    </div>
  );
}

export default memo(Father)

// 下面是子組件的部分代碼,我在子組件中使用 useEffect 監測 props.defaultValue
useEffect(() => {
  console.log("在這個做一些事,設置組件的初始狀態")
},[props.defaultValue])

細心的朋友可能發現了,在 Father 組件每次觸發 render 的時候,都會觸發子組件的 useEffect ,應為組件的 useEffect 監測了 props.defaultValue,

這時,又有人問了,我給子組件傳的 defaultValue 是不會改變的,如何觸發了 子組件的 useEffect ?

注意:我給子組件傳 defaultValue[0] 的時候,在外面有包了一層 [ ] , 結果是--> [defaultValue[0]] ,也就是說,每次觸發 Father 的 render,

      子組件的 defaultValue 都會拿到一個新地址的 [ ],同理,只要在 function Father() { } 的函數體內定義的 [ ] (對象) 的指針,都在狀態改變的時候指向新的作用域,

      從而被 子組件的 useEffect 監測到,觸發不必要的刷新。

解決:1. 不要監測 props.defaultValue,給一個[ ](空數組)。

   2. 在函數 function Father() { } 體外申明一個變量 defaultValue = [{name: "全部", key: 0, isAll: true, disabled: false}]

   3. 在函數 function Father() { } 體內用 useState 申明變量 const [defaultValue] = useState([{name: "全部", key: 0, isAll: true, disabled: false}])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

作者:趣談前端_徐小夕
鏈接:https://www.jianshu.com/p/16bef85ebd30
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
作者:春餅sama
鏈接:https://www.jianshu.com/p/7bc5eb7eaaac
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
useHistory


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM