Skip to content

React ​

JSX.Element 与 React.ReactNode 与 React.ReactElement ​

jsx 文件就是实现 React.createElement() 的一个语法糖。 React.createElement 方法会根据传入 type 的类型来区分返回普通的 HTMLElement 或者是 React.ReactElement 实例

React.ReactElement 在使用上就是一个含有 type、props、key 的实例对象,由 React 最终渲染成真实的 DOM 元素

ts
// index.d.ts
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
  type: T
  props: P
  key: Key | null
}

// Playground.tsx
const element: React.ReactElement = React.createElement("div", { className: "test", id: "unique" }, "hello world")
ReactDOM.render(element, document.getElementById("render-container"))
console.log(element)

实际的 ReactElement 对象则具有以下元素:ReactElement

React.ReactNode 是一种联合类型,并且继承了 React.ReactElement

ts
type ReactText = string | number
type ReactChild = ReactElement | ReactText
// ...
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined

JSX.Element 就是通过继承 React.ReactNode 实现的,没有任何区别

区分 class 组件和 function 组件 ​

How Does React Tell a Class from a Function?

为什么要区分 ​

箭头函数的组件没有 this,new 会报错

怎么区分 ​

class 组件 会继承 React.Component,MyComponent instanceof React.Component

js
// Inside React
class Component {}
Component.prototype.isReactComponent = {}

// We can check it like this
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent)

ref ​

class 组件 ​

jsx
import React from "react"

export default class Test extends React.Component {
  ref = React.createRef()

  componentDidUpdate() {
    this.ref.current.focus()
  }

  render() {
    return <input ref={this.ref} />
  }
}

函数组件 ​

jsx
import { useRef } from "react"

export default function Test() {
  const ref = useRef()

  useEffect(() => {
    this.ref.current.focus()
  }, [])

  return <input ref={this.ref} />
}

函数组件不会暴露出自己的实例,需要使用 forwardRef 来包装组件

jsx
import { forwardRef, useImperativeHandle } from "react"

function Child(props, ref) {
  useImperativeHandle(ref, () => {
    return {
      onXXX,
      onResetXXX,
      XXX,
    }
  })

  return <>...</>
}

export default forwardRef(Child)

Fiber ​

tree

由于 react 和 vue 的响应式实现原理不同,react 每次更新都需要渲染一颗更大的虚拟 DOM 树

fiber tree

在 fiber 出现之前,react 的虚拟 DOM 树只有指向子节点的指针,所以中断渲染,暂存当前的 DOM 节点信息就会丢失父节点和兄弟节点的信息,无法完成遍历
通过 requestIdleCallback 来控制遍历的进度条,决定是否让出线程给其他操作

React 中最多会存在两颗 Fiber 树

  • currentFiber:页面中显示的内容
  • workInProgressFiber:内存中正在重新构建的 Fiber 树

双缓存 Fiber 树 ​

在 React 中最多会同时存在两棵 Fiber 树。当前屏幕上显示内容对应的 Fiber 树称为 current Fiber 树,正在内存中构建的 Fiber 树称为 workInProgress Fiber 树
React 应用的根节点通过 current 指针在不同 Fiber 树的 rootFiber 间切换来实现 Fiber 树的切换
当 workInProgress Fiber 树构建完成交给 Renderer 渲染在页面上后,应用根节点的 current 指针指向 workInProgress Fiber 树,此时 workInProgress Fiber 树就变为 current Fiber 树

Hooks 链表 ​

hooks mount 阶段 ​

当函数式组件初始化时,会调用 renderWithHooks 函数,初始化走 HooksDispatcherOnMount,后续更新走 HooksDispatcherOnUpdate

初始化时,会调用 mountWorkInProgressHook 创建 hook 链表节点,挂载到 fiber 节点的 memoizedState 上(Fiber 上的 memoizedState 指向 hooks 链表,hook 身上的 memoizedState 存储他们自己的状态)

hooks update 阶段 ​

调用 updateWorkInProgressHook 克隆 hook 节点,进行新旧比较

  1. 由 nextCurrentHook 中间变量 记录旧的 hooks 链表
  2. 由 nextWorkInProgressHook 中间变量 克隆旧的 hook 节点形成新的 hooks 链表
  3. currentHook 指向旧 hooks 链表;workInProgressHook 指向新的 hooks 链表,返回 workInProgressHook

合成事件 ​

React 事件系统工作原理

v16 之前所有事件统一绑定在 document 上
v17 绑定在 ReactDOM.render(app, container) 的 container 上

事件绑定 ​

  1. diff 时判断 dom 的 props 是否有合成事件(onXxx)
  2. 判断事件是否有对应的原生事件
  3. 调用 addTrappedEventListener 将真正的事件绑定在顶层

事件触发 ​

  1. 任意一个事件触发,执行 dispatchEvent 函数
  2. 检测到自己需要处理的事件类型时,处理该事件
  3. 找到对应 DOM 节点
  4. 反向触发合成事件链,捕获
  5. 正向触发合成事件链,冒泡

彻底搞懂 React 18 并发机制的原理 ​

异步获取 state ​

ts
// function
function fn1() {
  setCount(count + 1)
}
function fn2() {
  setTimeout(() => {
    alert(count)
  }, 3000)
}

// class
fn1 = () => {
  this.setState({ count: this.state.count + 1 })
}
fn2 = () => {
  setTimeout(() => {
    alert(this.state.count)
  }, 3000)
}

在 function 中,每次页面更新都会重新触发 function,生成新的函数,异步的 alert 只能拿到那个时候执行的 state。
在 class 中,始终是同一个 class 实例,每次页面更新只会重新出发 render 函数,通过 this 拿到的是最新的 state。
useState 通过链表的结构存在对应的 fiber 节点上,按顺序区分哪个 state 是哪个,所以不允许 if

useEffect 和 useMemo 区别 ​

  • useEffect 是在 DOM 改变之后触发,useMemo 在 DOM 渲染之前就触发,类似 shouldComponentUpdate 生命周期
  • 在 useMemo 中使用 setState 会产生死循环,并且会有警告,因为 useMemo 是在渲染中进行的,你在其中操作 DOM 后,又会导致触发 memo

key ​

在 diff 算法的实现过程中,如果 key 和 element type 都是相同的话,则通过 useFiber() 函数,基于旧 fiber 节点和新 element 的 props 来复用旧 fiber 节点,否则直接删除旧 fiber 节点,创建新的 fiber 节点。而复用或者创建新的 fiber 节点就意味着复用旧的 DOM 对象或者创建新的 DOM 对象(HostComponent 类型的 fiber 节点所关联的 DOM 对象存放在它的 stateNode 属性上)。

useMemoizedFn 替代 useCallback ​

ts
function useMemoizedFn(func) {
  if (typeof func !== "function") return

  // 通过 useRef 保持其引用地址不变,并且值能够保持值最新
  const funcRef = useRef(func)
  funcRef.current = useMemo(() => {
    return func
  }, [func])

  const memoizedFn = useRef()

  if (!memoizedFn.current) {
    // 返回的持久化函数,调用该函数的时候,调用原始的函数
    memoizedFn.current = function (...args) {
      return funcRef.current.apply(this, args)
    }
  }

  return memoizedFn.current
}