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

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

当我们使用这个方法时,如果是第一次使用,则取initState的值,否则就取上一次的值(laststate).

在内部,我们创建了一个setState方法,该方法用于更新state的值

然后返回一个lastSate属性和setState方法。

看似完美,但是我们其实忽略了一个问题:每次执行玩setState都应该重新渲染当前组件的。

所以我们需要在setState里面执行刷新操作

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 (

{count}

<button

onClick={() => {

setCount(count + 1);

}}

增加

);

}

// 新增方法

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

如代码所示,我们在setState里添加了个render方法。

render方法则会执行

ReactDOM.render(

,

document.getElementById(‘root’)

);

也就是重新渲染啦。

好了,现在是不是已经完整了呢?

不,还有个问题:就说我们这里只是用了一个useState,要是我们使用了很多个呢?难道要声明很多个全局变量吗?

这显然是不行的,所以,我们可以设计一个全局数组来保存这些state

let lastState = []

let stateIndex = 0

function useState(initState) {

lastState[stateIndex] = lastState[stateIndex] || initState;

const currentIndex = stateIndex

function setState(newState) {

lastState[currentIndex ] = newState

render()

}

return [lastState[stateIndex++],setState]

}

这里的currentIndex是利用了闭包的思想,将某个state相应的index记录下来了。

好了,useState方法就到这里基本完成了。是不是so easy!!!

React.memo介绍


看下面的代码!你发现什么问题?

import React ,{useState}from ‘react’;

import ReactDOM from ‘react-dom’;

import ‘./index.css’;

function Child({data}) {

console.log(“天啊,我怎么被渲染啦,我并不希望啊”)

return (

child

)

}

function App(){

const [count, setCount] = useState(0);

return (

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

增加

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

没错,就是尽管我们传个子组件的props是固定的值,当父组件的数据更改时,子组件也被重新渲染了,我们是希望当传给子组件的props改变时,才重新渲染子组件。

所以引入了React.memo。

看看介绍

React.memo() 和 PureComponent 很相似,它帮助我们控制何时重新渲染组件。

组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会走一遍渲染流程。但是通过 PureComponent 和 React.memo(),我们可以仅仅让某些组件进行渲染。

import React ,{useState,memo}from ‘react’;

import ReactDOM from ‘react-dom’;

import ‘./index.css’;

function Child({data}) {

console.log(“天啊,我怎么被渲染啦,我并不希望啊”)

return (

child

)

}

Child = memo(Child)

function App(){

const [count, setCount] = useState(0);

return (

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

增加

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

因此,当Child被memo包装后,就只会当props改变时才会重新渲染了。

当然,由于React.memo并不是react-hook的内容,所以这里并不会取讨论它是怎么实现的。

手写useCallback


useCallback的使用

当我们试图给一个子组件传递一个方法的时候,如下代码所示

import React ,{useState,memo}from ‘react’;

import ReactDOM from ‘react-dom’;

function Child({data}) {

console.log(“天啊,我怎么被渲染啦,我并不希望啊”)

return (

child

)

}

// eslint-disable-next-line

Child = memo(Child)

function App(){

const [count, setCount] = useState(0);

const addClick = ()=>{console.log(“addClick”)}

return (

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

增加

);

}

function render(){

ReactDOM.render(

,

document.getElementById(‘root’)

);

}

render()

发现我们传了一个addClick方法 是固定的,但是却每一次点击按钮子组件都会重新渲染。

这是因为你看似addClick方法没改变,其实旧的和新的addClick是不一样的,如图所示

在这里插入图片描述

这时,如果想要,传入的都是同一个方法,就要用到useCallBack。

如代码所示

import React ,{useState,memo,useCallback}from ‘react’;

import ReactDOM from ‘react-dom’;

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

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


使用

最后

除了简历做到位,面试题也必不可少,整理了些题目,前面有117道汇总的面试到的题目,后面包括了HTML、CSS、JS、ES6、vue、微信小程序、项目类问题、笔试编程类题等专题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ter() {

// 返回值:最新的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


使用

最后

除了简历做到位,面试题也必不可少,整理了些题目,前面有117道汇总的面试到的题目,后面包括了HTML、CSS、JS、ES6、vue、微信小程序、项目类问题、笔试编程类题等专题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-aLV21awo-1713085717117)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值