本篇內(nèi)容主要講解“ReactHook核心原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“ReactHook核心原理是什么”吧!
我們提供的服務(wù)有:成都做網(wǎng)站、成都網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、鹿寨ssl等。為近1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的鹿寨網(wǎng)站制作公司
基本準(zhǔn)備工作
手寫useState:useState的使用,原理實(shí)現(xiàn)。
React.memo介紹
手寫useCallback:useCallback的使用,原理實(shí)現(xiàn)。
手寫useMemo:使用,原理。
手寫useReducer:使用,原理。
手寫useContext:使用,原理。
手寫useEffect:使用,原理。
手寫useLayoutEffect:使用,原理。
基本準(zhǔn)備工作
利用 creact-react-app 創(chuàng)建一個項目
已經(jīng)把項目放到 github:https://github.com/Sunny-lucking/HowToBuildMyReactHook 可以卑微地要個star嗎
手寫useState
useState的使用
useState可以在函數(shù)組件中,添加state Hook。
調(diào)用useState會返回一個state變量,以及更新state變量的方法。useState的參數(shù)是state變量的初始值,初始值僅在初次渲染時有效。
更新state變量的方法,并不會像this.setState一樣,合并state。而是替換state變量。下面是一個簡單的例子, 會在頁面上渲染count的值,點(diǎn)擊setCount的按鈕會更新count的值。
function App(){ const [count, setCount] = useState(0); return ( <div> {count} <button onClick={() => { setCount(count + 1); }} > 增加 </button> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
原理實(shí)現(xiàn)
let lastState function useState(initState) { lastState = lastState || initState; function setState(newState) { lastState = newState } return [lastState,setState] } function App(){ //。。。 } ReactDOM.render( <App />, document.getElementById('root') );
如代碼所示,我們自己創(chuàng)建了一個useState方法
當(dāng)我們使用這個方法時,如果是第一次使用,則取initState的值,否則就取上一次的值(laststate).
在內(nèi)部,我們創(chuàng)建了一個setState方法,該方法用于更新state的值
然后返回一個lastSate屬性和setState方法。
看似完美,但是我們其實(shí)忽略了一個問題:每次執(zhí)行玩setState都應(yīng)該重新渲染當(dāng)前組件的。
所以我們需要在setState里面執(zhí)行刷新操作
let lastState function useState(initState) { lastState = lastState || initState; function setState(newState) { lastState = newState render() } return [lastState,setState] } function App(){ const [count, setCount] = useState(0); return ( <div> {count} <button onClick={() => { setCount(count + 1); }} > 增加 </button> </div> ); } // 新增方法 function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
如代碼所示,我們在setState里添加了個render方法。render方法則會執(zhí)行
ReactDOM.render( <App />, document.getElementById('root') );
也就是重新渲染啦。
好了,現(xiàn)在是不是已經(jīng)完整了呢?
不,還有個問題:就說我們這里只是用了一個useState,要是我們使用了很多個呢?難道要聲明很多個全局變量嗎?
這顯然是不行的,所以,我們可以設(shè)計一個全局?jǐn)?shù)組來保存這些state
let lastState = [] let stateIndex = 0 function useState(initState) { lastState[stateIndex] = lastState[stateIndex] || initState; const currentIndex = stateIndex function setState(newState) { lastState[stateIndex] = newState render() } return [lastState[stateIndex++],setState] }
這里的currentIndex是利用了閉包的思想,將某個state相應(yīng)的index記錄下來了。
好了,useState方法就到這里基本完成了。是不是so easy!!!
React.memo介紹
看下面的代碼!你發(fā)現(xiàn)什么問題?
import React ,{useState}from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } function App(){ const [count, setCount] = useState(0); return ( <div> <Child data={123}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
沒錯,就是盡管我們傳個子組件的props是固定的值,當(dāng)父組件的數(shù)據(jù)更改時,子組件也被重新渲染了,我們是希望當(dāng)傳給子組件的props改變時,才重新渲染子組件。
所以引入了React.memo。
看看介紹
React.memo() 和 PureComponent 很相似,它幫助我們控制何時重新渲染組件。
組件僅在它的 props 發(fā)生改變的時候進(jìn)行重新渲染。通常來說,在組件樹中 React 組件,只要有變化就會走一遍渲染流程。但是通過 PureComponent 和 React.memo(),我們可以僅僅讓某些組件進(jìn)行渲染。
import React ,{useState,memo}from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } Child = memo(Child) function App(){ const [count, setCount] = useState(0); return ( <div> <Child data={123}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
因此,當(dāng)Child被memo包裝后,就只會當(dāng)props改變時才會重新渲染了。
當(dāng)然,由于React.memo并不是react-hook的內(nèi)容,所以這里并不會取討論它是怎么實(shí)現(xiàn)的。
手寫useCallback
useCallback的使用
當(dāng)我們試圖給一個子組件傳遞一個方法的時候,如下代碼所示
import React ,{useState,memo}from 'react'; import ReactDOM from 'react-dom'; function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } // eslint-disable-next-line Child = memo(Child) function App(){ const [count, setCount] = useState(0); const addClick = ()=>{console.log("addClick")} return ( <div> <Child data={123} onClick={addClick}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
發(fā)現(xiàn)我們傳了一個addClick方法 是固定的,但是卻每一次點(diǎn)擊按鈕子組件都會重新渲染。
這是因?yàn)槟憧此芶ddClick方法沒改變,其實(shí)舊的和新的addClick是不一樣的,如圖所示
在這里插入圖片描述
這時,如果想要,傳入的都是同一個方法,就要用到useCallBack。
如代碼所示
import React ,{useState,memo,useCallback}from 'react'; import ReactDOM from 'react-dom'; function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } // eslint-disable-next-line Child = memo(Child) function App(){ const [count, setCount] = useState(0); // eslint-disable-next-line const addClick = useCallback(()=>{console.log("addClick")},[]) return ( <div> <Child data={123} onClick={addClick}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
useCallback鉤子的第一個參數(shù)是我們要傳遞給子組件的方法,第二個參數(shù)是一個數(shù)組,用于監(jiān)聽數(shù)組里的元素變化的時候,才會返回一個新的方法。
原理實(shí)現(xiàn)
我們知道useCallback有兩個參數(shù),所以可以先寫
function useCallback(callback,lastCallbackDependencies){ }
跟useState一樣,我們同樣需要用全局變量把callback和dependencies保存下來。
let lastCallback let lastCallbackDependencies function useCallback(callback,dependencies){ }
首先useCallback會判斷我們是否傳入了依賴項,如果沒有傳的話,說明要每一次執(zhí)行useCallback都返回最新的callback
let lastCallback let lastCallbackDependencies function useCallback(callback,dependencies){ if(lastCallbackDependencies){ }else{ // 沒有傳入依賴項 } return lastCallback }
所以當(dāng)我們沒有傳入依賴項的時候,實(shí)際上可以把它當(dāng)作第一次執(zhí)行,因此,要把lastCallback和lastCallbackDependencies重新賦值
let lastCallback let lastCallbackDependencies function useCallback(callback,dependencies){ if(lastCallbackDependencies){ }else{ // 沒有傳入依賴項 lastCallback = callback lastCallbackDependencies = dependencies } return lastCallback }
當(dāng)有傳入依賴項的時候,需要看看新的依賴數(shù)組的每一項和來的依賴數(shù)組的每一項的值是否相等
let lastCallback let lastCallbackDependencies function useCallback(callback,dependencies){ if(lastCallbackDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastCallbackDependencies[index] }) }else{ // 沒有傳入依賴項 lastCallback = callback lastCallbackDependencies = dependencies } return lastCallback } function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) }
當(dāng)依賴項有值改變的時候,我們需要對lastCallback和lastCallbackDependencies重新賦值
import React ,{useState,memo}from 'react'; import ReactDOM from 'react-dom'; let lastCallback // eslint-disable-next-line let lastCallbackDependencies function useCallback(callback,dependencies){ if(lastCallbackDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastCallbackDependencies[index] }) if(changed){ lastCallback = callback lastCallbackDependencies = dependencies } }else{ // 沒有傳入依賴項 lastCallback = callback lastCallbackDependencies = dependencies } return lastCallback } function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } // eslint-disable-next-line Child = memo(Child) function App(){ const [count, setCount] = useState(0); // eslint-disable-next-line const addClick = useCallback(()=>{console.log("addClick")},[]) return ( <div> <Child data={123} onClick={addClick}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
手寫useMemo
使用
useMemo和useCallback類似,不過useCallback用于緩存函數(shù),而useMemo用于緩存函數(shù)返回值
let data = useMemo(()=> ({number}),[number])
如代碼所示,利用useMemo用于緩存函數(shù)的返回值number,并且當(dāng)只有監(jiān)聽元素為[number],也就是說,當(dāng)number的值發(fā)生改變的時候,才會重新執(zhí)行
()=> ({number})
然后返回新的number
原理
所以,useMemo的原理跟useCallback的差不多,仿寫即可。
import React ,{useState,memo,}from 'react'; import ReactDOM from 'react-dom'; let lastMemo // eslint-disable-next-line let lastMemoDependencies function useMemo(callback,dependencies){ if(lastMemoDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastMemoDependencies[index] }) if(changed){ lastMemo = callback() lastMemoDependencies = dependencies } }else{ // 沒有傳入依賴項 lastMemo = callback() lastMemoDependencies = dependencies } return lastMemo } function Child({data}) { console.log("天啊,我怎么被渲染啦,我并不希望啊") return ( <div>child</div> ) } // eslint-disable-next-line Child = memo(Child) function App(){ const [count, setCount] = useState(0); // eslint-disable-next-line const [number, setNumber] = useState(20) let data = useMemo(()=> ({number}),[number]) return ( <div> <Child data={data}></Child> <button onClick={() => { setCount(count + 1)}}> 增加 </button> </div> ); } function render(){ ReactDOM.render( <App />, document.getElementById('root') ); } render()
手寫useReducer
使用
先簡單介紹下useReducer。
const [state, dispatch] = useReducer(reducer, initState);
useReducer接收兩個參數(shù):
第一個參數(shù):reducer函數(shù),第二個參數(shù):初始化的state。
返回值為最新的state和dispatch函數(shù)(用來觸發(fā)reducer函數(shù),計算對應(yīng)的state)。
按照官方的說法:對于復(fù)雜的state操作邏輯,嵌套的state的對象,推薦使用useReducer。
聽起來比較抽象,我們先看一個簡單的例子:
// 官方 useReducer Demo // 第一個參數(shù):應(yīng)用的初始化 const initialState = {count: 0}; // 第二個參數(shù):state的reducer處理函數(shù) function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { // 返回值:最新的state和dispatch函數(shù) const [state, dispatch] = useReducer(reducer, initialState); return ( <> // useReducer會根據(jù)dispatch的action,返回最終的state,并觸發(fā)rerender Count: {state.count} // dispatch 用來接收一個 action參數(shù)「reducer中的action」,用來觸發(fā)reducer函數(shù),更新最新的狀態(tài) <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
其實(shí)意思可以簡單的理解為,當(dāng)state是基本數(shù)據(jù)類型的時候,可以用useState,當(dāng)state是對象的時候,可以用reducer,當(dāng)然這只是一種簡單的想法。大家不必引以為意。具體情況視具體場景分析。
原理
看原理你會發(fā)現(xiàn)十分簡單,簡單到不用我說什么,不到十行代碼,不信你直接看代碼
import React from 'react'; import ReactDOM from 'react-dom'; let lastState // useReducer原理 function useReducer(reducer,initialState){ lastState = lastState || initialState function dispatch(action){ lastState = reducer(lastState,action) render() } return [lastState,dispatch] } // 官方 useReducer Demo // 第一個參數(shù):應(yīng)用的初始化 const initialState = {count: 0}; // 第二個參數(shù):state的reducer處理函數(shù) function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { // 返回值:最新的state和dispatch函數(shù) const [state, dispatch] = useReducer(reducer, initialState); return ( <> {/* // useReducer會根據(jù)dispatch的action,返回最終的state,并觸發(fā)rerender */} Count: {state.count} {/* // dispatch 用來接收一個 action參數(shù)「reducer中的action」,用來觸發(fā)reducer函數(shù),更新最新的狀態(tài) */} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } function render(){ ReactDOM.render( <Counter />, document.getElementById('root') ); } render()
手寫useContext
使用
createContext 能夠創(chuàng)建一個 React 的 上下文(context),然后訂閱了這個上下文的組件中,可以拿到上下文中提供的數(shù)據(jù)或者其他信息。
基本的使用方法:
const MyContext = React.createContext()
如果要使用創(chuàng)建的上下文,需要通過 Context.Provider 最外層包裝組件,并且需要顯示的通過
子組件在匹配過程中只會匹配最新的 Provider,也就是說如果有下面三個組件:ContextA.Provider->A->ContexB.Provider->B->C
如果 ContextA 和 ContextB 提供了相同的方法,則 C 組件只會選擇 ContextB 提供的方法。
通過 React.createContext 創(chuàng)建出來的上下文,在子組件中可以通過 useContext 這個 Hook 獲取 Provider 提供的內(nèi)容
const {funcName} = useContext(MyContext);
從上面代碼可以發(fā)現(xiàn),useContext 需要將 MyContext 這個 Context 實(shí)例傳入,不是字符串,就是實(shí)例本身。
這種用法會存在一個比較尷尬的地方,父子組件不在一個目錄中,如何共享 MyContext 這個 Context 實(shí)例呢?
一般這種情況下,我會通過 Context Manager 統(tǒng)一管理上下文的實(shí)例,然后通過 export 將實(shí)例導(dǎo)出,在子組件中在將實(shí)例 import 進(jìn)來。
下面我們看看代碼,使用起來非常簡單
import React, { useState, useContext } from 'react'; import ReactDOM from 'react-dom'; let AppContext = React.createContext() function Counter() { let { state, setState } = useContext(AppContext) return ( <> Count: {state.count} <button onClick={() => setState({ number: state.number + 1 })}>+</button> </> ); } function App() { let [state, setState] = useState({ number: 0 }) return ( <AppContext.Provider value={{ state, setState }}> <div> <h2>{state.number}</h2> <Counter></Counter> </div> </AppContext.Provider> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
要是用過vue的同學(xué),會發(fā)現(xiàn),這個機(jī)制有點(diǎn)類似vue 中提供的provide和inject
原理
原理非常簡單,由于createContext,Provider 不是ReactHook的內(nèi)容, 所以這里值需要實(shí)現(xiàn)useContext,如代碼所示,只需要一行代碼
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; let AppContext = React.createContext() function useContext(context){ return context._currentValue } function Counter() { let { state, setState } = useContext(AppContext) return ( <> <button onClick={() => setState({ number: state.number + 1 })}>+</button> </> ); } function App() { let [state, setState] = useState({ number: 0 }) return ( <AppContext.Provider value={{ state, setState }}> <div> <h2>{state.number}</h2> <Counter></Counter> </div> </AppContext.Provider> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
手寫useEffect
使用
它跟class組件中的componentDidMount,componentDidUpdate,componentWillUnmount具有相同的用途,只不過被合成了一個api。
import React, { useState, useEffect} from 'react'; import ReactDOM from 'react-dom'; function App() { let [number, setNumber] = useState(0) useEffect(()=>{ console.log(number); },[number]) return ( <div> <h2>{number}</h2> <button onClick={() => setNumber(number+1)}>+</button> </div> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
如代碼所示,支持兩個參數(shù),第二個參數(shù)也是用于監(jiān)聽的。當(dāng)監(jiān)聽數(shù)組中的元素有變化的時候再執(zhí)行作為第一個參數(shù)的執(zhí)行函數(shù)
原理
原理發(fā)現(xiàn)其實(shí)和useMemo,useCallback類似,只不過,前面前兩個有返回值,而useEffect沒有。(當(dāng)然也有返回值,就是那個執(zhí)行componentWillUnmount函功能的時候?qū)懙姆祷刂?,但是這里返回值跟前兩個作用不一樣,因?yàn)槟悴粫?/p>
let xxx = useEffect(()=>{ console.log(number); },[number])
來接收返回值。
所以,忽略返回值,你可以直接看代碼,真的很類似,簡直可以用一模一樣來形容
import React, { useState} from 'react'; import ReactDOM from 'react-dom'; let lastEffectDependencies function useEffect(callback,dependencies){ if(lastEffectDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastEffectDependencies[index] }) if(changed){ callback() lastEffectDependencies = dependencies } }else{ callback() lastEffectDependencies = dependencies } } function App() { let [number, setNumber] = useState(0) useEffect(()=>{ console.log(number); },[number]) return ( <div> <h2>{number}</h2> <button onClick={() => setNumber(number+1)}>+</button> </div> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
你以為這樣就結(jié)束了,其實(shí)還沒有,因?yàn)榈谝粋€參數(shù)的執(zhí)行時機(jī)錯了,實(shí)際上作為第一個參數(shù)的函數(shù)因?yàn)槭窃跒g覽器渲染結(jié)束后執(zhí)行的。而這里我們是同步執(zhí)行的。
所以需要改成異步執(zhí)行callback
import React, { useState} from 'react'; import ReactDOM from 'react-dom'; let lastEffectDependencies function useEffect(callback,dependencies){ if(lastEffectDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastEffectDependencies[index] }) if(changed){ setTimeout(callback()) lastEffectDependencies = dependencies } }else{ setTimeout(callback()) lastEffectDependencies = dependencies } } function App() { let [number, setNumber] = useState(0) useEffect(()=>{ console.log(number); },[number]) return ( <div> <h2>{number}</h2> <button onClick={() => setNumber(number+1)}>+</button> </div> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
手寫useLayoutEffect
使用
官方解釋,這兩個hook基本相同,調(diào)用時機(jī)不同,請全部使用useEffect,除非遇到bug或者不可解決的問題,再考慮使用useLayoutEffect。
原理
原理跟useEffect一樣,只是調(diào)用時機(jī)不同
上面說到useEffect的調(diào)用時機(jī)是瀏覽器渲染結(jié)束后執(zhí)行的,而useLayoutEffect是在DOM構(gòu)建完成,瀏覽器渲染前執(zhí)行的。
所以這里需要把宏任務(wù)setTimeout改成微任務(wù)
import React, { useState} from 'react'; import ReactDOM from 'react-dom'; let lastEffectDependencies function useLayouyEffect(callback,dependencies){ if(lastEffectDependencies){ let changed = !dependencies.every((item,index)=>{ return item === lastEffectDependencies[index] }) if(changed){ Promise.resolve().then(callback()) lastEffectDependencies = dependencies } }else{ Promise.resolve().then(callback()) lastEffectDependencies = dependencies } } function App() { let [number, setNumber] = useState(0) useLayouyEffect(()=>{ console.log(number); },[number]) return ( <div> <h2>{number}</h2> <button onClick={() => setNumber(number+1)}>+</button> </div> ) } function render() { ReactDOM.render( <App />, document.getElementById('root') ); } render()
到此,相信大家對“ReactHook核心原理是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
分享標(biāo)題:ReactHook核心原理是什么
瀏覽路徑:http://aaarwkj.com/article46/iijdhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、搜索引擎優(yōu)化、網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作、網(wǎng)站導(dǎo)航、外貿(mào)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)