☞阅读原文
有的人可能会不理解,大前端平台化的战火为谁而燃,吾辈何以为战?
专注于移动互联网大前端致富,一直是我们最崇高的理想,而ReactNative是横亘在中间的桥头堡。
纵观行业风向,有作壁上观者,有磨刀霍霍者,有从入门到放弃者,有大刀阔斧者,但是缺乏深潜微操者。
啊哈,是时候该我出手了。
祭出“大海航术”,经过一年来不懈钻研,基于React Developer Tools研发插件,实时绘制运行时三棵树–Fiber双树、Native View树、React方法调用树,在上帝视角和时间旅行的引领下,冲破波诡云谲的Fiber算法迷航,日照大海现双龙。
本文主要针对ReactNative(以下简称 RN)的React.js源码进行分析,先说清楚开发者接触到的API,然后再深挖对应底层实现逻辑,最后找找微操的空间。如果有对RN不太熟悉的朋友,建议看一下《进击ReactNative-疾如风》热热身,该文从“原理+实践,现学现做”的角度手写石器时代RN,粗线条描述跨平台套路,迂回包抄,相对比较轻松!本文则正面刚React源码,略显烧脑。
话说,做大事,就要用大斧头。先耍耍阿里“三板斧”撼动一下。
近几年的移动互联网北漂生涯,给我结结实实的上了一课:人生,除了发财,就是不断探索、抽象、践行、强化自己的方法论、稳固自己的价值观,过程呈螺旋式上升,只要把握住总结复盘的秘诀,成长就很快乐。
我攻坚RN的原动力,就是借假修真。平台化技术最终王者也许花落Flutter或者小程序(还有很多人在纠结到底哪家强,耽误了学习,其实这好比考清华还是考北大,Top2高校有那么难选么,真正难选的是Top3高校),但这不重要,我能举一,必能反三,这就是霸道。我旨在强化出一套王者无界的方法论,如何从零将RN技能练到比肩高阶Android的熟练度,并且同样适用于进击Flutter和小程序。
现在市面上高水准的RN解析文章太少了(老外写的硬核文章居多),而且大多停留在理论层面,只给出算法理论和源码片段,难以深入微操,只能作者说啥就是啥,反正不明觉厉。也罢,自力更生啃源码必须提上日程。
我始终相信,只有源码才是唯一的真相,不二的注释,思想的火花,王者的农药。
事实上,终于在眼泪中明白并验证了这一点,源码大法好啊,得到的比想要的多得多(贫穷限制了我的想象)。往小的说,技术成长(自嗨)。往大的说,核心竞争力(钱)。
本文和你分享的是如何通过先进生产力相对轻松地看懂代码,区别于呆板的流水式英文阅读,挑战一下:
聪明的童靴往往都会有一些亟需亲自操刀的疑问,我也不能免俗。问题有了,那满意的答案呢?
// 组件类
class Component {
// 变量
props;
state;
// 方法
constructor(props, context);
setState(state, callback): void;
forceUpdate(callBack): void;
render(): ReactNode;
}
// 静态生命周期
interface StaticLifecycle {
getDerivedStateFromProps?: GetDerivedStateFromProps;
}
// 新生命周期
interface NewLifecycle {
getSnapshotBeforeUpdate?(prevProps, prevState): SS | null;
componentDidUpdate?(prevProps, prevState, snapshot): void;
}
// 废弃生命周期
interface DeprecatedLifecycle
{
componentWillMount?(): void;
UNSAFE_componentWillMount?(): void;
componentWillReceiveProps?(nextProps, nextContext): void;
UNSAFE_componentWillReceiveProps?(nextProps, nextContext): void;
componentWillUpdate?(nextProps, nextState, nextContext): void;
UNSAFE_componentWillUpdate?(nextProps, nextState, nextContext): void;
}
// 组件生命周期(继承新和废弃生命周期)
interface ComponentLifecycle
extends NewLifecycle
, DeprecatedLifecycle
{
componentDidMount?(): void;
shouldComponentUpdate?(nextProps, nextState, nextContext): boolean;
componentWillUnmount?(): void;
componentDidCatch?(error, errorInfo): void;
}
我们不是一个人在战斗(想发财),切忌闭门造车,只有集思广益,站在巨人的肩膀上才能事半功倍。
网上一顿关键字索引,找点时间,给点耐心,泛读 + 精读数十篇后,你的感觉才能慢慢滴上来。
本着坚定看多,数量堆死力量,经过不间断的阅读输出,姿势见涨,比方说通过XMind自由缩放源码地图帮助理解、手写RN寻求理论加实践、抽象伪代码表述助力说清楚等。
硬核带货时间,安利一下我的博客主页和微信朋友圈,我会阶段性将看到的ReactNative优秀文章汇总起来。发盆友圈,我是认真的,停是不可能停下来的,天天上班天天发。欢迎相互切磋,共同进步。
Fiber架构里程碑
**Why:**一路狂奔式地更新,无暇处理用户响应,引发界面咔咔咔。
**What:**Fiber(纤维),是比线程控制更精密的并发处理机制。支持更新过程碎片化,化整为零,允许紧急任务插队,可中断恢复。本质上,它还是一个工具,用来帮助开发者操纵DOM API,从而构建出页面。
**How Much:**纵享丝滑。
**硬核资料:**业界大牛Lin Clark在2017 React大会的演讲Lin Clark - A Cartoon Intro to Fiber - React Conf 2017。这个内容太棒啦,墙裂建议大家看一看(没有字幕,英文流利的同学可以挑战一下,或者像我一样发挥暴躁的想象力假装听懂了)。网上大部分Fiber算法分析都引用了她的卡通图。
术语
Component:组件,是可复用的小的代码片段,它们返回要在页面中渲染的React元素。分为类组件(继承Component的普通组件和继承PureComponent的纯组件)和函数式组件(直接返回Element的函数)。
// 普通组件
class App extends React.Component {
render() {
return {'点击数0'} ;
}
}
// 纯组件
class App extends React.PureComponent {
render() {
return {'点击数0'} ;
}
}
// 函数式组件
const App = function () {
return {'点击数0'} ;
}
JSX:是类Html标签式写法转化为纯对象Element函数调用式写法的语法糖。Babel会把JSX转译成一个名为 React.createElement 函数调用.
class App extends React.Component {
render() {
// Babel转换JSX后
return React.createElement(
// 类型type
{$$typeof: Symbol(react.forward_ref), displayName: "Text", propTypes: {…}, render: ƒ},
// 属性props
{style: {color: "black"}, __source: {…}},
// 子节点children
"点击数0"
);
}
}
Instance:组件实例,组件类实例化的结果,ref指向组件实例(函数式组件不能实例化)。在生成Fiber节点时会调用new Component()创建。
// App
{
forceUpdate: ƒ (),
isReactComponent: ƒ (),
setState: ƒ (),
componentDidMount: ƒ (),
componentWillUnmount: ƒ (),
constructor: ƒ App(props),
isMounted: (...),
render: ƒ (),
replaceState: (...),
__proto__: Component
}
Element:元素,描述了你在屏幕上想看到的内容。是DOM节点的一种纯对象描述,即虚拟DOM,对应组件render方法主要返回值。详见React.createElement。
// App
{
// React Element唯一标识
$$typeof: Symbol(react.element),
// 开发者指定唯一标识,用于复用
key: null,
// 属性
props: {rootTag: 241},
// 引用
ref: null,
// 类型
type: ƒ App(props),
_owner: null,
_store: {validated: true},
_self: null,
_source: {fileName: "/Users/shengshuqiang/dream/AdvanceOnReactNative/Aw…native/Libraries/ReactNative/renderApplication.js", lineNumber: 38},
__proto__: Object
}
// Text
{
$$typeof: Symbol(react.element),
key: null,
props: {style: {color: "black"}, children: "点击数0"},
ref: null,
type: {$$typeof: Symbol(react.forward_ref), displayName: "Text", propTypes: {…}, render: ƒ},
_owner: FiberNode {id: 11, tag: 1, key: null, elementType: ƒ, type: ƒ, …},
_store: {validated: false},
_self: null,
_source: {fileName: "/Users/shengshuqiang/dream/AdvanceOnReactNative/AwesomeProject/App.js", lineNumber: 213},
__proto__: Object
}
FiberNode:碎片化更新中可操作的细粒度节点,用于存储中间态计算结果,为“可紧急插队、可中断恢复”的页面刷新提供技术支持。详见ReactNative.createFiberFromElement。
// FiberNode
{
actualDuration: 175.7499999985157,
actualStartTime: 9793.884999999136,
// 候补树,在调用render或setState后,会克隆出一个镜像fiber,diff产生出的变化会标记在镜像fiber上。而alternate就是链接当前fiber tree和镜像fiber tree, 用于断点恢复
alternate: null,
// 第一个子节点
child: FiberNode {id: 12, tag: 11, key: null, elementType: {…}, type: {…}, …},
// TODO
childExpirationTime: 0,
contextDependencies: null,
// 副作用,增删改操作。Placement=2;Update=4;PlacementAndUpdate=6;Deletion=8;
effectTag: 5,
// 描述了它对应的组件。对于复合组件,类型是函数或类组件本身。对于宿主组件(div,span等),类型是字符串。定义此 Fiber 节点的函数或类。对于类组件,它指向构造函数,对于 DOM 元素,它指定 HTML 标记。我经常使用这个字段来理解 Fiber 节点与哪个元素相关。
// ClassComponent对应为函数,如APPContainer()。ForwardRef、ContextConsumer、ContextProvider对应为对象,如{$$typeof: Symbol(react.forward_ref), render: ƒ, displayName: "View"}。HostComponent对应为字符串,如“RCTView”。HostText对应为null。
elementType: ƒ App(props),
// TODO
expirationTime: 0,
// 用来保存中断前后 effect 的状态,用户中断后恢复之前的操作。这个意思还是很迷糊的,因为 Fiber 使用了可中断的架构
firstEffect: FiberNode {id: 13, tag: 1, key: null, elementType: ƒ, type: ƒ, …},
// 我添加的Fiber节点唯一标识(采用id自增生成),用于生成Fiber双树
id: 11,
index: 0,
// 复用标识
key: null,
// 参考firstEffect
lastEffect: FiberNode {id: 13, tag: 1, key: null, elementType: ƒ, type: ƒ, …},
// 在前一个渲染中用于创建输出的 Fiber 的 props
memoizedProps: {rootTag: 191},
// 用于创建输出的 Fiber 状态。处理更新时,它会反映当前在屏幕上呈现的状态
memoizedState: null,
mode: 4,
// workInProgress tree上每个节点都有一个effect list,用来存放需要更新的内容。此节点更新完毕会向子节点或邻近节点合并 effect list
nextEffect: FiberNode {id: 10, tag: 5, key: null, elementType: "RCTView", type: "RCTView", …},
// props是函数的参数。一个 fiber 的pendingProps在执行开始时设置,并在结束时设置memoizedProps。已从 React 元素中的新数据更新并且需要应用于子组件或 DOM 元素的 props
pendingProps: {rootTag: 191},
ref: null,
// 父节点
return: FiberNode {id: 10, tag: 5, key: null, elementType: "RCTView", type: "RCTView", …},
selfBaseDuration: 28.63000000070315,
// 兄弟节点
sibling: null,
// 保存组件的类实例、DOM 节点或与 Fiber 节点关联的其他 React 元素类型的引用。总的来说,我们可以认为该属性用于保持与一个 Fiber 节点相关联的局部状态。(HostRoot对应{containerInfo};ClassComponent对应为new的函数对象实例;HostComponent对应为ReactNativeFiberHostComponent,包含_children和_nativeTag;HostText对应为nativeTag)
stateNode: hookClazz {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …},
// 它在协调算法中用于确定需要完成的工作。如前所述,工作取决于React元素的类型
tag: 1,
treeBaseDuration: 155.36499999871012,
// 同elementType
type: ƒ App(props),
// state更新队列。状态更新、回调和 DOM 更新的队列
updateQueue: null,
_debugID: 12,
_debugIsCurrentlyTiming: false,
_debugOwner: null,
_debugSource: {fileName: "/Users/shengshuqiang/dream/AdvanceOnReactNative/Aw…native/Libraries/ReactNative/renderApplication.js", lineNumber: 38},
__proto__: Object
}
DOM:文档对象模型(Document Object Model),简单说就是界面控件树(对应Html是DOM树,对应Native是View树)的节点。
UIManager.createView [3,"RCTRawText",1,{"text":"点击数0"}]
UIManager.createView [5,"RCTText",1,{"ellipsizeMode":"tail","allowFontScaling":true,"accessible":true,"color":-16777216}]
UIManager.setChildren [5,[3]]
UIManager.createView [7,"RCTView",1,{"flex":1,"pointerEvents":"box-none","collapsable":true}]
UIManager.setChildren [7,[5]]
UIManager.createView [9,"RCTView",1,{"pointerEvents":"box-none","flex":1}]
UIManager.setChildren [9,[7]]
UIManager.setChildren [1,[9]]
搭一个自己专属的游乐场–本地可运行环境(开发平台macOS,目标平台Android)。
react-native init AwesomeProject
。我们来读源码(16.8.3 react,0.59.8 react-native)吧!
# 源码目录/Users/shengshuqiang/dream/AdvanceOnReactNative/AwesomeProject/node_modules
.
├── react
│ └── cjs
│ └── react.development.js # 纯JS侧React相关定义和简单实现
└── react-native
├── LICENSE
└── Libraries
├── Components # 官方提供的各种组件,如View、ScrollView、Touchable等
└── Renderer
└── oss
├── GreateNavigationArt.js # “大海航术”核心实现,主要hook调用,打印调用栈日志和dump Fiber双树信息,约600行
└── ReactNativeRenderer-dev.js # ReactNative上层JS代码核心实现,约2W行
我的读码套路:读博客➢跑Demo➢打日志➢打断点➢大猜想➢证因果➢得结论。
说了这么多,我也记不住。抽象一下,这不就是在茫茫大海航行的技术么,就叫“航海术”吧。
对付简单的算法,这招基本够用,否则就真的钱难挣了。
但是,Fiber算法,忒难了。第一个回合硬着头皮看下来,只知道一堆乱七八糟的调用,混杂着各种光怪陆离的Fiber属性,而且用到了复杂的树数据结构,还是双树。
这些,小本子根本记不过来。来张我的笔记感受一下(不用细看,我也没打算讲这张图,大家看个意思),一波操作下来,差不多要2天闭关专注的投入,要是被打断了,都找不到北。
按这个套路,连日志加调试带瞎猜,发现装不下去了,我太难了。一度跌入绝望之谷,挣扎着把源码看了三遍(毕竟指望这一波发财),仍然没什么收获,等着顿悟吧。
直到那一天,我终于等到了这个变数–如果能可视化Fiber双树在运行时的状态变化,是否有望突破React技术壁垒?
脑子再活一点的我就想:“可不可以写个脚本把Fiber双树画出来”,随后的问题就是“能不能写个插件实时绘制运行时Fiber双树”,进一步“绘制实时方法调用树(看着有点像抽象语法树),有问题吗?”能有啥问题,没问题,那就干。
说到底,“海航术”通过日志和调试阅读源码的方向是没有问题的,有问题的是仅通过分析上万条日志信息,过程枯燥乏味,很难通过想象串联这么大量级的信息。如果借助工具提高生产力,可视化图像具象日志信息,那就能攻守易势。特别对于这种抽象的树形结构,没有什么比画图更通俗易懂了。
本着**DRY(Dont Repeat Yourself)**原则,一步步迭代插件。当然,过程是艰辛的,无法一蹴而就。能想到接入react-devtools插件,是因为李阳大牛推荐过该工具帮助分析Virtual DOM树,恰巧彼时团队内部也有童靴在扩展该工具。接入插件当时并没有把握,表面上是扩大战果,但也可能被拖入新的泥潭,舍本逐末。幸好运气不错,在瓶颈期通过董思文和陈卓双大牛的点拨下,灰常顺利的搞出来了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmSUTeuv-1605447230986)(https://shengshuqiang.github.io/assets/ReactDeveloperToolsDemo.png)]
这里必须给React Developer Tools点32个赞,这是我迄今见过最好的架构,我就一JS倔强青铜的水平,竟然看着文档能把源码跑起来(过程中编译相关小问题找公司FE大牛给解了),进一步把自己的脚本集成进去,模仿已有脚本一顿Ctrl+F、Ctrl+C、Ctrl+V就成了,延展性可见一斑,不服不行。
“海航术”的大方向(日志、调试、想象)是正确的,这个想象操作空间太大,是个非标品。“大海航术”的大就大在可视化放飞想象力。
让我们一起欣赏一下大海航术的视觉盛宴▼:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7pZ984o-1605447230992)(https://shengshuqiang.github.io/assets/大海航术动图.gif)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JeTn2t7S-1605447230994)(https://shengshuqiang.github.io/assets/大海航术动图2.gif)]
更多详见Html Demo 页面。
React官方文档和简单Debug能解决大部分问题(前提你得知道问题是什么),剩下的交给时间(时间是一种解药,也是一种毒药)。
组件变量/方法 | 概念 | 调用时机 | 作用 | 最佳实践 |
---|---|---|---|---|
props | 属性 | 使用时设置属性 this.props读取 |
存储父组件传递的信息 | 1. UI组件,根据属性纯展示,没有内部逻辑 |
state | 状态 | setState设置 this.state读取 |
存储自身维护的状态 | 1. 容器组件,纯逻辑处理,通过组合UI组件完成渲染 2. 构造函数直接this.state赋值。否则setState替代 3. 反面模式: 直接复制 prop 到 state |
constructor | 构造函数 | 在React组件挂载之前 | 用于创建组件实例 | 1. 初始化state或进行方法绑定,否则不需要实现 2. 不是props传递的唯一入口(仅初始化调用,后续props更新不调用) 3. super(props)必须在使用this.props之前调用 |
setState | 设置状态 | 用户主动调用 | 将更改排入队列并通知重新渲染 | 1. 控制影响组件粒度,避免大规模刷新(性能杀手) 2. 区分哪些生命周期中不能调用setState,避免死循环 3. 仅影响组件显示状态的数据放在state里面,其他数据可用成员变量存储 4. 不总是立即更新组件,会批量推迟更新 |
forceUpdate | 强制更新 | 用户主动调用 | 跳过shouldComponentUpdate直接触发render | 1. 谨慎使用 2. 如render方法依赖于其他数据,则可调用forceUpdate强制刷新 |
render | 渲染 唯一必须实现 |
Diff比较前 | 描述当前组件的颜值 | 1. 合理通过组件进行封装,确保可读性和可维护性 2. 减少inline-function 3. 养成良好的编程习惯(可扩展性、鲁棒性、可靠性、易用性、可移植性等) |
每个组件都包含“生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。
生命周期 | 概念 | 类型 | 调用时机 | 调用次数 | 调用setState | 作用 | 最佳实践 |
---|---|---|---|---|---|---|---|
static getDerivedStateFromProps | 从属性获取状态 | 常规方法 | 在调用render方法之前调用(初始挂载及后续更新时都调用) | 多次 | 不支持(无法持有引用) | 返回一个对象来更新 state(返回null则不更新任何内容) | 适用于state值在任何时候都取决于props |
getSnapshotBeforeUpdate | 更新前获取快照回调 | 新增方法 | 在最近一次渲染输出(提交到 DOM 节点)之前调用 | 多次 | 支持 | 使得组件能在发生更改之前从 DOM 中捕获一些信息 | 不常见,可能出现在 UI 处理中(如滚动位置) |
componentDidUpdate | 组件已更新回调 | 新增方法 | 在更新后会被立即调用(首次渲染和shouldComponentUpdate返回false时不会调用) | 多次 | 支持 | 当组件更新后,可以在此处对 DOM 进行操作 | 对更新前后的 props 进行了比较判断后触发逻辑(props变化时触发网络请求) |
componentWillMount UNSAFE_componentWillMount |
组件待挂载回调 | 废弃方法 | 在挂载之前被调用 | 一次 | 支持 | 用于触发挂载前逻辑 | 避免在此方法中引入任何副作用或订阅(改用componentDidMount) |
componentWillReceiveProps UNSAFE_componentWillReceiveProps |
组件待接收属性回调 | 废弃方法 | 在已挂载的组件接收新的 props 之前被调用(初始渲染和setState不调用) | 多次 | 支持 | 用于触发接收属性前逻辑 | 1. 不建议用(使用通常会出现 bug 和不一致性) 2. 更新状态以响应 prop 更改 |
componentWillUpdate UNSAFE_componentWillUpdate |
组件待更新回调 | 废弃方法 | 当组件收到新的 props 或 state 时,在渲染之前调用(初始渲染和shouldComponentUpdate返回false不会调用) | 多次 | 不支持(避免循环调用) | 用于触发组件更新前逻辑 | 不建议用(改用componentDidUpdate) |
componentDidMount | 组件已挂载回调 | 常规方法 | 在组件挂载后(插入 DOM 树中)立即调用 | 一次 | 支持 | 用于触发挂载后逻辑 | 依赖于 DOM 节点的初始化(添加订阅、网络请求)应该放在这里 |
shouldComponentUpdate | 组件是否更新 | 常规方法 | 当props或state发生变化时,会在渲染执行之前被调用(首次渲染或使用forceUpdate时不会调用) | 多次 | 不支持(避免循环调用) | 减少不必要的渲染(性能优化) | 1.考虑使用内置的PureComponent组件 2. 不建议深比较(性能杀手) |
componentWillUnmount | 组件待卸载回调 | 常规方法 | 在组件卸载及销毁之前直接调用 | 一次 | 不支持(该组件将永远不会重新渲染和挂载) | 用于触发卸载前逻辑 | 执行必要的清理操作(清除timer、取消网络请求、注销订阅等) |
componentDidCatch | 子组件出错回调 | 常规方法 | 在子组件抛出错误后被调用 | 多次 | 支持 | 用于记录错误 | 上报错误日志 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqtraQ9E-1605447230995)(https://shengshuqiang.github.io/assets/生命周期图谱.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5jofGAi-1605447230996)(https://shengshuqiang.github.io/assets/生命周期调用.png)]
备注:
React源码解析,需要牢记:React组件是数据的函数,v = f(d)。抓住输入和输出,才能有的放矢。本次解析分为二段,初始渲染时间线(用户进入页面Fiber算法干啥类)、用户点击渲染时间线(用户点击按钮切换文本为图标,Fiber算法又干啥类)。这两个场景是所有Fiber算法行为的本源,万变不离其宗。然后再用简单伪代码回顾一下。
初始化页面布局(里面有一堆组件,远比我们写的要多)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcMH6xym-1605447230997)(https://shengshuqiang.github.io/assets/初始化页面布局.png)]
初始化JS2Native通信(通信主要是通过桥UIManager调用createView创建、setChildren关联(增删改)和updateView更新)
1. invoke UIManager.createView [3,"RCTRawText",11,{"text":"点击数0"}]
2. invoke UIManager.createView [5,"RCTText",11,{"ellipsizeMode":"tail","allowFontScaling":true,"accessible":true,"fontSize":30,"color":-1,"textAlignVertical":"center","textAlign":"center"}]
3. invoke UIManager.setChildren [5,[3]]
4. invoke UIManager.createView [7,"RCTView",11,{"backgroundColor":-65536,"height":150,"width":300,"accessible":true}]
5. invoke UIManager.setChildren [7,[5]]
6. invoke UIManager.createView [9,"RCTView",11,{"flex":1,"pointerEvents":"box-none","collapsable":true}]
7. invoke UIManager.setChildren [9,[7]]
8.
9. invoke UIManager.createView [13,"RCTView",11,{"pointerEvents":"box-none","flex":1}]
10. invoke UIManager.setChildren [13,[9]]
11.
12. invoke UIManager.setChildren [11,[13]]
初始化Fiber树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dbk3x052-1605447230998)(https://shengshuqiang.github.io/assets/初始化Fiber树.jpg)]
初始化NativeView树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8EkmUuY-1605447230999)(https://shengshuqiang.github.io/assets/初始化NativeView树.png)]
手机横过来看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ow6OkkJ5-1605447230999)(https://shengshuqiang.github.io/assets/React算法初始渲染时间线-横版.png)]
用户点击页面组件布局
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2C54SIt-1605447231000)(https://shengshuqiang.github.io/assets/点击页面组件布局.png)]
用户点击JS2Native通信,
1. invoke UIManager.measure [7,27]
1. invoke UIManager.playTouchSound []
2. invoke UIManager.createView [15,"RCTImageView",11,{"loadingIndicatorSrc":null,"defaultSrc":null,"src":[{"uri":"http://demo.sc.chinaz.com/Files/pic/icons/5918/c12.png"}],"shouldNotifyLoadEvents":false,"opacity":0.85,"overflow":"hidden","height":100,"width":100}]
3. invoke UIManager.manageChildren [7,[],[],[],[],[0]]
4. invoke UIManager.manageChildren [7,[],[],[15],[0],[]]
5. invoke UIManager.updateView [7,"RCTView",{"backgroundColor":-16777216}]
1. invoke UIManager.updateView [15,"RCTImageView",{"opacity":null}]
2. invoke UIManager.updateView [7,"RCTView",{"backgroundColor":-65536}]
用户点击Fiber树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kXVaOgFw-1605447231000)(https://shengshuqiang.github.io/assets/用户点击渲染Fiber双树图.jpg)]
用户点击NativeView树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqgZERqh-1605447231001)(https://shengshuqiang.github.io/assets/用户点击NativeView树图.png)]
手机横过来看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6UivuFE-1605447231002)(https://shengshuqiang.github.io/assets/React算法用户点击渲染时间线-横版.png)]
Talk is cheap. Show me the code.
function ReactNativeRenderer_render() {
const ClassComponent = 1;
const HostComponent = 5;
const HostText = 6;
const Snapshot = 256;
const Placement = 2;
const Update = 4;
const PlacementAndUpdate = 6;
const Deletion = 8;
const root = {};
const {current, workInProgress, rootContainerInstance} = root;
let {nextUnitOfWork, nextEffect} = root;
const {oldProps, newProps, oldState, newState, oldContext, newContext} = workInProgress;
/** performWorkOnRoot */
{
/** renderRoot */
{
// 深度优先遍历完所有Fiber节点
/** workLoop */
{
while (nextUnitOfWork !== null) {
/** performUnitOfWork */
{
/** beginWork */
{
// 判断数据是否变化(属性相关)
const hasDataChanged = {}
// 数据没有变化,则直接当前Fiber节点克隆出工作Fiber节点,详见bailoutOnAlreadyFinishedWork
const bailoutOnAlreadyFinishedWork = function (workInProgress) {
};
if (!hasDataChanged) {
nextUnitOfWork = bailoutOnAlreadyFinishedWork(workInProgress);
} else {
// 数据变化,重新创建Fiber节点
/** updateXXX */
switch (workInProgress.tag) {
case ClassComponent: {
/** updateClassComponent
* 调用生命周期,新旧取决于用于在类里面增加的方法是新还是旧,
* 如果都有则只调用新的,新生命周期对应construct->getDerivedStateFromProps->render,
* 旧生命周期对应construct->componentWillMount->UNSAFE_componentWillMount->render。
* nextChildren = instance.render()
* */
{
let instance = workInProgress.stateNode;
// 根据是否有新生命周期方法判断是否要调用旧生命周期
const ctor = workInProgress.type;
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles = ctor.getDerivedStateFromProps && instance.getSnapshotBeforeUpdate;
if (instance === null) {
// 初始创建
/** constructClassInstance */
{
// 调用construct实例化组件
instance = new ctor();
}
/** mountClassInstance */
{
/** applyDerivedStateFromProps */
{
// 调用新生命周期getDerivedStateFromProps
getDerivedStateFromProps(newProps, oldState);
}
if (!hasNewLifecycles) {
// 调用旧生命周期
/** callComponentWillMount */
{
// 调用旧生命周期componentWillMount
instance.componentWillMount();
instance.UNSAFE_componentWillMount();
}
}
}
} else {
// 已存在,则Diff更新(为了简化,忽略resumeMountClassInstance)
/** updateClassInstance */
{
// 更新实例
let shouldUpdate;
const hasPropsChanged = oldProps !== newProps || oldContext !== newContext;
if (!hasNewLifecycles && hasPropsChanged) {
// 无新生命周期且属性变化
/** callComponentWillReceiveProps */
{
// 调用旧生命周期componentWillReceiveProps
instance.componentWillReceiveProps(newProps, newContext);
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
}
}
/** applyDerivedStateFromProps */
{
// 调用新生命周期getDerivedStateFromProps
getDerivedStateFromProps(newProps, oldState);
}
/** checkShouldComponentUpdate */
{
if (instance.shouldComponentUpdate) {
// 刷新逻辑交个用户控制,也就是大家说的高性能操作
shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
} else if (ctor.prototype.isPureReactComponent) {
// 纯组件,进行浅比较判断是否刷新
const shallowEqual = function () {
};
shouldUpdate = !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
} else {
// 普通组件,直接刷新
shouldUpdate = true;
}
}
if (shouldUpdate) {
if (!hasNewLifecycles) {
// 调用旧生命周期componentWillUpdate
instance.componentWillUpdate(newProps, newState, newContext);
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
}
}
}
}
/** finishClassComponent */
{
if (!shouldUpdate) {
nextUnitOfWork = bailoutOnAlreadyFinishedWork(workInProgress);
} else {
const nextChildren = instance.render();
/** reconcileChildFibers
* 硬核Diff算法
* */
{
const isObject = typeof nextChildren === "object" && nextChildren;
if (isObject) {
/** reconcileSingleElement */
{
if (workInProgress) {
// 判断key是否相等
const isKeyEquals = workInProgress.key === nextChildren.key;
if (isKeyEquals) {
// 判断类型是否相同
const isTypeEquals = child.elementType === nextChildren.type;
if (isTypeEquals) {
// Diff算法:类型相同,复用子节点树&删除子节点兄弟树
(function deleteRemainingChildren(sibling) {
})(workInProgress.sibling);
workInProgress.child = (function useFiber(workInProgress) {
})(workInProgress);
} else {
// Diff算法:类型不相同,删除全部子节点树
(function deleteRemainingChildren(sibling) {
})(workInProgress);
// Diff算法:新建子节点
workInProgress.child = (function createFiberFromElement(nextChildren) {
})(nextChildren);
}
} else {
// Diff算法:key不同,删除子节点树
(function deleteChild(sibling) {
})(workInProgress);
// Diff算法:新建子节点
workInProgress.child = (function createFiberFromElement(nextChildren) {
})(nextChildren);
}
}
}
} else {
/** 暂时忽略 */
// string、number
// array
// iterator
// undefined
// deleteRemainingChildren(returnFiber, currentFirstChild)
}
}
nextUnitOfWork = workInProgress.child;
}
}
}
break;
}
default:
/** 忽略,不重要 */
break;
}
}
nextUnitOfWork = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
}
if (nextUnitOfWork === null) {
{
/** completeUnitOfWork
* 深度优先遍历回溯,调用桥UIManager创建&连接Native View。
* 同时生成副作用链表。
* */
while (true) {
// nextUnitOfWork = completeWork(
/** completeWork */
{
switch (workInProgress.tag) {
case HostComponent: {
// 是否已实例化
const hasInstance = current && workInProgress.stateNode != null;
if (hasInstance) {
/** updateHostComponent$1 */
{
if (oldProps !== newProps) {
const updatePayload = (function prepareUpdate(workInProgress) {
})(workInProgress);
workInProgress.updateQueue = updatePayload;
}
}
} else {
let instance;
const tag = (function allocateTag() {
})();
const ReactNativeViewConfigRegistry = function () {
};
const viewConfig = ReactNativeViewConfigRegistry.get(workInProgress);
const updatePayload = create(props, viewConfig.validAttributes);
/** createInstance */
{
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload // props
);
function ReactNativeFiberHostComponent() {
};
instance = new ReactNativeFiberHostComponent(tag, viewConfig)
}
/** appendAllChildren */
{
(function appendAllChildren(workInProgress) {
})(workInProgress);
}
/** finalizeInitialChildren */
{
const {parentInstance, nativeTags} = workInProgress;
UIManager.setChildren(
parentInstance._nativeTag, // containerTag
nativeTags // reactTags
);
}
}
}
break;
case HostText: {
// 是否已实例化
const hasInstance = current && workInProgress.stateNode != null;
const {oldText, newText} = [oldProps, newProps];
if (hasInstance) {
/** updateHostText$1 */
{
if (oldText !== newText) {
/** createTextInstance */
{
const tag = (function allocateTag() {
})();
UIManager.createView(
tag, // reactTag
"RCTRawText", // viewName
rootContainerInstance, // rootTag
{text: newText} // props
);
workInProgress.stateNode = tag;
}
}
}
} else {
/** createTextInstance */
{
const tag = (function allocateTag() {
})();
UIManager.createView(
tag, // reactTag
"RCTRawText", // viewName
rootContainerInstance, // rootTag
{text: newText} // props
);
workInProgress.stateNode = tag;
}
}
}
break;
default:
/** 忽略 */
break;
}
}
if (nextUnitOfWork !== null) {
return nextUnitOfWork;
}
/** 自底向上归并有效副作用节点,连接成副作用链表 */
}
nextUnitOfWork = completeUnitOfWork(workInProgress);
}
}
}
}
}
}
const {finishedWork} = root;
const {firstEffect} = finishedWork;
if (finishedWork !== null) {
/** completeRoot */
{
const instance = finishedWork.stateNode;
/** commitRoot */
{
nextEffect = firstEffect;
while(nextEffect) {
/** commitBeforeMutationLifeCycles */
{
switch (finishedWork.tag) {
case ClassComponent:
{
if (finishedWork.effectTag & Snapshot) {
// 调用新生命周期getSnapshotBeforeUpdate
instance.getSnapshotBeforeUpdate(oldProps, oldState);
}
}
break;
default:
// 忽略
break;
}
}
nextEffect = nextEffect.nextEffect;
}
nextEffect = firstEffect;
while(nextEffect) {
/** commitAllHostEffects */
{
var {effectTag} = nextEffect;
var primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement:
{
/** commitPlacement */
{
const {containerID, moveFromIndices, moveToIndices, addChildReactTags, addAtIndices, removeAtIndices} = finishedWork;
UIManager.manageChildren(containerID, moveFromIndices, moveToIndices,addChildReactTags,addAtIndices,removeAtIndices);
}
}
break;
case PlacementAndUpdate:
{
/** commitPlacement */
{
const {containerID, moveFromIndices, moveToIndices, addChildReactTags, addAtIndices, removeAtIndices} = finishedWork;
UIManager.manageChildren(containerID, moveFromIndices, moveToIndices,addChildReactTags,addAtIndices,removeAtIndices);
}
/** commitWork */
{
switch (finishedWork.tag) {
case HostComponent:
{
/** commitUpdate */
const {reactTag, viewName, props} = finishedWork;
UIManager.updateView(reactTag, viewName, props);
}
break;
case HostText:
{
/** commitTextUpdate */
const {reactTag, props} = finishedWork;
UIManager.updateView(reactTag, "RCTRawText", props);
}
break;
default:
// 忽略
break;
}
}
}
break;
case Update:
{
/** commitWork */
{
switch (finishedWork.tag) {
case HostComponent:
{
/** commitUpdate */
const {reactTag, viewName, props} = finishedWork;
UIManager.updateView(reactTag, viewName, props);
}
break;
case HostText:
{
/** commitTextUpdate */
const {reactTag, props} = finishedWork;
UIManager.updateView(reactTag, "RCTRawText", props);
}
break;
default:
// 忽略
break;
}
}
}
break;
case Deletion:
{
/** commitDeletion */
{
/** commitUnmount */
{
let {node} = root;
while(true) {
switch (node.tag) {
case ClassComponent:
{
instance.componentWillUnmount();
}
break;
default:
// 忽略
break;
}
node = node.sibling;
if (!node.return) {
break;
}
}
}
}
}
break;
default:
// 忽略
break;
}
}
nextEffect = nextEffect.nextEffect;
}
root.current = finishedWork;
nextEffect = firstEffect;
while(nextEffect) {
/** commitAllLifeCycles */
{
const current$$1 = nextEffect.alternate;
switch (finishedWork.tag) {
case ClassComponent:
{
if (current$$1 === null) {
// 调用生命周期componentDidMount
instance.componentDidMount();
} else {
// 调用新生命周期componentDidUpdate
instance.componentDidUpdate(oldProps, oldState);
}
}
break;
default:
// 忽略
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Bm1svjG-1605447231003)(https://shengshuqiang.github.io/assets/React源码解析.png)]
让浏览器休息好,浏览器就能跑得更快。
留到下期。
基于上面插件,同理研发。
我的数据映射关系怎么来的,这个插件是怎么写的,这又可以再写一篇。
感谢岳母大人和媳妇大人的默默付出,感谢士兴大佬、朝旭大神、车昊大哥、张杰大哥、思文大拿、陈卓大牛的技术支持和迷津指点。
曾经在知乎看到一个问题,“能魔改react-native源码的是什么水平的前端?”我挑战了这个水平。
Airbnb摇了摇头,说“ RN太难了”,然后倒下了。但是我们,必须,站到台前,领导大家~
谨以此文,献给(那些)曾经热爱互联网技术,并和并肩作战的伙伴们一同度过时光的人们,呈现这重逢的此刻。