React源码学习笔记

277 阅读10分钟

源码学习笔记

以功能点为最小学习单元,由点到线、由线到面的学习方法

参考学习一些优秀的资料,为学习源码积蓄能量

小笔记

1. React有三种方式更新
 - React.render() || hydrate(ReactDOMServer渲染)
 - setState()
 - forceUpdate()

功能点:

  1. React.createElement:

    • webpack编写生React.createElement并嵌套调用,最终生成vdom。
  2. ReactDOM.render

    • 未完待续
  3. fiber

    • 含义和作用

      • 每个ReactElement对应一个Fiber
      • 记录节点的各种状态,比如ClassComponent中的state和props的状态就是记录在Fiber对象上的。只有当Fiber对象更新后,才会更新ClassComponent的this.state和this.props,this上的state和props是根据Fiber对象的state、props更新的。实际上也方便了ReactHooks,因为hooks是为FunctionalComponent服务的。虽然FunctionnaComponent没有this,但是Fiber上有,是可以拿到state和props的
      • 串联整个应用形成树结构,每个ReactElement通过props.children与其它ReactElement连接起来(如下 工作流程图 所示)
    • 结构解析:

    • 结构属性详解

      // A Fiber is work on a Component that needs to be done or was done. There can
      // be more than one per component.
      export type Fiber = {|
        // These first fields are conceptually members of an Instance. This used to
        // be split into a separate type and intersected with the other Fiber fields,
        // but until Flow fixes its intersection bugs, we've merged them into a
        // single type.
      
        // An Instance is shared between all versions of a component. We can easily
        // break this out into a separate object to avoid copying so much to the
        // alternate versions of the tree. We put this on a single object for now to
        // minimize the number of objects created during the initial render.
      
        // Tag identifying the type of fiber.
        // 标记不同的组件类型
        // 有原生的DOM节点,有React自己的节点
        tag: WorkTag,
      
        // Unique identifier of this child.
        // ReactElement里面的key
        key: null | string,
      
        // The value of element.type which is used to preserve the identity during
        // reconciliation of this child.
        // ReactElement.type,也就是我们调用createElement的第一个参数
        elementType: any,
      
        // The resolved function/class/ associated with this fiber.
        // 异步组件resolve之后返回的内容,一般是function或class
        // 比如懒加载
        type: any,
      
        // The local state associated with this fiber.
        // 当前Fiber的状态(比如浏览器环境就是DOM节点)
      
        // 不同类型的实例都会记录在stateNode上
        // 比如DOM组件对应DOM节点实例
        // ClassComponent对应Class实例
        // FunctionComponent没有实例,所以stateNode值为null
      
        //state更新了或props更新了均会更新到stateNode上
        stateNode: any,
      
        // Conceptual aliases
        // parent : Instance -> return The parent happens to be the same as the
        // return fiber since we've merged the fiber and instance.
      
        // Remaining fields belong to Fiber
      
        // The Fiber to return to after finishing processing this one.
        // This is effectively the parent, but there can be multiple parents (two)
        // so this is only the parent of the thing we're currently processing.
        // It is conceptually the same as the return address of a stack frame.
        // 是指程序(program)在处理完当前fiber之后应当返回的fiber,概念上来说,
        // 与一个栈帧返回一个地址是相同的,它也可被认为是一个parent fiber。
        return: Fiber | null,
      
        // Singly Linked List Tree Structure.
        // 链表结构兄弟节点的return指向同一个父节点                   
        // 指向自己的第一个子节点
        child: Fiber | null,
        // 兄弟节点                   
        sibling: Fiber | null,
        index: number,
      
      // The ref last used to attach this node.
        // I'll avoid adding an owner field for prod and model that as functions.
        ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
      
        // Input is the data coming into process this fiber. Arguments. Props.
        // 新的变动带来的新的props,即nextProps
        pendingProps: any, // This type will be more specific once we overload the tag. 
        //上一次渲染完成后的props,即 props
        memoizedProps: any, // The props used to create the output. 用于创建output的props值
      
        // A queue of state updates and callbacks.
        // 该Fiber对应的组件,所产生的update,都会放在该队列中
        updateQueue: UpdateQueue<any> | null,
      
        // The state used to create the output
        // 上次渲染的state,即 state
        // 新的state由updateQueue计算得出,并覆盖memoizedState
        memoizedState: any,
      
        // Dependencies (contexts, events) for this fiber, if it has any
        // 一个列表,存在该Fiber依赖的contexts,events
        dependencies: Dependencies | null,
      
        // Bitfield that describes properties about the fiber and its subtree. E.g.
        // the ConcurrentMode flag indicates whether the subtree should be async-by-
        // default. When a fiber is created, it inherits the mode of its
        // parent. Additional flags can be set at creation time, but after that the
        // value should remain unchanged throughout the fiber's lifetime, particularly
        // before its child fibers are created.
            
        // mode有conCurrentMode和strictMode
        // 用来描述当前Fiber和其他子树的Bitfield
        // 共存的模式表示这个子树是否默认是 异步渲染的
        // Fiber刚被创建时,会继承父Fiber
        // 其他标识也可以在创建的时候被设置,但是创建之后不该被修改,特别是它的子Fiber创建之前
        mode: TypeOfMode,
      
        // Effect
        effectTag: SideEffectTag,
      
        // Singly linked list fast path to the next fiber with side-effects.
        nextEffect: Fiber | null,
      
        // The first and last fiber with side-effect within this subtree. This allows
        // us to reuse a slice of the linked list when we reuse the work done within
        // this fiber.
        firstEffect: Fiber | null,
        lastEffect: Fiber | null,
      
        // Represents a time in the future by which this work should be completed.
        // Does not include work found in its subtree.
        // 代表任务在未来的哪个时间点 应该被完成
        // 不包括该Fiber的子树产生的任务
        expirationTime: ExpirationTime,
      
        // This is used to quickly determine if a subtree has no pending changes.
        // 快速确定子树中是否有 update
        // 如果子节点有update的话,就记录应该更新的时间
        childExpirationTime: ExpirationTime,
      
        // This is a pooled version of a Fiber. Every fiber that gets updated will
        // eventually have a pair. There are cases when we can clean up pairs to save
        // memory if we need to.
        // 在FIber树更新的过程中,每个Fiber都有与其对应的Fiber
        // 我们称之为 current <==> workInProgress
        // 在渲染完成后,会交换位置
        // doubleBuffer Fiber在更新后,不用再重新创建对象,
        // 而是复制自身,并且两者相互复用,用来提高性能
        alternate: Fiber | null,
      
        // Time spent rendering this Fiber and its descendants for the current update.
        // This tells us how well the tree makes use of sCU for memoization.
        // It is reset to 0 each time we render and only updated when we don't bailout.
        // This field is only set when the enableProfilerTimer flag is enabled.
        // 这个fiber以及它的子元素此次更新所花费的时间
        actualDuration?: number,
      
        // If the Fiber is currently active in the "render" phase,
        // This marks the time at which the work began.
        // This field is only set when the enableProfilerTimer flag is enabled.
        actualStartTime?: number,
      
        // Duration of the most recent render time for this Fiber.
        // This value is not updated when we bailout for memoization purposes.
        // This field is only set when the enableProfilerTimer flag is enabled.
        selfBaseDuration?: number,
      
        // Sum of base times for all descendants of this Fiber.
        // This value bubbles up during the "complete" phase.
        // This field is only set when the enableProfilerTimer flag is enabled.
        treeBaseDuration?: number,
      
        // Conceptual aliases
        // workInProgress : Fiber ->  alternate The alternate used for reuse happens
        // to be the same as work in progress.
        // __DEV__ only
        _debugID?: number,
        _debugSource?: Source | null,
        _debugOwner?: Fiber | null,
        _debugIsCurrentlyTiming?: boolean,
        _debugNeedsRemount?: boolean,
      
        // Used to verify that the order of hooks does not change between renders.
        _debugHookTypes?: Array<HookType> | null
      |}
      
    • 工作流程图(串联整个应用形成树结构,也就是循环链表)

    • 循环链表

        // 一个简单的循环链表的插入和删除
          // person的类型定义
          interface Person {
              name : string  //姓名
              age : number  //年龄,依赖这个属性排序
              next : Person  //紧跟在后面的人,默认是null
              previous : Person //前面相邻的那个人,默认是null
          }
          var firstNode = null; //一开始链表里没有节点
          
          //插入的逻辑
          function insertByAge(newPerson:Person){
              if(firstNode = null){
              	//如果 firstNode为空,说明newPerson是第一个人,  
              	//把它赋值给firstNode,并把next和previous属性指向自身,自成一个环。
                	firstNode = newPerson.next = newPerson.previous = newPerson;
                
              } else { 
              	//队伍里有人了,新来的人要找准自己的位置
                   var next = null; // 记录newPerson插入到哪个人前边
                   var person = firstNode; // person 在下边的循环中会从第一个人开始往后找
                   
                   do {
                        if (person.age > newPerson.age) {
                            // 如果person的年龄比新来的人大,说明新来的人找到位置了,
                            // 他恰好要排在person的前边,结束
                            next = person;
                            break;
                        }
                        //继续找后面的人
                        person = person.next;
                  } while (node !== firstNode); //这里的while是为了防止无限循环,毕竟是环形的结构
                  
                  if(next === null){ 
                      // 找了一圈发现 没有person的age比newPerson大,
                      // 说明newPerson应该放到队伍的最后,也就是说newPerson的后面应该是firstNode。
                      next = firstNode;
                  } else if(next === firstNode){ 
                      // 找第一个的时候就找到next了,说明newPerson要放到firstNode前面,
                      // 这时候firstNode就要更新为newPerson
                      firstNode = newPerson
                  }
                  //下面是newPerson的插入操作,给next及previous两个人的前后链接都关联到newPerson
                  var previous = next.previous;
                  previous.next = next.previous = newPerson; 
                  newPerson.next = next;
                  newPerson.previous = previous;
              }
              //插入成功
          }
          
          //删除第一个节点
          function deleteFirstPerson(){
              if(firstNode === null) return; //队伍里没有人,返回
              var next = firstNode.next; //第二个人
              if(firstNode === next) {
                  //这时候只有一个人
                  firstNode = null;
                  next = null;
              } else {
                  var lastPerson = firstNode.previous; //找到最后一个人
                  firstNode = lastPerson.next = next; //更新新的第一人
                  next.previous = lastPerson; //并在新的第一人和最后一人之间建立连接
              }
          }
      
      
  4. FiberRoot

    • 作用及含义

      是整个React应用的起点

      包含应用挂载的目标节点(<div id='root'>root</div>)

      记录整个React应用更新过程中的各种信息

    • 与RootFiber的关系

      FiberRoot.current = RootFiber
      RootFiber.stateNode = FiberRoot
      
    • createFiberRoot():初始化fiberRoot和rootFiber

      // 初始化fiberRoot和rootFiber
      // fiberRoot与rootFiber相互赋值
      export function createFiberRoot(
        containerInfo: any,
        tag: RootTag,
        hydrate: boolean,
      ): FiberRoot {
        //新建fiberRoot对象
        const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
      
        // Cyclic construction. This cheats the type system right now because
        // stateNode is any.
        //初始化RootFiber
        const uninitializedFiber = createHostRootFiber(tag);
        //FiberRoot和RootFiber的关系
        //FiberRoot.current = RootFiber
        root.current = uninitializedFiber;
        //RootFiber.stateNode = FiberRoot
        uninitializedFiber.stateNode = root;
      
        return root;
      }
      
    • FiberRootNode()

      type BaseFiberRootProperties = {|
        // The type of root (legacy, batched, concurrent, etc.)
        // 用于标记组件类型
        tag: RootTag,
      
        // Any additional information from the host associated with this root.
        // ReactDOM.render挂载的根节点
        containerInfo: any,
        // Used only by persistent updates.
        // 只有在持久更新中才会用到,也就是不支持增量更新的平台会用到,
        // react-dom不会用到也就是不更新某一块地方,而是整个应用完全更新
        pendingChildren: any,
        // The currently active root fiber. This is the mutable root of the tree.
        // 当前应用root节点对应的Fiber对象,即Root Fiber
        // ReactElement会有一个树结构,同时一个ReactElement对应一个Fiber对象,
        // 所以Fiber也会有树结构
        // current:Fiber对象 对应的是 root 节点,即整个应用根对象
        current: Fiber,
      
        pingCache:
          | WeakMap<Thenable, Set<ExpirationTime>>
          | Map<Thenable, Set<ExpirationTime>>
          | null,
        // 任务有三种,优先级有高低:
        //(1)没有提交的任务
        //(2)没有提交的被挂起的任务
        //(3)没有提交的可能被挂起的任务
                                      
        // 当前更新对应的过期时间
        finishedExpirationTime: ExpirationTime,
        // A finished work-in-progress HostRoot that's ready to be committed.
        // 已经完成任务的FiberRoot对象,如果你只有一个Root,
        // 那么该对象就是这个Root对应的Fiber或null
        // 在commit(提交)阶段只会处理该值对应的任务
        finishedWork: Fiber | null,
        // Timeout handle returned by setTimeout. 
        // Used to cancel a pending timeout, if it's superseded by a new one.
        // 在任务被挂起的时候,通过setTimeout设置的响应内容,
        // 并且清理之前挂起的任务 还没触发的timeout
        timeoutHandle: TimeoutHandle | NoTimeout,
        // Top context object, used by renderSubtreeIntoContainer
        // 顶层context对象,只有主动调用renderSubtreeIntoContainer才会生效
        context: Object | null,
        pendingContext: Object | null,
        // Determines if we should attempt to hydrate on the initial mount
        //用来判断 第一次渲染 是否需要融合
        +hydrate: boolean,
        // Node returned by Scheduler.scheduleCallback
        callbackNode: *,
        // Expiration of the callback associated with this root
        // 跟root有关联的回调函数的时间
        callbackExpirationTime: ExpirationTime,
        // Priority of the callback associated with this root
        callbackPriority: ReactPriorityLevel,
        // The earliest pending expiration time that exists in the tree
        // 存在root中,最旧的挂起时间
        // 不确定是否挂起的状态(所有任务一开始均是该状态)
        firstPendingTime: ExpirationTime,
        // The earliest suspended expiration time that exists in the tree
        firstSuspendedTime: ExpirationTime,
        // The latest suspended expiration time that exists in the tree
        // 存在root中,最新的挂起时间
        // 不确定是否挂起的状态(所有任务一开始均是该状态)
        lastSuspendedTime: ExpirationTime,
        // The next known expiration time after the suspended range
        nextKnownPendingLevel: ExpirationTime,
        // The latest time at which a suspended component pinged the root to
        // render again
        // 被挂起的组件发出ping信号使根再次呈现的最新时间
        lastPingedTime: ExpirationTime,
        lastExpiredTime: ExpirationTime,
      |};
      
      type ProfilingOnlyFiberRootProperties = {|
        interactionThreadID: number,
        memoizedInteractions: Set<Interaction>,
        pendingInteractionMap: PendingInteractionMap,
      |};
      
      // The follow fields are only used by enableSuspenseCallback for hydration.
      type SuspenseCallbackOnlyFiberRootProperties = {|
        hydrationCallbacks: null | SuspenseHydrationCallbacks,
      |};
      
      export type FiberRoot = {
        ...BaseFiberRootProperties,
        ...ProfilingOnlyFiberRootProperties,
        ...SuspenseCallbackOnlyFiberRootProperties,
      };
      
      // 创建fiberRoot对象
      function FiberRootNode(containerInfo, tag, hydrate) {
        this.tag = tag; // 用于标记组件类型
        this.current = null;
        this.containerInfo = containerInfo; // 根节点
        this.pendingChildren = null;
        this.pingCache = null;
        this.finishedExpirationTime = NoWork;
        this.finishedWork = null;
        this.timeoutHandle = noTimeout;
        this.context = null;
        this.pendingContext = null;
        this.hydrate = hydrate;
        this.callbackNode = null;
        this.callbackPriority = NoPriority;
        this.firstPendingTime = NoWork;
        this.firstSuspendedTime = NoWork;
        this.lastSuspendedTime = NoWork;
        this.nextKnownPendingLevel = NoWork;
        this.lastPingedTime = NoWork;
        this.lastExpiredTime = NoWork;
      
        if (enableSchedulerTracing) {
          this.interactionThreadID = unstable_getThreadID();
          this.memoizedInteractions = new Set();
          this.pendingInteractionMap = new Map();
        }
        if (enableSuspenseCallback) {
          this.hydrationCallbacks = null;
        }
      }
      
  5. Scheduler.js 协调浏览器调度额包

  6. scheduleWork

7.未完待续