源码学习笔记
以功能点为最小学习单元,由点到线、由线到面的学习方法
参考学习一些优秀的资料,为学习源码积蓄能量
小笔记
1. React有三种方式更新
- React.render() || hydrate(ReactDOMServer渲染)
- setState()
- forceUpdate()
功能点:
-
React.createElement:
- webpack编写生React.createElement并嵌套调用,最终生成vdom。
-
ReactDOM.render
- 未完待续
-
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; //并在新的第一人和最后一人之间建立连接 } }
-
-
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; } }
-
-
Scheduler.js 协调浏览器调度额包
-
scheduleWork
7.未完待续