乾貨滿滿-原來這才是hooks-React Hooks使用心得

序言

---最後有招聘資訊哦~

React是一個庫,它不是一個框架。用於構建用戶介面的Javascript庫。這裡大家需要認識這一點。react的核心在於它僅僅是考慮了如何將dom節點更快更好更合適的渲染到瀏覽器中。它本身提供的涉及框架的理念是不多的。class組件是如此,hooks組件也是如此。

ClassComponent

我們先回顧一下,這是一個react的class組件:

class HelloMessage extends React.Component {
  constructor (props) {
      super(props)
      this.state = {
          num: 1
      }
  }
  componentDidMount () {
      alert('mounted!')
  }
  addNum () {
      this.setState({
          num: this.state.num + 1;
      })
  }
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);

 

它包含了:

  • props
  • state
  • setState
  • render
  • 生命周期

再來看看class組件中比較特殊的,具有代表意義的東西:

1、setState

通過對象更新的方式更新組件狀態,react通過diff演算法自動更新dom

2、Ref

一個可以用來訪問dom對象的東西

3、this

classComponent中通過this訪問各種數據/方法

4、生命周期

 

 

 

 

class組件存在的問題

* 邏輯分散
* 數據分散
* 組件拆分繁瑣,state樹往往較大

我們構建React組件的方式與組件的生命周期是耦合的。這一鴻溝順理成章的迫使整個組件中散布著相關的邏輯。在下面的示例中,我們可以清楚地了解到這一點。有三個的方法componentDidMount、componentDidUpdate和updateRepos來完成相同的任務——使repos與任何props.id同步。

 

componentDidMount () {
    this.updateRepos(this.props.id)
 }
 componentDidUpdate (prevProps) {
    if (prevProps.id !== this.props.id) {
      this.updateRepos(this.props.id)
    }
 }
 updateRepos = (id) => {
    this.setState({ loading: true })
    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false
      }))
  }

 

為了解決這個問題(它是一系列問題的其中之一,這裡只簡單舉例),react創造了一個全新的方式來解決,那就是hooks。

 

FunctionalComponent

hooks的目的是讓大家在一定程度上 「使用函數的想法去開發組件」 。
純函數組件、高階組件
先粗淺理解函數式

主要思想是把運算過程盡量寫成一系列嵌套的函數調用。舉例來說,現在有這樣一個數學表達式:

(1 + 2) * 3 - 4

 

傳統的過程式編程,可能這樣寫:

 

var a = 1 + 2;

var b = a * 3;

var c = b - 4;

 

函數式編程要求使用函數,我們可以把運算過程定義為不同的函數,然後寫成下面這樣:

 

var result = subtract(multiply(add(1,2), 3), 4);

 

函數式的特點

  1. 函數是”第一等公民”

  2. 只用”表達式”,不用”語句”

“表達式”(expression)是一個單純的運算過程,總是有返回值;”語句”(statement)是執行某種操作,沒有返回值。函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,而且都有返回值。

原因是函數式編程的開發動機,一開始就是為了處理運算(computation),不考慮系統的讀寫(I/O)。”語句”屬於對系統的讀寫操作,所以就被排斥在外。

當然,實際應用中,不做I/O是不可能的。因此,編程過程中,函數式編程只要求把I/O限制到最小,不要有不必要的讀寫行為,保持計算過程的單純性。

純函數組件
FunctionComponent:

滿足「不修改狀態」, 「引用透明」 ,「組件一等公民」, 「沒有副作用」, 「表達式

 

 

 

 

 

 

高階組件
higherOrderComponent(高階組件):

傳入一個組件,返回另一個包裝組件,向其注入數據、邏輯,達到拆分組件的目的

 

 

 

import * as React from 'react';

export default function () {
    return (
        ComponentA({ Com: ComponentB })
    )
}

function ComponentA (
    { Com }: { Com: React.FunctionComponent<any> }
) {
    return (
        <div>
            <Com style={{ color: 'red' }} />
        </div>
    )
}

function ComponentB ({ style }: {
    style: any
}) {
    return (
        <div style={style}>hello high order component</div>
    )
}

 

 

Hooks與實踐

Hooks你可以理解為具備class組件功能的函數式組件——至少它的理想是函數式

HooksAPI-今天的重點不在這裡,這裡不去細細講它

1、useState

2、useReducer

3、useEffect

4、useLayoutEffect

5、useMemo

6、useCallback

7、useContext

8、useRef

 

Hooks基本規則
1、只在最頂層使用 Hook(why? 後文提到)

不要在循環,條件或嵌套函數中調用 Hook, 確保總是在你的 React 函數的最頂層調用他們。遵守這條規則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調用。這讓 React 能夠在多次的 useState 和 useEffect 調用之間保持 hook 狀態的正確。(如果你對此感到好奇,在下面會有更深入的解釋。)

2、只在 React 函數中調用 Hook
3、嚴格控制函數的行數(相信我,超過500行的函數,任何人都會頭禿)
hooks生命周期對應

 

export default function HooksComponent () {
    const [ date, setDate ] = React.useState(new Date());
    const div = React.useRef(null);

    const tick = React.useCallback(() => {
        setDate(new Date());
    }, [])

    const color = React.useCallback(() => {
        if (div.current) {
            if (div.current.style.cssText.indexOf('red') > -1) {
                div.current.style.cssText = 'background: green;'
            } else {
                div.current.style.cssText = 'background: red;'
            }
        }
    }, [ div ]);

    const timerID = React.useMemo(() => {
        console.log('==組件是否更新(概念有區別)==')
        return setInterval(
            () => tick(),
            1000
        );
    }, [ div ])

    React.useEffect(() => {
        console.log('==組件載入完成==')
        return () => {
            console.log('==組件將要銷毀==')
            clearInterval(timerID);
        }
    }, [])

    React.useEffect(() => {
        console.log('==組件更新完成==')
        color();
    }, [ date ])

    console.log('==組件將要載入==')
    console.log('==組件獲取新的props==')
    console.log('==組件將要更新==')


    return (
        <DemoComponent />
    )
}

 

 

 

 

Hooks原理demo實現

實現文件:

 

import * as React from 'react';

type State = {
    [key: string]: any
}

export type Reducer = (state: State, action: string) => State;

export type UseReducer = typeof demoReducer;

type Queue<T> = {
    root: Queue<T> | null,
    next: Queue<T> | null,
    prev: Queue<T> | null,
    value: T,
    index: number
}

const memo: Queue<State>[] = [];

let index = 0;

function demoReducer (reducer: Reducer, initalState?: State, update?: () => void) : any[] {
    let has = !!memo[index];
    if (!memo[index]) {
        if (index === 0) {
            memo[0] = GeQueue<State>(initalState, index);
            memo[0].root =  memo[0];
        } else {
            memo[index] = GeQueue<State>(initalState, index);
            memo[index].root =  memo[0];
            memo[index].prev = memo[index - 1];
            memo[index-1].next = memo[index];
        }
        index = index + 1;
    }
    let state;
    if (has) {
        state = memo[index].value;
        index = index + 1;
    } else {
        state = memo[index - 1].value;
    }
    let bindex = index;
    return [
        state,
        (action: string) => {
            memo[bindex - 1].value = reducer(memo[bindex - 1].value, action)
            update();
        }
    ]
}

function GeQueue<T>(value: T, index: number): Queue<T> {
    return {
        root: null,
        prev: null,
        next: null,
        value,
        index
    }
}

export function DemoHoc ({ Com }: any) {
    const [ u, setU ] = React.useState(1)
    return (
        <div>
            <Com useReducer={(reducer: Reducer, initalState?: State) => {
                return demoReducer(reducer, initalState, () => {
                    index = 0;
                    setU(u + 1);
                })
            }} />
        </div>
    )
}

 

使用文件:

 

import * as React from 'react';
import { DemoHoc, UseReducer } from './useReducer/useReducerDemo'
import { Button } from 'antd';

export default function HookDemo () {
    return (
        <DemoHoc Com={DemoComponent} />
    )
}

const initialState = {count: 0};

function reducer(state: any, action: any) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}


function DemoComponent({ useReducer }: {
    useReducer: UseReducer
}) {
    const [ state, dispatch ] = useReducer(reducer, initialState);
    const [ state2, dispatch2 ] = useReducer(reducer, {...initialState});
    
    return (
        <div>
            Count: {state.count}
            <Button onClick={() => dispatch({type: 'decrement'})}>-</Button>
            <Button onClick={() => dispatch({type: 'increment'})}>+</Button>
            <div></div>
            Count2: {state2.count}
            <Button onClick={() => dispatch2({type: 'decrement'})}>-</Button>
            <Button onClick={() => dispatch2({type: 'increment'})}>+</Button>
        </div>
    )
}

 

原理:
我們先看實現文件的這個函數(不了解ts的同學,可以忽略ts的理解):

 

const memo: Queue<State>[] = [];

let index = 0;
function demoReducer (reducer: Reducer, initalState?: State, update?: () => void) : any[] {
    let has = !!memo[index];
    if (!memo[index]) {
        if (index === 0) {
            memo[0] = GeQueue<State>(initalState, index);
            memo[0].root =  memo[0];
        } else {
            memo[index] = GeQueue<State>(initalState, index);
            memo[index].root =  memo[0];
            memo[index].prev = memo[index - 1];
            memo[index-1].next = memo[index];
        }
        index = index + 1;
    }
    let state;
    if (has) {
        state = memo[index].value;
        index = index + 1;
    } else {
        state = memo[index - 1].value;
    }
    let bindex = index;
    return [
        state,
        (action: string) => {
            memo[bindex - 1].value = reducer(memo[bindex - 1].value, action)
            update();
        }
    ]
}

 

  • 最開始,我們用了一個memo對象(它是一個隊列)這裡簡單的定義成數組。 然後又又一個index,記錄數字關係
  • 然後重點來了

 

let has = !!memo[index];
    if (!memo[index]) {
        if (index === 0) {
            memo[0] = GeQueue<State>(initalState, index);
            memo[0].root =  memo[0];
        } else {
            memo[index] = GeQueue<State>(initalState, index);
            memo[index].root =  memo[0];
            memo[index].prev = memo[index - 1];
            memo[index-1].next = memo[index];
        }
        index = index + 1;
    }
    let state;
    if (has) {
        state = memo[index].value;
        index = index + 1;
    } else {
        state = memo[index - 1].value;
    }
    let bindex = index;

 

我們調用useReducer的時候,從memo中嘗試獲取index的值,若存在的話,將state賦值為當前memo里的值並返回

  • 最後我們提供了一個觸發更新的函數

然後-我們再看看使用的程式碼:

 

const initialState = {count: 0};

function reducer(state: any, action: any) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}


function DemoComponent({ useReducer }: {
    useReducer: UseReducer
}) {
    const [ state, dispatch ] = useReducer(reducer, initialState);
    const [ state2, dispatch2 ] = useReducer(reducer, {...initialState});
    
    return (
        <div>
            Count: {state.count}
            <Button onClick={() => dispatch({type: 'decrement'})}>-</Button>
            <Button onClick={() => dispatch({type: 'increment'})}>+</Button>
            <div></div>
            Count2: {state2.count}
            <Button onClick={() => dispatch2({type: 'decrement'})}>-</Button>
            <Button onClick={() => dispatch2({type: 'increment'})}>+</Button>
        </div>
    )
}

 

  • useReducer時經過memo的存儲,我們在隊列結構里存下了值,當dispatch的時候,memo中的變數將會改變。

所以各位看出來了嗎?

這就是hooks隱藏的邏輯所在!

重點來了~
hooks的本質是什麼?

 註:我的實現是為了理解它,與官方源碼存在出入哈,理解原理就好~

Hooks本質看坑與規範

hooks的本質是-閉包。
它在外部作用域存儲了值。
當setState發生時,整個函數(相當於render)都被觸發調用,那麼所有的變數就會重新賦值就像是下面的關鍵字:

  • const
  • let
  • function
    好多同學會覺得,奇怪了,為啥這個函數被重新調用,不會重新賦值呢?
    關鍵就是閉包!
    事實上hooks的每個setState都會導致function變成一個片段。在這之前的變數並不是之後的變數。
    而我們在onclick等事件中引用的函數,有可能會存儲舊的變數引用!這就會導致一些大問題!!!
  • 例如記憶體泄漏
  • 例如值的引用無效
    那怎麼解決?
    react其實提供了解決辦法,就是useMemo useCallback等等。你可以再深入的去理解它。

Hooks使用大忌

1、 外部頂層變數問題,不要把全局變數放在函數外部,特別是你在開發公共組件的時候,你可以用useRef
2、依賴項不正確問題,依賴項不正確會導致閉包問題。
3、組件拆分太粗問題,函數不建議太長,500行以內最佳
4、盲目使用usexxx,要深刻理解才能更好的使用hooks

 

最後

高效編碼;健康生活;

招人啦招人啦,SHEIN前端崗後端崗等你來投,可以私信部落客,也可以掃碼查看。期待與你做同事!