欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React Hooks核心原理深入分析講解

 更新時(shí)間:2022年12月17日 11:08:45   作者:helloworld1024fd  
這篇文章主要介紹了react hooks實(shí)現(xiàn)原理,文中給大家介紹了useState dispatch 函數(shù)如何與其使用的 Function Component 進(jìn)行綁定,節(jié)后實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下

React Hooks已經(jīng)推出一段時(shí)間,大家應(yīng)該比較熟悉,或者多多少少在項(xiàng)目中用過(guò)。寫(xiě)這篇文章簡(jiǎn)單分析一下Hooks的原理,并帶大家實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的Hooks。

這篇寫(xiě)的比較細(xì),相關(guān)的知識(shí)點(diǎn)都會(huì)解釋,給大家刷新一下記憶。

Hooks

Hooks是React 16.8推出的新功能。以這種更簡(jiǎn)單的方式進(jìn)行邏輯復(fù)用。之前函數(shù)組件被認(rèn)為是無(wú)狀態(tài)的。但是通過(guò)Hooks,函數(shù)組件也可以有狀態(tài),以及類組件的生命周期方法。

useState用法示例:

import React, { useState } from 'react';
function Example() {
  // count是組件的狀態(tài)
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>        Click me      </button>
    </div>
  );
}

閉包

開(kāi)始之前,我們來(lái)簡(jiǎn)單回顧一下閉包的概念,因?yàn)镠ooks的實(shí)現(xiàn)是高度依賴閉包的。

閉包(Closure),Kyle Simpson在《你不知道的Javascript》中總結(jié)閉包是:

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

閉包就是,函數(shù)可以訪問(wèn)到它所在的詞法作用域,即使是在定義以外的位置調(diào)用。

閉包的一個(gè)重要應(yīng)用就是,實(shí)現(xiàn)內(nèi)部變量/私有數(shù)據(jù)。

var counter = 0;
// 給計(jì)數(shù)器加1
function add() {
  counter += 1;
}
// 調(diào)用 add() 3次
add(); // 1
add(); // 2
counter = 1000;
add(); // 1003

這里因?yàn)閏ounter不是內(nèi)部變量,所以誰(shuí)都能修改它的值。我們不想讓人隨意修改counter怎么辦?這時(shí)候就可以用閉包:

function getAdd() {
  var counter = 0;
  return function add() {counter += 1;}
}
var add = getAdd();
add(); // 1
add(); // 2
add(); // 3
counter = 1000 // error! 當(dāng)前位置無(wú)法訪問(wèn)counter

我們還可以把函數(shù)的定義挪到調(diào)用的位置,用一個(gè)立即執(zhí)行函數(shù)表達(dá)式IIFE(Immediately Invoked Function Expression):

var add = (function getAdd() {
  var counter = 0;
  return function add() {counter += 1;}
})();
add(); // 1
add(); // 2
add(); // 3

這種通過(guò)IIFE創(chuàng)建閉包的方式也叫做模塊模式(Module Pattern),它創(chuàng)建了一個(gè)封閉的作用域,只有通過(guò)返回的對(duì)象/方法來(lái)操縱作用域中的值。這個(gè)模式由來(lái)已久了,之前很多Javascript的庫(kù),比如jQuery,就是用它來(lái)導(dǎo)出自己的實(shí)例的。

開(kāi)始動(dòng)手實(shí)現(xiàn)

理清閉包的概念后可以著手寫(xiě)了。從簡(jiǎn)單的入手,先來(lái)實(shí)現(xiàn)setState。

function useState(initialValue) {
  var _val = initialValue; // _val是useState的變量
  function state() {
    // state是一個(gè)內(nèi)部函數(shù),是閉包
    return _val;
  }
  function setState(newVal) {
    _val = newVal;
  }
  return [state, setState];
}
var [foo, setFoo] = useState(0);
console.log(foo()); // 0
setFoo(1);
console.log(foo()) // 1

根據(jù)useState的定義來(lái)實(shí)現(xiàn)。比較簡(jiǎn)單不需要多解釋。

參考 前端進(jìn)階面試題詳細(xì)解答

將useState應(yīng)用到組件中

現(xiàn)在我們將這個(gè)簡(jiǎn)易版的useState應(yīng)用到一個(gè)Counter組件中:

function Counter() {
  const [count, setCount] = useState(0);
  return {
    click: () => setCount(count() + 1),
    render: () => console.log('render:', { count: count() })
  }
}
const C = Counter();
C.render(); // render: { count: 0 }
C.click();
C.render(); // render: { count: 1 }

這里簡(jiǎn)單起見(jiàn),就不render真實(shí)DOM了,因?yàn)槲覀冎魂P(guān)心組件的狀態(tài),所以每次render的時(shí)候打印count的值。

這里點(diǎn)擊click之后,counter的值加一,useState的基本功能實(shí)現(xiàn)了。但現(xiàn)在state是一個(gè)函數(shù)而不是一個(gè)變量,這和React的API不一致,接下來(lái)我們就來(lái)改正這一點(diǎn)。

過(guò)期閉包

function useState(initialValue) {
  var _val = initialValue
  // 去掉了state()函數(shù)
  function setState(newVal) {
    _val = newVal
  }
  return [_val, setState] //直接返回_val
}
var [foo, setFoo] = useState(0)
console.log(foo) // 0
setFoo(1) // 更新_val
console.log(foo) // 0 - BUG!

如果我們直接把state從函數(shù)改成變量,問(wèn)題就出現(xiàn)了,state不更新了。無(wú)論點(diǎn)擊幾次,Counter的值始終不變。這個(gè)是過(guò)期閉包問(wèn)題(Stale Closure Problem)。因?yàn)樵趗seState返回的時(shí)候,state就指向了初始值,所以后面即使counter的值改變了,打印出來(lái)的仍然就舊值。我們想要的是,返回一個(gè)變量的同時(shí),還能讓這個(gè)變量和真實(shí)狀態(tài)同步。那如何來(lái)實(shí)現(xiàn)呢?

模塊模式

解決辦法就是將閉包放在另一個(gè)閉包中。

const MyReact = (function() {
  let _val //將_val提升到外層閉包
  return {
    render(Component) {
      const Comp = Component()
      Comp.render()
      return Comp
    },
    useState(initialValue) {
      _val = _val || initialValue //每次刷新
      function setState(newVal) {
        _val = newVal
      }
      return [_val, setState]
    }
  }
})()

我們運(yùn)用之前提到的模塊模式,創(chuàng)建一個(gè)MyReact模塊(第一層閉包),返回的對(duì)象中包含useState方法(第二層閉包)。useState返回值中的state,指向的是useState閉包中的_val,而每次調(diào)用useState,_val都會(huì)重新綁定到上層的_val上,保證返回的state的值是最新的。解決了過(guò)期閉包的問(wèn)題。

MyReact還提供了另外一個(gè)方法render,方法中調(diào)用組件的render方法來(lái)“渲染”組件,也是為了不渲染DOM的情況下進(jìn)行測(cè)試。

function Counter() {
  const [count, setCount] = MyReact.useState(0)
  return {
    click: () => setCount(count + 1),
    render: () => console.log('render:', { count })
  }
}
let App
App = MyReact.render(Counter) // render: { count: 0 }
App.click()
App = MyReact.render(Counter) // render: { count: 1 }

這里每次調(diào)用MyReact.render(Counter),都會(huì)生成新的Counter實(shí)例,調(diào)用實(shí)例的render方法。render方法中調(diào)用了MyReact.useState()。MyReact.useState()在多次執(zhí)行之間,外層閉包中的_val值保持不變,所以count會(huì)綁定到當(dāng)前的_val上,這樣就可以打印出正確的count值了。

實(shí)現(xiàn)useEffect

實(shí)現(xiàn)了useState之后,接下來(lái)實(shí)現(xiàn)useEffect。

const MyReact = (function() {
  let _val, _deps // 將狀態(tài)和依賴數(shù)組保存到外層的閉包中
  return {
    render(Component) {
      const Comp = Component()
      Comp.render()
      return Comp
    },
    useEffect(callback, depArray) {
      const hasNoDeps = !depArray
      const hasChangedDeps = _deps ? !depArray.every((el, i) => el === _deps[i]) : true
      if (hasNoDeps || hasChangedDeps) {
        callback()
        _deps = depArray
      }
    },
    useState(initialValue) {
      _val = _val || initialValue
      function setState(newVal) {
        _val = newVal
      }
      return [_val, setState]
    }
  }
})()
// usage
function Counter() {
  const [count, setCount] = MyReact.useState(0)
  MyReact.useEffect(() => {
    console.log('effect', count)
  }, [count])
  return {
    click: () => setCount(count + 1),
    noop: () => setCount(count),
    render: () => console.log('render', { count })
  }
}
let App
App = MyReact.render(Counter)
// effect 0
// render {count: 0}
App.click()
App = MyReact.render(Counter)
// effect 1
// render {count: 1}
App.noop()
App = MyReact.render(Counter)
// // 沒(méi)有執(zhí)行effect
// render {count: 1}
App.click()
App = MyReact.render(Counter)
// effect 2
// render {count: 2}

在MyReact.useEffect中,我們將依賴數(shù)組保存到_deps,每次調(diào)用,都和前一次的依賴數(shù)組進(jìn)行比對(duì)。發(fā)生變化才觸發(fā)回調(diào)。

注意這里在比較依賴時(shí)用的是Object.is, React在比較state變化時(shí)也是用它。注意Object.is在比較時(shí)不會(huì)做類型轉(zhuǎn)換(和==不同)。另外NaN === NaN返回false,但是Object.is(NaN, NaN)會(huì)返回true。

(簡(jiǎn)單起見(jiàn),我們實(shí)現(xiàn)的useEffect,回調(diào)函數(shù)是同步執(zhí)行的,所以打印出來(lái)的log是effect先執(zhí)行,然后才是render。實(shí)際React中useEffect的回調(diào)函數(shù)應(yīng)該是異步執(zhí)行的)

支持多個(gè)Hooks

到此為止我們已經(jīng)簡(jiǎn)單實(shí)現(xiàn)了useState和useEffect。但還有一個(gè)問(wèn)題,就是useState和useEffect每個(gè)組件中只能用一次。

那么怎么才能支持使用多次hooks呢,我們可以將hooks保存到一個(gè)數(shù)組中。

const MyReact = (function() {
  let hooks = [],
    currentHook = 0 // 存儲(chǔ)hooks的數(shù)組,和數(shù)組指針
  return {
    render(Component) {
      const Comp = Component() // 執(zhí)行effect
      Comp.render()
      currentHook = 0 // 每次render后,hooks的指針清零
      return Comp
    },
    useEffect(callback, depArray) {
      const hasNoDeps = !depArray
      const deps = hooks[currentHook]
      const hasChangedDeps = deps ? !depArray.some((el, i) => !Object.is(el, deps[i])) : true
      if (hasNoDeps || hasChangedDeps) {
        callback()
        hooks[currentHook] = depArray
      }
      currentHook++ // 每調(diào)用一次指針加一
    },
    useState(initialValue) {
      hooks[currentHook] = hooks[currentHook] || initialValue
      const setStateHookIndex = currentHook // 注意??這句不是沒(méi)用。是避免過(guò)期閉包問(wèn)題。
      const setState = newState => (hooks[setStateHookIndex] = newState)
      return [hooks[currentHook++], setState]
    }
  }
})()

注意這里用了一個(gè)新的變量setStateHookIndex來(lái)保存currentHook的值。這是為了避免useState閉包包住舊的currentHook的值。

將改動(dòng)應(yīng)用到組件中:

function Counter() {
  const [count, setCount] = MyReact.useState(0)
  const [text, setText] = MyReact.useState('foo') // 第二次用了useState
  MyReact.useEffect(() => {
    console.log('effect', count, text)
  }, [count, text])
  return {
    click: () => setCount(count + 1),
    type: txt => setText(txt),
    noop: () => setCount(count),
    render: () => console.log('render', { count, text })
  }
}
let App
App = MyReact.render(Counter)
// effect 0 foo
// render {count: 0, text: 'foo'}
App.click()
App = MyReact.render(Counter)
// effect 1 foo
// render {count: 1, text: 'foo'}
App.type('bar')
App = MyReact.render(Counter)
// effect 1 bar
// render {count: 1, text: 'bar'}
App.noop()
App = MyReact.render(Counter)
// // 不運(yùn)行effect
// render {count: 1, text: 'bar'}
App.click()
App = MyReact.render(Counter)
// effect 2 bar
// render {count: 2, text: 'bar'}

實(shí)現(xiàn)多個(gè)hooks支持的基本思路,就是用一個(gè)數(shù)組存放hooks。每次使用hooks時(shí),將hooks指針加1。每次render以后,將指針清零。

Custom Hooks

接下來(lái),可以借助已經(jīng)實(shí)現(xiàn)的hooks繼續(xù)實(shí)現(xiàn)custom hooks:

function Component() {
  const [text, setText] = useSplitURL('www.google.com')
  return {
    type: txt => setText(txt),
    render: () => console.log({ text })
  }
}
function useSplitURL(str) {
  const [text, setText] = MyReact.useState(str)
  const masked = text.split('.')
  return [masked, setText]
}
let App
App = MyReact.render(Component)
// { text: [ 'www', 'google', 'com' ] }
App.type('www.reactjs.org')
App = MyReact.render(Component)
// { text: [ 'www', 'reactjs', 'org' ] }}

重新理解Hooks規(guī)則

了解Hooks的實(shí)現(xiàn)可以幫助我們理解Hooks的使用規(guī)則。還記得使用Hooks的原則嗎?hooks只能用到組件最外層的代碼中,不能包裹在if或者循環(huán)里,原因是在React內(nèi)部,通過(guò)數(shù)組來(lái)存儲(chǔ)hooks。所以必須保證每次render,hooks的順序不變,數(shù)量不變,才能做deps的比對(duì)。

到此這篇關(guān)于React Hooks核心原理深入分析講解的文章就介紹到這了,更多相關(guān)React Hooks內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React跨端動(dòng)態(tài)化之從JS引擎到RN落地詳解

    React跨端動(dòng)態(tài)化之從JS引擎到RN落地詳解

    這篇文章主要為大家介紹了React跨端動(dòng)態(tài)化之從JS引擎到RN落地,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 在react中使用windicss的問(wèn)題

    在react中使用windicss的問(wèn)題

    這篇文章主要介紹了在react中使用windicss的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • React語(yǔ)法中設(shè)置TS校驗(yàn)規(guī)則的步驟詳解

    React語(yǔ)法中設(shè)置TS校驗(yàn)規(guī)則的步驟詳解

    這篇文章主要給大家介紹了React語(yǔ)法中如何設(shè)置TS校驗(yàn)規(guī)則,文中有詳細(xì)的代碼示例供大家參考,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-10-10
  • React并發(fā)更新與性能優(yōu)化解析

    React并發(fā)更新與性能優(yōu)化解析

    這篇文章主要為大家介紹了React并發(fā)更新與性能優(yōu)化解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • React+Typescript創(chuàng)建項(xiàng)目的實(shí)現(xiàn)步驟

    React+Typescript創(chuàng)建項(xiàng)目的實(shí)現(xiàn)步驟

    通過(guò)React組件庫(kù)和TypeScript的強(qiáng)類型特性,開(kāi)發(fā)者可以創(chuàng)建出具有優(yōu)秀用戶體驗(yàn)和穩(wěn)定性的Web應(yīng)用程序,本文主要介紹了React+Typescript創(chuàng)建項(xiàng)目的實(shí)現(xiàn)步驟,感興趣的可以了解一下
    2023-08-08
  • 使用?React?Hooks?重構(gòu)類組件的示例詳解

    使用?React?Hooks?重構(gòu)類組件的示例詳解

    這篇文章主要介紹了如何使用?React?Hooks?重構(gòu)類組件,本文就來(lái)通過(guò)一些常見(jiàn)示例看看如何使用 React Hooks 來(lái)重構(gòu)類組件,需要的朋友可以參考下
    2022-07-07
  • ahooks useVirtualList 封裝虛擬滾動(dòng)列表

    ahooks useVirtualList 封裝虛擬滾動(dòng)列表

    這篇文章主要為大家介紹了ahooks useVirtualList 封裝虛擬滾動(dòng)列表詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • React?split實(shí)現(xiàn)分割字符串的使用示例

    React?split實(shí)現(xiàn)分割字符串的使用示例

    當(dāng)我們需要將一個(gè)字符串按照指定的分隔符進(jìn)行分割成數(shù)組時(shí),我們可以在組件的生命周期方法中使用split方法來(lái)實(shí)現(xiàn)這個(gè)功能,本文就來(lái)介紹一下,感興趣的可以了解下
    2023-10-10
  • React Native自定義控件底部抽屜菜單的示例

    React Native自定義控件底部抽屜菜單的示例

    本篇文章主要介紹了React Native自定義控件底部抽屜菜單的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • 詳解React中Props的淺對(duì)比

    詳解React中Props的淺對(duì)比

    這篇文章主要介紹了React中Props的淺對(duì)比的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用React,感興趣的朋友可以了解下
    2021-05-05

最新評(píng)論