【useState原理】源码调试吃透REACT-HOOKS(一)

2022-08-12 11:48:02 浏览数 (1)

【useState原理】源码调试吃透REACT-HOOKS(一)

1 导读

2022年了,用React开发不使用hook是不行的。同时一方面,由于我在日常开发中已经许久没有使用class组件,所以一直对于hook的设计理念、实现原理和相关源码有一定的兴趣。原因无他,用hook真的太爽了。

开始之前,先抛出几个问题:

  1. react-hook解决了什么问题?
  2. react中的函数是无状态的,hook是怎么做到赋予其状态的?
  3. 典型问题:为什么hook必须在顶层调用?-->引申:在函数组件中多个hook是怎么记录的
  4. useMemouseCallback是怎么做缓存的?
  5. hook的调用过程,从挂载、首次渲染、二次渲染到销毁的流程?
  6. ······

从这里开始,我们一一解答。

2 HOOK的相关概念

协调器目录 github.com/facebook/re…

2.1 为什么要在react中引入hooks?

不知道诸位有没有使用class组件的经历,属实是又臭又长,繁多且命名复杂的生命周期给开发者带来的体验并不好。在这之前的function组件由于没有状态的概念,只能用来承载简单的UI,这显然不行,react的数据驱动意味着状态逻辑实际上是无处不在的。

依据官方文档的解释,引入hook解决了三个以及更多的问题

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的class

实际体现上,我也无比认同引入hook的实际效果

  • hook的引入使我们在无需修改组件结构的情况下即可复用状态逻辑,不管是在跨层级状态共享还是复杂逻辑抽象上都有了质的提高
  • 我们在使用函数式组件时不再关注生命周期,只要保证hook在最顶层即可在函数中将和组件相关联的部分自由地拆分
  • hook 使你在非 class 的情况下可以使用更多的 React 特性

2.2 Fiber结构

我有一篇文章讲的是Fiber结构的实现https://juejin.cn/post/7030069221342052389,这里只给一下代码:

代码语言:javascript复制
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 实例变量,从字面意思也应该可以看出这里保存了tag、key、type、state类似这样的有很强实际意义的属性
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;
	...
}

除了Fiber结构,还需要强调的一点是,react16.8之后的Fiber架构:

  • Scheduler(调度器),还没看到请忽略,请记住这个概念
  • Reconciler(协调器)
  • Renderer(渲染器)

3 hook是怎么赋予函数式组件状态的?

开始之前,贴一下我整个hook篇的调试代码

代码语言:javascript复制
import {useState, useEffect, useRef} from 'react';

const useMockRef = init => {
  const [ref] = useState({current: init});
  return ref;
};

const CountButton = () => {
  const [count, setCount] = useState(0);
  const [tick, setTick] = useState(0);

  const realRef = useRef(0);
  const mockRef = useMockRef(0);

  const handleClick = () => {
    setCount(count   1);
    mockRef.current  = 1;
    realRef.current  = 1;
  };

  const handleRefCountClick = () => {
    mockRef.current  = 10;
    realRef.current  = 10;
    console.log('mockRef.current', mockRef.current);
  };

  useEffect(() => {
    setTick(count   Math.random());
  }, [count]);
  return (
    <>
      <button onClick={handleClick}>{count}:Render by state</button>
      <button onClick={handleRefCountClick}>Click to add ref</button>
      <div style={{color: 'red'}}>{tick}</div>
      <div style={{color: 'yellow'}}>{mockRef?.current}</div>
      <div style={{color: 'green'}}>{realRef?.current}</div>
    </>
  );
};

export {CountButton};

进入到这里,我们需要进入到react源码的部分,这里我们需要关注的是FiberBeginWork

github.com/facebook/re…

调试代码

代码语言:javascript复制
const CountButton = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count   1);
  };
  return (
    <>
      <button onClick={handleClick}>{count}:Render by state</button>
    </>
  );
};

3.1 beginWork

在之前的文章中,我们其实已经对Fiber有一定的了解,也知道了在react中一个Fiber其实也就是对应一个虚拟DOM。那么我们现在看到function beginWork #L3829

代码语言:javascript复制
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
    ...
}

可以发现beginWork的入参为「current、workInProgress、renderLanes」,前两者对应react架构中的两颗Fiber树renderLanes则是和优先级相关的参数,和Scheduler相关,这一部分我还没仔细研究

0 人点赞