# React 专题

# 概念

# 受控组件

  • 受控组件 对于某个组件,它的状态是否收到外界的控制,如 input 的值是否受 value 控制,并且改变的值通过 change 改变外界的值
  • 非受控组件 对应地,组件的状态不受外界控制,如 input 只传入 defaultValue 作为初始值

# jsx

jsx 是一种描述当前组件内容的数据结构

# Fiber

  • Fiber 是一种架构

    通过保存的信息链接整个 fiber 树

    // Fiber,用来链接其他fiber节点形成的fiber树
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;
    
  • Fiber 是一种数据结构

    • 储存节点的静态信息以及动态信息

      • 静态信息

        每个 fiber 节点都对应着一个 react element,保存了该组件的类型(函数组件,类组件,原生组件对应的 dom 节点信息)

        // Instance,静态节点的数据结构属性
        this.tag = tag;
        this.key = key;
        this.elementType = null;
        this.type = null;
        this.stateNode = null;
        
      • 动态信息

        保存了每个组件改变的状态,要执行的工作(删除,插入或者更新)

        this.ref = null;
        
        // 作为动态的工作单元的属性
        this.pendingProps = pendingProps;
        this.memoizedProps = null;
        this.updateQueue = null;
        this.memoizedState = null;
        this.dependencies = null;
        
  • Fiber 是一种更新机制

    保存着更新的优先级,副作用等,节点之间通过单向链链接,可随时被高优先级或者申请执行时间不够而被中断

    this.mode = mode;
    
    this.effectTag = NoEffect;
    this.subtreeTag = NoSubtreeEffect;
    this.deletions = null;
    this.nextEffect = null;
    
    this.firstEffect = null;
    this.lastEffect = null;
    
    // 作为调度优先级的属性
    this.lanes = NoLanes;
    this.childLanes = NoLanes;
    
    // 指向该fiber在另一次更新时对应的fiber
    this.alternate = null;
    

# 源码

# render 阶段

# beginWork

beginWork的工作是传入当前Fiber节点,创建子Fiber节点,通过 current===null 判断是 mount 还是 update

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null

# update

满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child

  • didReceiveUpdate === false
    • oldProps === newProps && workInProgress.type === current.type (props 和 fiber.type 不变)
    • !includesSomeLane(renderLanes,updateLanes),当前fiber节点优先级不够

# mount

fiberRootNode以外,会根据fiber.tag不同,创建不同类型的子fiber节点,最终调用的是recocileChildren,最终都会生成新的Fiber节点返回并赋值给workInProgress.child

  • 对于mount的组件,会创建新的fiber节点
  • 对于update的组件,会与current进行对比(diff算法),将比较结果生成新的fiber节点
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes
) {
  if (current === null) {
    // 对于mount的组件
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes
    );
  } else {
    // 对于update的组,会带上effectTag属性
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes
    );
  }
}

# completeWork

beginWork一样通过判断current !== null 来判断是mount 还是update

处理props等参数,提交commit

export const completeWork = (workInProgress: FiberNode) => {
  if (__LOG__) {
    console.log("complete流程", workInProgress);
  }
  const newProps = workInProgress.pendingProps;
  const current = workInProgress.alternate;
  switch (workInProgress.tag) {
    case HostComponent:
      if (current !== null && workInProgress.stateNode) {
        // 更新
        // TODO 更新元素属性
        // 不应该在此处调用updateFiberProps,应该跟着判断属性变化的逻辑,在这里打flag
        // 再在commitWork中更新fiberProps,我准备把这个过程留到「属性变化」相关需求一起做
        updateFiberProps(workInProgress.stateNode, newProps);
      } else {
        // 初始化DOM
        const instance = createInstance(workInProgress.type, newProps);
        // 挂载DOM
        appendAllChildren(instance, workInProgress);
        workInProgress.stateNode = instance;

        // TODO 初始化元素属性
      }
      // 冒泡flag
      bubbleProperties(workInProgress);
      return null;
    case HostRoot:
      bubbleProperties(workInProgress);
      return null;
    case HostText:
      if (current !== null && workInProgress.stateNode) {
        // 更新
        const oldText = current.memoizedProps?.content;
        const newText = newProps.content;
        if (oldText !== newText) {
          markUpdate(workInProgress);
        }
      } else {
        // 初始化DOM
        const textInstance = createTextInstance(newProps.content);
        workInProgress.stateNode = textInstance;
      }

      // 冒泡flag
      bubbleProperties(workInProgress);
      return null;
    case FunctionComponent:
      bubbleProperties(workInProgress);
      return null;
    default:
      console.error("completeWork未定义的fiber.tag", workInProgress);
      return null;
  }
};

# update

判断current的同时还需要判断workInProgress.stateNode,如果不为null则是update,主要处理的 prop:

  • onClickonChange等事件注册
  • 处理style
  • 处理children prop

最终处理完的props会被赋值给workInProgress.updateQueue,最后在commit阶段被渲染,props是一个数组,基数值为key,偶数值为value

# mount

主要逻辑:

  • 通过beginWork生成的 tag 为 fiber 节点生成 DOM 节点
  • 将子 DOM 节点插入到刚刚生成的 DOM 节点中
  • update步骤中的updateHostComponent类似的props处理过程

每次都把生成的 DOM 节点插入到父亲节点中,commit后只需把rootFiberstateNode插入到fiberRoot即可以更新全文档

# effectList

WARNING

v18 已经进行重构,变为subtreeFlags,通过冒泡传递到rootFiber,最终还得遍历树处理effect

关于 effectList 重构为 subtreeFlags (opens new window)

commit阶段需要对存在effectTagfiber节点进行对应的操作,来源一条叫effectList的单向链表

completeWork中的上层completeUnitOfWork中,每执行完completeWork且存在effectTag,就会把fiber节点添加到该链表中

# 触发更新

# 触发更新方式

  • render
  • setState
  • dispatch reducer
  • forceUpdate

# Q&A

# React Fiber作用

  • 作为架构
    在v15的Reconciler采用递归执行,数据存储递归调用栈,被称为Stack Reconciler;在v16+Reconciler基于Fiber节点实现,被成为Fiber Reconciler
  • 作为静态数据结构
    每一个Fiber 节点对应一个React Element,保存了该组件的类型(函数式组件,类组件,原生组件),对应DOM节点等数据
  • 作为动态单元
    每个Fiber节点保存了本次更新中该组件改变的状态,要执行的工作(被删除,被插入,被更新等)

# 更新时wip tree如何生成?

每次触发更新,都会从FiberRootNode开始自定向下的遍历fiber tree执行beginWork,如果遇到的是FC或者是classComponent,则会执行改函数,得到reactElement,与current fiber 进行Reconciler,生成wip的children,在此阶段为fiber打上flagcompleteWork阶段消费

# 优先级插队如何实现?

React每次产生update都会生成对应的优先级,并创建performConcurrentWork函数交由Scheduler进行调度,优先级足够高将排序到taskQueue的最前面,调度该函数时会进行优先级判断,以及FiberRootNode中是否有callbackNode,如果存在也会把旧的回调事件取消

# 如何生成新的 fiber 链呢?

那就是在beginworke里,在处理子节点时调用的reconcileChildFibers 然后调用reconcileSingleElement下层调用 useFiber 产生alternate,最后commitRoot更新视图

# 生成的真实节点如何合并的呢?

beginwork的时候对 fiber 完成 tag 的标记,completework时向上遍历,如果判断是原生节点就调用appendAllChildren把子节点添加到当前节点下,fiber保存 DOM 的字段是stateNode

# 优化

# 渲染优化

# ReRender 三要素

props 默认是全等比较,每次传入的 props 都是全新的对象,oldProps !== newProps true

  • props 从父组件接受 data children 等参数
  • state 作为 props 传递给子孙组件
  • context 从 provider 中获取数据

# 自顶向下更新

React中数据发生变化,会自顶向下构建一颗完整的组件树,即使组件内部没有发生变化,默认都会被重新渲染

function Demo1() {
  const [count, setCount] = useState(0);
  return (
    <>
      <input
        type="number"
        value={count}
        onChange={(e) => setCount(+e.target.value)}
      />
      <p>num is {count}</p>
      <ExpensiveCpn />
    </>
  );
}
function ExpensiveCpn() {
  const now = performance.now();
  while (performance.now() - now > 100) {}
  console.log("耗时组件 render");
  return <p>耗时组件</p>;
}

# 抽离变的部分

把 Input 及展示需要用到 state 的部分抽离成一个组件,把变与不变分离

function Demo2() {
  return (
    <>
      <Input></Input>
      <ExpensiveCpn />
    </>
  );
}
function Input() {
  const [count, setCount] = useState(0);
  return (
    <>
      <input
        type="number"
        value={count}
        onChange={(e) => setCount(+e.target.value)}
      />
      <p>num is {count}</p>
    </>
  );
}

# 优化父组件

父组件不变,props 传递不变组件到可变组件中,也可以达到不变组件不重渲染

function Demo3() {
  return (
    <InputWrapper>
      <ExpensiveCpn />
    </InputWrapper>
  );
}

function InputWrapper({ children }) {
  const [count, setCount] = useState(0);
  return (
    <div title={count + ""}>
      <input
        type="number"
        value={count}
        onChange={(e) => setCount(+e.target.value)}
      />
      <p>num is {count}</p>
      {children}
    </div>
  );
}

# context 传参

const numCtx = createContext(0);
const updateNumCtx = createContext(() => {});
function Demo4() {
  const [num, updateNum] = useState(0);
  return (
    <>
      <numCtx.Provider value={num}>
        <updateNumCtx.Provider value={updateNum}>
          <Middle></Middle>
        </updateNumCtx.Provider>
      </numCtx.Provider>
    </>
  );
}

function Middle() {
  return (
    <>
      <Button></Button>
      <Show></Show>
    </>
  );
}
function Button() {
  const updateNum = useContext(updateNumCtx);
  console.log("btn render");
  return <button onClick={() => updateNum(Math.random())}>随机数</button>;
}

function Show() {
  const num = useContext(numCtx);
  return <p>num is: {num}</p>;
}

# memo

使用memo对中间组件的渲染结果缓存,原理是 memo 会对 props 进行浅比较

const Middle = memo(() => {
  return (
    <>
      <Button></Button>
      <Show></Show>
    </>
  );
});

# useMemo

使用 useMemo 对渲染结果缓存

const Middle = () => {
  return useMemo(() => {
    return (
      <>
        <Button></Button>
        <Show></Show>
      </>
    );
  }, []);
};

# BrowserRouter 核心原理实现

  • 历史操作:pushState,replaceState 不会触发popState
  • 监听变更:window.addEventListener('popState',()={})
  • 操作:history.back(),history.forward(),history.go() 会触发popState

# 相关库

immutable.js React-app-rewired