tiny-react助你看懂react源码

975 阅读3分钟

tiny-react一个基于React17精简而来的仓库

tiny-react是一个为了简化react源码学习的库,和react17的区别就是少了很多功能,只实现了核心的逻辑,和preact这种react-like库有着根本区别,preact更像是一个和react有着相同的接口但是实现细节却不尽相同的react,而tiny-react是从react官方仓库精简而来,它更像官方react的阉割版,所以每一行代码,每一个函数都能在react最新的官方仓库中找到出处,而且总共的代码只有6千多行,刨除掉ReactDOM只有4000多行,能让React源码学习的难度大大降低

使用指南

  • 在阅读源码前,你需要对react的大体原理有一定的了解在这里强烈推荐去通读一下卡颂老师的React技术揭秘
  • 对react的大体原理有一定了解后就可以开始看源码了,不过有些同学可能对一些源码中使用频繁的的数据结构和算法还不怎么了解,这时候就可以看一下下面的看源码前需要了解的数据结构和算法,如果你已经非常了解这些知识则可以跳过
  • react-dom这个模块,可以不用太关注,虽然他的代码有2000多行,但是大量的代码都是dom操作和事件委托相关函数,对学习React核心原理影响不大,不过其中 浏览器事件优先级 相关的代码还是要注意一下
  • react-reconciler这个模块需要重点关注特别是其中的 ReactFiberWorkLoop 他是React源码的骨干,把所有的模块连接到了一起
  • scheduler这个模块是代码最少,最简单的模块了,而且基本没有和其他模块耦合,可以直接单独看他的源码

看源码前需要了解的数据结构和算法

Feature

时间切片

/**
 *  打开performance查看Concurrent Mode下render阶段的时间切片
 * 点击一下按钮然后会开始BitList的render阶段,可以看到处于render阶段(也就是点击按钮后的前几秒)时input
 * 是可以输入的,但是由于要渲染的东西太多,到commit阶段时就会开始卡住,
 * 所以此时会卡顿的瓶颈在浏览器渲染太耗时,而不是在react
 */

import React, { useState } from '../packages/react'

const data = Array.from({ length: 50e4 }, (_, i) => i)
const CHUNK_SIZE = 1e4 / 10

/**
 * fiber是最小的工作粒度,如果要保证render过程中能保证浏览器能
 * 处于交互的状态就得保证一个fiber render的过程不会太耗
 * 事件,所以可以根据机能设置合适的CHUNK_SIZE
 * @param param0
 * @returns
 */
const Chunk = ({ start }: { start: number }): any => {
  const end = Math.min(data.length, start + CHUNK_SIZE)
  const children = Array.from({ length: end - start })
  for (let i = start; i < end; ++i) {
    children[i - start] = <div key={i}>{i}</div>
  }

  return children
}

const BigList = () => {
  const children = []

  for (let i = 0; i < data.length; i += CHUNK_SIZE) {
    children.push(<Chunk start={i} />)
  }
  return <div>{children}</div>
}

export const TimeSlicingDemo = () => {
  const [isShowBigList, setIsShowBigList] = useState(false)

  return (
    <div>
      <button
        onClick={() => {
          //由于点击事件内产生的更新会按Sync优先级处理
          //我们手动用setTimeout去掉点击事件的执行上下文
          setTimeout(() => {
            setIsShowBigList(!isShowBigList)
          })
        }}
      >
        toggle BigList
      </button>
      <br />
      <input placeholder="输入点东西,看看交互有没有被阻塞" />
      <br />
      {isShowBigList ? <BigList /> : null}
    </div>
  )
}

image

useState和useEffect

image

useLayoutEffect

image

优先级调度

import React, { useState, useEffect } from '../packages/react'

export const PriorityScheduling = () => {
  const [count, updateCount] = useState(0)

  const onClick = () => {
    updateCount((count) => count + 2)
  }

  console.log({ count })

  useEffect(() => {
    //暂时不支持ref直接用选择器获取
    const button = document.getElementById('discretEventDispatcher')!
    setTimeout(() => updateCount(1), 1000)
    setTimeout(() => {
      button.click()
      //根据机能给第二个setTimeout一个合适的时间,或者适当的加长数组的长度
      //以保证点击事件触发时,前一个低优先级的更新的render阶段还没有完成
      //才能看到插队情况发生
    }, 1030)
  }, [])

  return (
    <div>
      <button id="discretEventDispatcher" onClick={onClick}>
        增加2
      </button>
      <div>
        {Array.from(new Array(10000)).map((v, index) => (
          <span key={index}>{count}</span>
        ))}
      </div>
    </div>
  )
}

image