知其然知其所以然,框架工的自我尝试进阶
React源码浅析
知识点:
React Api
createElement
、createContext
、JSX=>JS
、ConcurrentMode
、Ref
、Component
、Suspense
、Hooks
React 中的更新创建
ReactDOM.render
、Fiber
、UpdateQueue
、FiberRoot
、Update
、expirationTime
Fiber Scheduler
scheduleWork
、batchedUpdates
、performWork
、performUnitOfWork
、requestWork
、react scheduler
、renderRoot
开始更新
beginWork以及优化
、各类组件的更新过程
、调节子节点的过程
完成各个节点的更新
completeUnitOfWork
、虚拟DOM对比
、completeWork
、错误捕获处理
、unwindwork
、完成整棵树更新
提交更新
commitRoot整体流程
、开发时的帮助方法
、提交快照
、提交DOM插入
、提交DOM更新
、提交DOM删除
、提交所有生命周期
各功能的实现功能
context的实现过程
、ref的实现过程
、hydrate的实现过程
、React的事件体系
Suspense
更新优先级的概念
、更新挂起的概念
、Suspense组件更新
、timeout处理
、retry重新尝试渲染
、lazy组件更新
Hooks
核心原理
、useState
、useEffect
、useContent
、其他Hooks API
React Api 相关
React.js
1 | const React = { |
ReactElement.js
1 | // hasOwnProperty 方法返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是知否有指定的键) |
ReactElement
通过 createElement
创建的,传入三个参数:
type(指代
ReactElement
的类型)- 字符串。
'div'
等,被称为HostComponent
- Class。继承自
Component
或者PureComponent
的组件,称为ClassComponent
- 方法。
functinal Component
- 原生。
Fragment
、AsyncModel
、Symbol
- 字符串。
config
- children
ReactElement
只是一个用来承载信息的容器,里面有几个信息:
type
,用来判断如何创建节点key
和ref
这些特殊信息props
新的属性内容$$type
用于确定是否属于ReactElement
ReactBaseClasses.js
1 | import ReactNoopUpdateQueue from './ReactNoopUpdateQueue'; |
ReactCreateRef.js
1 | export function createRef(){ |
Refs 提供了一种方式,允许我们访问 DOM 节点或者在 render 方法中创建的 React 元素。
使用 Refs 的情况:
- 管理焦点,文本选择或者媒体播放
- 触发强制动画
- 集成第三方 DOM 库
使用 Refs 的方法:
- string ref
- function
- createRef
例子:
1 | export default class RefDemo extends React.Component { |
forwardRef.js
1 | export default function forwardRef(render:(props,ref)){ |
React.forwardRef 会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
主要使用的是:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
React.forwardRef 接受渲染函数作为参数。React 将使用 props 和 refs 作为参数来调用此函数。此函数应该返回 React 节点。
1 | // PureComponent |
ReactContext.js
1 | export function createContext(defaultValue,calculateChangedBits){ |
ReactLazy.js
1 | export function lazy(ctor:()=>Thenable<T,R>){ |
Hooks
简单用法:
1 | import React, { useState,useEffect } from 'react'; |
ReactChildren.js
1 | const SEPARATOP = '.'; |
主要方法流程图:
graph TD A(开始) contextPool(contextPool) mapIntoWithKeyPrefixInternal(mapIntoWithKeyPrefixInternal) traverseAllChildren(traverseAllChildren) traverseAllChildrenImpl(traverseAllChildrenImpl) mapSingleChildrenContext(mapSingleChildrenContext) 是否多个节点{是否多个节点} isArray{isArray} 循环每个节点((循环每个节点)) 对每个节点调用mapFunc返回map之后的节点(对每个节点调用mapFunc返回map之后的节点) 往result中推入clone节点并替换key(往result中推入clone节点并替换key) A --> mapIntoWithKeyPrefixInternal mapIntoWithKeyPrefixInternal -. 开始的时候获取 .-> contextPool mapIntoWithKeyPrefixInternal -. 结束之后归还 .-> contextPool mapIntoWithKeyPrefixInternal --> traverseAllChildren traverseAllChildren --> traverseAllChildrenImpl traverseAllChildrenImpl --> 是否多个节点 是否多个节点 --Y--> 循环每个节点 是否多个节点 --N--> mapSingleChildrenContext 循环每个节点 --> traverseAllChildrenImpl mapSingleChildrenContext --> 对每个节点调用mapFunc返回map之后的节点 对每个节点调用mapFunc返回map之后的节点 --> isArray isArray --Y--> mapIntoWithKeyPrefixInternal isArray --N--> 往result中推入clone节点并替换key
ReactContext.js
1 | export function createCOntext(defaultValue,calculateChangedBits){ |
memo.js
1 | export default function memo( |
创建更新的方式
主要是下面的知识点:
ReactDOM.render
、ReactDOM.render
、setState
、forceUpdate
步骤:
- 创建 ReactRoot
- 创建 FiberRoot 和 RootFiber
- 创建更新
ReactDOM.js
1 | // ReactDOM |
render 与 hydrate 方法都可以传入三个参数,包括 ReactElement、DOM包裹节点和渲染结束后执行的回调方法,返回 legacyRenderSubtreeIntoContainer
方法执行的结果。
1 | function legacyRenderSubtreeIntoContainer( |
先判断 root 是否不存在,不存在调用legacyCreateRootFromDOMContainer
,传入 container
,forceHydrate
两个参数。legacyCreateRootFromDOMContainer
函数创建了一个 ReactRoot
,而 forceHydrate
布尔类型参数在 hydrate
传入 true
,在 render
里面传入 false
。前者是服务器渲染,后者是客户端渲染。服务器渲染当服务器呈现标记的节点,React 会保留它并附加事件处理程序,从而具有高性能的首屏加载,客户端渲染当初始DOM与当前 DOM 存在差异,则可能会更改节点。可以看到 如果是 false
会 container.removeChild(rootSibling)
1 | function ReactRoot( |