手写ReactHook核心原理,再也不怕面试官问我ReactHook原理

本文详细解释了React中的高级Hook函数useCallback,useMemo,useReducer,useContext以及useEffect的工作原理和用法,包括它们如何处理依赖项变化和缓存策略。
摘要由CSDN通过智能技术生成

);

}

render()

useCallback钩子的第一个参数是我们要传递给子组件的方法,第二个参数是一个数组,用于监听数组里的元素变化的时候,才会返回一个新的方法。

原理实现

我们知道useCallback有两个参数,所以可以先写

function useCallback(callback,lastCallbackDependencies){

}

跟useState一样,我们同样需要用全局变量把callback和dependencies保存下来。

let lastCallback

let lastCallbackDependencies

function useCallback(callback,dependencies){

}

首先useCallback会判断我们是否传入了依赖项,如果没有传的话,说明要每一次执行useCallback都返回最新的callback

let lastCallback

let lastCallbackDependencies

function useCallback(callback,dependencies){

if(lastCallbackDependencies){

}else{ // 没有传入依赖项

}

return lastCallback

}

所以当我们没有传入依赖项的时候,实际上可以把它当作第一次执行,因此,要把lastCallback和lastCallbackDependencies重新赋值

let lastCallback

let lastCallbackDependencies

function useCallback(callback,dependencies){

if(lastCallbackDependencies){

}else{ // 没有传入依赖项

lastCallback = callback

lastCallbackDependencies = dependencies

}

return lastCallback

}

当有传入依赖项的时候,需要看看新的依赖数组的每一项和来的依赖数组的每一项的值是否相等

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 (

child

)

}

当依赖项有值改变的时候,我们需要对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 (

child

)

}

// 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 (

<button onClick={() => { setCount(count + 1)}}>

增加

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

手写useMemo


使用

useMemo和useCallback类似,不过useCallback用于缓存函数,而useMemo用于缓存函数返回值

let data = useMemo(()=> ({number}),[number])

如代码所示,利用useMemo用于缓存函数的返回值number,并且当只有监听元素为[number],也就是说,当number的值发生改变的时候,才会重新执行

()=> ({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 (

child

)

}

// 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 (

<button onClick={() => { setCount(count + 1)}}>

增加

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

手写useReducer


使用

先简单介绍下useReducer。

const [state, dispatch] = useReducer(reducer, initState);

useReducer接收两个参数:

第一个参数:reducer函数,第二个参数:初始化的state。

返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。

按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。

听起来比较抽象,我们先看一个简单的例子:

// 官方 useReducer Demo

// 第一个参数:应用的初始化

const initialState = {count: 0};

// 第二个参数:state的reducer处理函数

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函数

const [state, dispatch] = useReducer(reducer, initialState);

return (

<>

// useReducer会根据dispatch的action,返回最终的state,并触发rerender

Count: {state.count}

// dispatch 用来接收一个 action参数「reducer中的action」,用来触发reducer函数,更新最新的状态

<button onClick={() => dispatch({type: ‘increment’})}>+

<button onClick={() => dispatch({type: ‘decrement’})}>-

</>

);

}

其实意思可以简单的理解为,当state是基本数据类型的时候,可以用useState,当state是对象的时候,可以用reducer,当然这只是一种简单的想法。大家不必引以为意。具体情况视具体场景分析。

原理

看原理你会发现十分简单,简单到不用我说什么,不到十行代码,不信你直接看代码

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

// 第一个参数:应用的初始化

const initialState = {count: 0};

// 第二个参数:state的reducer处理函数

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函数

const [state, dispatch] = useReducer(reducer, initialState);

return (

<>

{/* // useReducer会根据dispatch的action,返回最终的state,并触发rerender */}

Count: {state.count}

{/* // dispatch 用来接收一个 action参数「reducer中的action」,用来触发reducer函数,更新最新的状态 */}

<button onClick={() => dispatch({type: ‘increment’})}>+

<button onClick={() => dispatch({type: ‘decrement’})}>-

</>

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

手写useContext


使用

createContext 能够创建一个 React 的 上下文(context),然后订阅了这个上下文的组件中,可以拿到上下文中提供的数据或者其他信息。

基本的使用方法:

const MyContext = React.createContext()

如果要使用创建的上下文,需要通过 Context.Provider 最外层包装组件,并且需要显示的通过 <MyContext.Provider value={{xx:xx}}> 的方式传入 value,指定 context 要对外暴露的信息。

子组件在匹配过程中只会匹配最新的 Provider,也就是说如果有下面三个组件:ContextA.Provider->A->ContexB.Provider->B->C

如果 ContextA 和 ContextB 提供了相同的方法,则 C 组件只会选择 ContextB 提供的方法。

通过 React.createContext 创建出来的上下文,在子组件中可以通过 useContext 这个 Hook 获取 Provider 提供的内容

const {funcName} = useContext(MyContext);

从上面代码可以发现,useContext 需要将 MyContext 这个 Context 实例传入,不是字符串,就是实例本身。

这种用法会存在一个比较尴尬的地方,父子组件不在一个目录中,如何共享 MyContext 这个 Context 实例呢?

一般这种情况下,我会通过 Context Manager 统一管理上下文的实例,然后通过 export 将实例导出,在子组件中在将实例 import 进来。

下面我们看看代码,使用起来非常简单

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 })}>+

</>

);

}

function App() {

let [state, setState] = useState({ number: 0 })

return (

<AppContext.Provider value={{ state, setState }}>

{state.number}

</AppContext.Provider>

)

}

function render() {

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

要是用过vue的同学,会发现,这个机制有点类似vue 中提供的provide和inject

原理

原理非常简单,由于createContext,Provider 不是ReactHook的内容,

所以这里值需要实现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 })}>+

</>

);

}

function App() {

let [state, setState] = useState({ number: 0 })

return (

<AppContext.Provider value={{ state, setState }}>

{state.number}

</AppContext.Provider>

)

}

function render() {

ReactDOM.render(

,

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 (

{number}

<button onClick={() => setNumber(number+1)}>+

)

}

function render() {

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

如代码所示,支持两个参数,第二个参数也是用于监听的。

当监听数组中的元素有变化的时候再执行作为第一个参数的执行函数

原理

原理发现其实和useMemo,useCallback类似,只不过,前面前两个有返回值,而useEffect没有。(当然也有返回值,就是那个执行componentWillUnmount函功能的时候写的返回值,但是这里返回值跟前两个作用不一样,因为你不会写

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 (

{number}

<button onClick={() => setNumber(number+1)}>+

)

}

function render() {

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

你以为这样就结束了,其实还没有,因为第一个参数的执行时机错了,实际上作为第一个参数的函数因为是在浏览器渲染结束后执行的。而这里我们是同步执行的。

所以需要改成异步执行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 (

{number}

<button onClick={() => setNumber(number+1)}>+

)

}

function render() {

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

手写useLayoutEffect


使用

学习笔记

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue等等

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

HTML/CSS

**HTML:**HTML基本结构,标签属性,事件属性,文本标签,多媒体标签,列表 / 表格 / 表单标签,其他语义化标签,网页结构,模块划分

**CSS:**CSS代码语法,CSS 放置位置,CSS的继承,选择器的种类/优先级,背景样式,字体样式,文本属性,基本样式,样式重置,盒模型样式,浮动float,定位position,浏览器默认样式

HTML5 /CSS3

**HTML5:**HTML5 的优势,HTML5 废弃元素,HTML5 新增元素,HTML5 表单相关元素和属性

**CSS3:**CSS3 新增选择器,CSS3 新增属性,新增变形动画属性,3D变形属性,CSS3 的过渡属性,CSS3 的动画属性,CSS3 新增多列属性,CSS3新增单位,弹性盒模型

JavaScript

**JavaScript:**JavaScript基础,JavaScript数据类型,算术运算,强制转换,赋值运算,关系运算,逻辑运算,三元运算,分支循环,switch,while,do-while,for,break,continue,数组,数组方法,二维数组,字符串

console.log(number);

},[number])

return (

{number}

<button onClick={() => setNumber(number+1)}>+

)

}

function render() {

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

手写useLayoutEffect


使用

学习笔记

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue等等

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

HTML/CSS

**HTML:**HTML基本结构,标签属性,事件属性,文本标签,多媒体标签,列表 / 表格 / 表单标签,其他语义化标签,网页结构,模块划分

**CSS:**CSS代码语法,CSS 放置位置,CSS的继承,选择器的种类/优先级,背景样式,字体样式,文本属性,基本样式,样式重置,盒模型样式,浮动float,定位position,浏览器默认样式

[外链图片转存中…(img-jrb94Tro-1714717805201)]

HTML5 /CSS3

**HTML5:**HTML5 的优势,HTML5 废弃元素,HTML5 新增元素,HTML5 表单相关元素和属性

**CSS3:**CSS3 新增选择器,CSS3 新增属性,新增变形动画属性,3D变形属性,CSS3 的过渡属性,CSS3 的动画属性,CSS3 新增多列属性,CSS3新增单位,弹性盒模型

[外链图片转存中…(img-CNqlz4WV-1714717805202)]

JavaScript

**JavaScript:**JavaScript基础,JavaScript数据类型,算术运算,强制转换,赋值运算,关系运算,逻辑运算,三元运算,分支循环,switch,while,do-while,for,break,continue,数组,数组方法,二维数组,字符串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值