目录
☞阅读原文
有的人可能会不理解,大前端平台化的战火为谁而燃,吾辈何以为战?
专注于移动互联网大前端致富,一直是我们最崇高的理想,而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解析文章太少了(老外写的硬核文章居多),而且大多停留在理论层面,只给出算法理论和源码片段,难以深入微操,只能作者说啥就是啥,反正不明觉厉。也罢,自力更生啃源码必须提上日程。
我始终相信,只有源码才是唯一的真相,不二的注释,思想的火花,王者的农药。
事实上,终于在眼泪中明白并验证了这一点,源码大法好啊,得到的比想要的多得多(贫穷限制了我的想象)。往小的说,技术成长(自嗨)。往大的说,核心竞争力(钱)。
本文和你分享的是如何通过先进生产力相对轻松地看懂代码,区别于呆板的流水式英文阅读,挑战一下:
- 承上(用户态–上层API怎么用)
- 组件中方法(constructor、setState、forceUpdate、render)的作用是什么?
- 生命周期调用时机是什么?
- 子组件变化,父组件和兄弟组件是否会刷新?state和props变化,有什么不一样?
- PureComponent比Component好在哪里,怎么能做得更好?
- 最佳实践(JSX不创建临时函数,Immutable,性能优化)?
- 启下(内核态–底层原理怎么玩)
- 各种概念的含义,对应数据结构是什么?
- 深入浅出Fiber双树算法?
- Diff算法在哪?
- Native操作指令从哪来?
解惑(考考你)
聪明的童靴往往都会有一些亟需亲自操刀的疑问,我也不能免俗。问题有了,那满意的答案呢?
组件
- 明明只写了几个组件,通过react-devtools看到的却是一堆布局,而且还有Context.Consumer,这些啥时候冒出来的,干啥的?
- React组件和Native View看起来不是一一对应的,那么映射关系是什么?
- 组件普通API调用时机、作用和最佳实践?
// 组件类
class Component<P, S> {
// 变量
props;
state;
// 方法
constructor(props, context);
setState(state, callback): void;
forceUpdate(callBack): void;
render(): ReactNode;
}
生命周期
- 区分哪些方法只会调用一次,哪些可能会调用多次?哪些方法中能使用setState,哪些不能?
- 区分每个方法调用条件,是props改变还是state,是初始化,更新还是都有?
- React16.3开始废弃和新增的方法是哪些,补位策略是什么?废弃方法现在还能不能用,新旧方法混用又怎样?
- 组件生命周期API调用时机、作用和最佳实践?
// 静态生命周期
interface StaticLifecycle {
getDerivedStateFromProps?: GetDerivedStateFromProps;
}
// 新生命周期
interface NewLifecycle<P, S, SS> {
getSnapshotBeforeUpdate?(prevProps, prevState): SS | null;
componentDidUpdate?(prevProps, prevState, snapshot): void;
}
// 废弃生命周期
interface DeprecatedLifecycle<P, S> {
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<P, S, SS> extends NewLifecycle<P, S, SS>, DeprecatedLifecycle<P, S> {
componentDidMount?(): void;
shouldComponentUpdate?(nextProps, nextState, nextContext): boolean;
componentWillUnmount?(): void;
componentDidCatch?(error, errorInfo): void;
}
数据结构
- 区分Element、Instance、DOM、Component、Fiber的不同含义以及之间关系?
- Fiber节点数据结构中各属性含义?
Virtual DOM
- Virtual DOM遇到了哪些假问题,又解决了哪些真问题?
- React有棵DOM树,树在哪,怎么看,怎么操作对应Native View树?
Diff算法
- Diff算法的策略是什么,能得出哪些最佳实践?
- 都说React有个Diff算法(Tree Diff 分层求异;Component Diff 同类同树,异类异树;Element Diff 增删移复用key),代码在哪里,怎么比较的?
原理
- React高效在哪?怎么做到的?
- React工作流程?
- 如何关联Native自定义组件?
- Fiber双树是啥?凭什么这么牛?
追过程
学习
我们不是一个人在战斗(想发财),切忌闭门造车,只有集思广益,站在巨人的肩膀上才能事半功倍。
网上一顿关键字索引,找点时间,给点耐心,泛读 + 精读数十篇后,你的感觉才能慢慢滴上来。
本着坚定看多,数量堆死力量,经过不间断的阅读输出,姿势见涨,比方说通过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 <Text style={
{color: 'black'}}>{'点击数0'}</Text>;
}
}
// 纯组件
class App extends React.PureComponent {
render() {
return <Text style={
{color: 'black'}}>{'点击数0'}</Text>;
}
}
// 函数式组件
const App = function () {
return <Text style={
{color: 'black'}}>{'点击数0'}</Text>;
}
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]]
UIMana