知其然知其所以然,框架工的自我尝试进阶
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( |