react hook用法及實現原理

一、hook的簡介

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

直接貼code,hook實現簡版計數器

簡單debugger下代碼

  • 咱們能夠看到hook是用memorizedState來保存狀態

二、useState的簡版實現

  • 核心做用是給函數組件增長了一個保持狀態的功能
let memoizedState;  //聲明memoizedState 
function useState(initialState) {
    memoizedState = memoizedState || initialState;
    function setState(newState) {
        memoizedState = newState; //設置狀態時候把新狀態賦值給memoizedState 
        render(); //從新render
    }
    return [memoizedState,setState]
}
複製代碼

從簡版的實現來看,仍是很容易理解,測試下效果react

三、userReducer簡介和實現

3.一、userReducer簡介

  • useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。(若是你熟悉 Redux 的話,就已經知道它如何工做了。)
  • 在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。

用userReducer實現計數器,可接收3個參數,reducer | initalArg(初始值) | init (定義初始值的函數)web

import React,{Fragment,useReducer} from 'react';
import ReactDOM from 'react-dom';
// reducer,跟redux中reducer同樣
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
function reducer(state,action){
    switch (action.type) {
        case INCREMENT:
            return {number:state.number+1};
        case DECREMENT:
            return {number:state.number-1};
        default:
            return state;
    }
}
//初始值
let initalArg = 0;
// 返回初始值的函數
function init(initalArg) {
    return {number:initalArg};
}

function Counter() {
    // state = {number:0}
    const [state,dispatch] = useReducer(reducer,initalArg,init);
    return (
        <Fragment>
            <p>{state.number}</p>
            <button onClick={()=>dispatch({type:INCREMENT})}>+</button>
            <button onClick={()=>dispatch({type:DECREMENT})}>-</button>
        </Fragment>
    )
}
function render() {
    ReactDOM.render(<Counter />,document.getElementById('root'));
}
render();

複製代碼

3.二、簡版實現原理

let memoizedState; //聲明記憶的狀態
function useReducer(reducer,initalArg,init) {
    let initialState;
    // init若是沒有傳值,initalArg爲默認的初始狀態。若是傳值,初始值函數處理後做爲初始狀態
    if(typeof init !='undefined'){
        initialState = init(initalArg);
    }else {
        initialState = initalArg;
    }
    memoizedState = memoizedState || initialState;
    function dispatch(action) {
        memoizedState = reducer(memoizedState,action);
        render();
    }

    return [memoizedState,dispatch]
}

複製代碼

運行結果redux

3.三、useReducer是useState的內部實現,重寫useState實現

let memoizedState;
function useReducer(reducer,initalArg,init) {
    let initialState;
    if(typeof init !='undefined'){
        initialState = init(initalArg);
    }else {
        initialState = initalArg;
    }
    memoizedState = memoizedState || initialState;
    function dispatch(action) {
        memoizedState = reducer(memoizedState,action);
        render();
    }

    return [memoizedState,dispatch]
}

function useState(initialState) {
    // 主要是reducer實現,把新狀態賦值過去
    return useReducer((oldState,newState)=>newState,initialState);
}

複製代碼

驗證如下數組

四、多個useState同時調用

當一個組件調用多個useState時,此時咱們須要用數組來保存多個初始值緩存

4.一、多個useState使用的示例demo

  • 兩個按鈕,一個改變name,一個改變number

4.二、實現原理

  • 以前都是使用一個useState,當多個useState時候,須要用數組保存全部的初始狀態
  • 須要用index記錄當前的索引
  • 每次render時候,index索引須要回覆初始值
import React,{Fragment} from 'react';
import ReactDOM from 'react-dom';

// 數組保存memoizedState
let memoizedState=[];
// 記錄索引
let index = 0;
function useState(initialState) {
    memoizedState[index] = memoizedState[index] || initialState;
    // 緩存當前索引,由於每次render,index索引會重置爲0
    let currentIndex = index;
    function setState(newState) {
        memoizedState[currentIndex] = newState;
        render();
    }
    return [memoizedState[index++],setState]
}

function Counter() {
    const [name,setName] = useState('計數器');
    const [number,setNumber] = useState(0);
    return (
        <Fragment>
            <p>{name }:{number}</p>
            <button onClick={()=>setName("計數器"+Date.now())}>更名稱</button>
            <button onClick={()=>setNumber(number+1)}>+</button>
        </Fragment>
    )
}
function render() {
    // 每次render,把index回覆初始值
    index = 0;
    ReactDOM.render(<Counter />,document.getElementById('root'));
}
render();

複製代碼

看下效果,源碼是用鏈表實現的,此處咱們用數組,邏輯是差很少,容易理解bash

五、useEffect簡介與實現

5.一、簡介

  • useEffect 給函數組件添加了操做反作用的能力, 好比事件的訂閱與取消、定時器的設置與清空
  • 相似於在類組件生命週期 componentDidMount、componentWillUnmount作的事情

計數器變化後,實現打印一句log的示例 參考連接dom

import React,{Fragment,useState} from 'react';
import ReactDOM from 'react-dom';

function Counter() {
    const [name,setName] = useState('計數器');
    const [number,setNumber] = useState(0);
    useEffect(()=>{
        // 訂閱
        console.log("訂閱狀態")
    },[number,name]);
    return (
        <Fragment>
            <p>{name }:{number}</p>
            <button onClick={()=>setName("計數器"+Date.now())}>更名稱</button>
            <button onClick={()=>setNumber(number+1)}>+</button>
        </Fragment>
    )
}
function render() {
    ReactDOM.render(<Counter />,document.getElementById('root'));
}
render();

複製代碼

5.二、簡版實現

  • useEffect第二個參數爲依賴項,即當依賴項改變時,纔會觸發回調

簡版實現函數

// 記錄最後依賴項
let lastDependencies;
function useEffect(callback,dependencies) {
    // 若是依賴項沒有傳值,則直接調用callback
    if(!dependencies) return callback();
    /* 一、首次渲染isChange爲true,把初始的依賴項賦給lastDependencies
    *  二、再次渲染時候,把lastDependencies和dependencies作對比,當不徹底相等時,才觸發回調
    */ 
    let isChange = lastDependencies? !dependencies.every((item,index)=>item===lastDependencies[index]):true;
    if(isChange){
        callback();
        lastDependencies = dependencies;
    }
}

複製代碼

5.三、當有多個useEffect時,如何實現

  • 統一把lastDependencies放到useState中的memoizedState中
let memoizedState=[];
let index = 0;
function useState(initialState) {
    memoizedState[index] = memoizedState[index] || initialState;
    let currentIndex = index;
    function setState(newState) {
        memoizedState[currentIndex] = newState;
        render();
    }
    return [memoizedState[index++],setState]
}

function useEffect(callback,dependencies) {
    console.log('dependencies',dependencies);
    // 若是依賴項沒有傳值,則直接調用callback
    if(!dependencies){
        // 保證索引對應
        index++;
        return callback();
    }
    // 從memoizedState取最後一個依賴項
    let lastDependencies = memoizedState[index];
    let isChange = lastDependencies? !dependencies.every((item,index)=>item===lastDependencies[index]):true;
    if(isChange){
        callback();
        // 往memoizedState存依賴項
        memoizedState[index] = dependencies;
    }
    // 索引遞增
    index++
}

複製代碼

驗證下效果測試

後續繼續補充

相關文章
相關標籤/搜索