React源码解析18(6)------ 实现useState

这篇具有很好参考价值的文章主要介绍了React源码解析18(6)------ 实现useState。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

摘要

在上一篇文章中,我们已经实现了函数组件。同时可以正常通过render进行渲染。

而通过之前的文章,beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。

实现之前,我们要先修改一下我们的index.js文件:

import jsx from '../src/react/jsx.js'
import ReactDOM from '../src/react-dom/index'
import { useState } from './react-dom/filberHook.js';

const root = document.querySelector('#root');

function App() {
  const [name, setName] = useState('kusi','key');
  window.setName = setName;
  const [age, setAge] = useState(20)
  window.setAge = setAge;
  return jsx("div", {
    ref: "123",
    children: jsx("span", {
      children: name + age
    })
  });
}

ReactDOM.createRoot(root).render(<App />)

由于我们这一篇并不会实现React的事件机制,所以我们先将setState的方法挂载在window上进行调试。有了基础,我们现在开始实现useState。

1.renderWithHook

在实现之前,我们先来思考一个问题。在之前实现beginWork机制的时候,我们为了兼容函数组件。获取子FilberNode的时候,函数组件是直接调用拿到返回值。
那么如果函数直接调用,是不是就已经调用了我们在函数里写的Hook。
所以我们把这一部分拆出来:

function updateFunctionComponent(filberNode) {
  const nextChildren = renderWithHook(filberNode);
  const newFilberNode = reconcileChildren(nextChildren);
  filberNode.child = newFilberNode;
  newFilberNode.return = filberNode;
  beginWork(newFilberNode)
}

在更新函数节点的时候,通过renderWithHook拿到函数执行的返回值:
那我们在renderWithHook里除了拿到函数执行的返回值,还要做什么呢?

这里值得注意的是,我们知道通过setState,函数组件会重新执行渲染。在这里,我们将函数的执行分为两种:第一次mount和后面的update。

就是执行useState这个过程,要分为两种,一种是mount下的useState,一种是update下的useState。OK,现在我们用一个标志去表示这两种状态,并且在renderWithHook下去改变它。

let hookWithStatus;
let workInPropgressFilber = null;

export const renderWithHook = (filberNode) => {
  if(filberNode.child){
    //更新
    hookWithStatus = 'update'
  }else{
    //mount
    hookWithStatus = 'mount'
  }
  workInPropgressFilber = filberNode;
  const nextChildren = filberNode.type();
  return nextChildren;
}

2.实现mountState和Hook结构

现在我们在beginWork执行完后,会执行renderWithHook,执行后会改变hookWithStatus这个标志。再然后就是调用函数本身了。
所以现在我们根据这个标志实现两种不同的useState:

export const useState = (state) => {
  if(hookWithStatus === 'mount'){
    return mountState(state)
  }else if(hookWithStatus === 'update'){
    return updateState(state)
  }
}

也就是页面第一次渲染时,执行函数组件里的内容,我们要调用mountState。现在我们实现mountState。

实现之前,我们先说一下在React中,是如何将组件中的Hook存储的。在React中是通过链表的方式,将不同的Hook存储起来。现在我们定义一下Hook的结构:

它具有三个属性。memoizedState表示存储的state值,updateQueue表示需要更新的值,next表示指向的下一个hook。

class Hook {
  constructor(memoizedState, updateQueue, next){
    this.memoizedState = memoizedState
    this.updateQueue = updateQueue
    this.next = next;
  }
}

所以在mountStaet中,我们要将这个链表结构实现出来:
这里我们定义一个headHook指向最外层的hook,workinProgressHook指向当前的hook。

function mountState(state) {
  const memoizedState = typeof state === 'function' ? state() : state;
  const hook = new Hook(memoizedState);
  hook.updateQueue= createUpdateQueue()
  if(workInPropgressHook === null){
    workInPropgressHook = hook;
    headHook = hook;
  }else{
    workInPropgressHook.next = hook;
    workInPropgressHook = hook;
  }
  return [memoizedState]
}

现在我们可以看一下HOOK的结构:

React源码解析18(6)------ 实现useState,react.js,javascript,前端
可以看出它是一个链表的结构,memoizedState保存的就是setState的初始值。

3.实现dispach更新

现在经过mount阶段后,我们已经有了一个基本的Hook链表。现在如果我在window下调用setState,那肯定是什么都不会发生的。

所以我们要实现setState方法,但是要调用setState方法是一定要更新的,所以我们将beginWork中的updateContainer方法修改一下,并且暴露出来:

function updateContainer(root, element) {
  const hostRootFilber = root.current;
  const update = createUpdate(element);
  hostRootFilber.updateQueue = createUpdateQueue()
  enqueueUpdate(hostRootFilber.updateQueue, update);
  wookLoop(root,hostRootFilber)
}

export const wookLoop = (root,hostRootFilber) => {
  if(!hostRootFilber){
    hostRootFilber = root.current
  }
  beginWork(hostRootFilber);
  completeWork(hostRootFilber);
  root.finishedWork = hostRootFilber;
  console.log(root)
  commitWork(root)
}

这样我就可以在hook的机制里面调用wookLoop了。现在我们实现dispatch:

function disaptchState(filber, hook, action) {
  const update = createUpdate(action);
  enqueueUpdate(hook.updateQueue, update);
  workUpdateHook = hook;
  wookLoop(filber.return.stateNode)
}

dispatchState方法传入当前的filberNode, 还有就是对应的hook,以及需要更新的action。
同时我们将准备更新的hook进行标记。

所以在mountState中:

function mountState(state) {
  const memoizedState = typeof state === 'function' ? state() : state;
  const hook = new Hook(memoizedState);
  //其他代码。。。
  const disaptch = disaptchState.bind(null,workInPropgressFilber,hook)
  return [memoizedState,disaptch]
}

我们将dispatch需要的参数传进去,并且只给外面放开action。这样就实现好了dispatch方法。

4.实现updateState方法

当我们将上面的过程实现完之后,如果在控制台调用setState。那么就会触发workLoop,同时会再走一次beginWork。
此时再进入renderWithHook之后,就不会再走mountState了,而是进入updateState。

而在updateState中,我们要做的事情也不是很复杂,只需要从头遍历Hook链表,如果是标记更新的Hook,就返回更新的内容。如果不是,就正常返回它的memoizedState就好了。

function updateState(state) {
  if(currentHook === workUpdateHook){
    const newHook = new Hook(workUpdateHook.updateQueue.shared.pending.action)
    newHook.updateQueue = createUpdateQueue();
    const disaptch = disaptchState.bind(null,workInPropgressFilber,newHook)
    currentHook = currentHook.next;
    const result = [workUpdateHook.updateQueue.shared.pending.action,disaptch];
    return result;
  }else{
    let result = currentHook.memoizedState;
    const disaptch = disaptchState.bind(null,workInPropgressFilber,currentHook)
    currentHook = currentHook.next;
    return [result,disaptch]
  }
}

所以这也是为什么,在React中,不能在条件语句里面使用Hook,如果你mountState生成的Hook链表会发生变化。那么在updateState里面,遍历链表的时候,就会出现值错位的情况。

OK,到这里useState的方法也已经实现完了。文章来源地址https://www.toymoban.com/news/detail-649414.html

到了这里,关于React源码解析18(6)------ 实现useState的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • react18中,useState 和 useEffect有什么区别

    useState 目的:useState用于在函数组件中添加状态。之前,只有类组件才能有自己的状态,但useState钩子使得函数组件也能够利用React的状态特性。 使用场景:当你需要在组件中存储、读取或更新一些数据时使用。例如,控制输入框的内容、切换按钮的状态等。 工作原理:useS

    2024年02月19日
    浏览(40)
  • React源码解析18(1)------ React.createElement 和 jsx

    我们知道在React17版本之前,我们在项目中是一定需要引入react的。 import React from “react” 即便我们有时候没有使用到React,也需要引入。原因是什么呢? 在React项目中,如果我们使用了模板语法JSX,我们知道它要先经过babel的转译。那babel会将JSX转换成什么样子的格式呢? 可

    2024年02月13日
    浏览(37)
  • React Hook - useState函数的详细解析

    在上一篇文章中, 我用到useState来让大家体验一下hooks函数 那么接下来我们来先研究一下上面核心的一段代码代表什么意思 useState来自react,需要从react中导入,是一个hook函数, 官方中也将它成为State Hook, 它与class组件里面的 this.state 提供的功能完全相同; 一般来说,在函数退出

    2024年01月25日
    浏览(43)
  • React源码解析18(2)------ FilberNode,FilberRootNode结构关系

    在上一篇,我们实现了通过JSX转换为ReactElement的方法,也看到了转换后React元素的结构。但是这个React元素,并不能很清楚的表达组件之间的关系,以及属性的处理。 所以在React内部,会将所有的React元素转换为Filber树。而这一章节,主要就是简单描述一下FilberNode的结构。 首

    2024年02月13日
    浏览(34)
  • React源码解析18(4)------ completeWork的工作流程【mount】

    经过上一章,我们得到的FilberNode已经具有了child和return属性。一颗Filber树的结构已经展现出来了。 那我们最终是想在页面渲染真实的DOM。所以我们现在要在completeWork里,构建出一颗离屏的DOM树。 之前在说FilberNode的属性时,我们提到过一个属性stateNode。它就是用来保存每个

    2024年02月13日
    浏览(38)
  • React源码解析18(3)------ beginWork的工作流程【mount】

    OK,经过上一篇文章。我们调用了: 生成了FilberRootNode和HostRootFilber。 并且二者之间的对应关系也已经确定。 而下一步我们就需要调用render方法来讲react元素挂载在root上: 所以我们现在要实现ReactDOM.createRoot中返回的render方法。 首先我们思考一下,在React中,除了通过上面的

    2024年02月13日
    浏览(48)
  • react 之 useState

    参考:https://blog.csdn.net/Ljwen_/article/details/125319191 一、基本使用 useState是 react 提供的一个定义响应式变量的 hook 函数,基本语法如下: 它返回一个状态和一个修改状态的方法,状态需要通过这个方法来进行修改; initialCount 是我们传入的一个初始状态,它是惰性的,我们可以

    2023年04月23日
    浏览(36)
  • react之useState详解

    它是React 16.8 的新特性,我们在React里面一般都是用Class去写组件,但是有时候会发现不用它更方便,但是问题来了: 不用Class的时候,数据的状态改变如何存储呢? 如果你用 var,let,const 等等,你会发现:值虽然改变了,但是页面并没有重新渲染,也就是说, 这些定

    2024年04月25日
    浏览(35)
  • React -- useState使用方法

    userState 是一个React Hook (函数)。它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果 1. useState是一个函数,返回值是一个数组 2. 数组中第一个参数是状态变量,第二个参数是set开头的函数名,用于修改变量 3. useState的参数作为count的初始值 4.使用举例

    2024年02月19日
    浏览(35)
  • [react] useState的一些小细节

    因为setState修改是异步的,加上会触发函数重新渲染, 如果代码长这样  一秒再修改,然后重新触发setTImeout, 然后再触发,重复触发循环 如果这样呢 还是会,因为你执行又会重新渲染  为什么修改多次还是跟不上呢? 因为是异步修改 ,所以会出现问题,怎么办?用传函数的形式解决 C

    2024年04月13日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包