本文基于源码: 0.40.0
最近把yoga源码看了一遍,它是一个按照 Flexbox 规范, 利用Web熟悉的API做高效measure 的库。本来想做个它在React Native for Android (以下简称RN4A)中的应用与分析,但是在这之前应该先将RN渲染流程搞懂,我们才能去进一步分析如何应用yoga去辅助测量。
在理解本章内容之前,需要有一定React的基础。在初识React时,它的JSX语法令人惊艳,但是这个语法编写的代码是怎么被运行的呢?我们需要明白它,才能更好地理解React/ReactNative源码。
如果只是开发Web应用,都要给babel添加 React preset ,如果是开发RN,那么它都内置做好了,使用者体会不到。这个preset的作用,就是将JSX语法转化为纯js的代码。转码的结果可以参考 React Without JSX 。
我拿了一个最普通的文件来做转码示例,原始的React代码如下:
export default classSampleextendsComponent{
render() {
return (
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>
Welcome to React Native!
</Text>
</View>
);
}
}
转码后:(经过部分精简)
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactNative = require('react-native');
function_interopRequireDefault(obj){
return obj && obj.__esModule ? obj : {default: obj};
}
// 省略部分代码
Sample = function(_Component){
// 省略部分代码
_createClass(Sample, [{
key: 'render', value: functionrender(){
return (
_react2.default.createElement(_reactNative.View, {style: styles.container},
_react2.default.createElement(_reactNative.Text, {style: styles.welcome}, 'Welcome to React Native!')));
}
}]);
return Sample;
}(_react.Component);
以上代码比较简单, _createClass 方法构建是一个辅助定义property的方法,它向Sample对象中定义了传入的 render 方法,并让他继承于 React.Component 。我们可以看到,原先JSX的控件都被转换为通过 React.createElement 构建的对象。
这里面的 View / Text 都是React Native内置的 React Component 。大家如果想读懂React的源码,需要明白 JSX仅是一个语法糖 ,实际上他们都是一堆js类。
在React早先的一个版本中,将代码拆分为React与ReactDOM(见 Two Packages: React and React DOM )。
核心的React包中包含了基础的createElement/createClass/声明周期等React相关、平台无关的代码。在Web中,通过ReactDOM内的API可以进行DOM渲染/server端渲染。在ReactNative中也是类似,可以通过它进行Native组件的渲染、操作。
从一定角度上来说,React的组件可以分为两种:
<div> / <img> 这种,在RN上就是 ListView / Image 这种。 React.createClass (ES6中可以继承 React.Component )。 对应上面转码的例子中, Sample 是复合组件, View 是元组件。复合组件是自定义Class,通过 render() 方法返回渲染对象。但是不同平台有 不同的元组件实现 ,在Web上的组件实现可以参考 深入理解react(源码分析) ,在RN上见后续分析。但是在转码之后,他们就被 React.createElment 转化成了一个平台无关的 ReactElement ,我们可以看一下相关代码:
// ReactElement.js
// 转码后调用的createElement函数
ReactElement.createElement = function(type, config, children){
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
// 省略部分Debug代码
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 从config中复制值至props中
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// 赋值children
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// 为默认属性赋值
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 省略部分Debug代码
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};
var ReactElement = function(type, key, ref, self, source, owner, props){
var element = {
// 标识这是一个ReactElement
$$typeof: REACT_ELEMENT_TYPE,
type: type, // 具体组件Object
key: key,
ref: ref, // 获取对象引用的回调
props: props, // 传入属性
_owner: owner
};
// 省略部分Debug代码
return element;
}
在React核心库中提供了 instantiateReactComponent.js ,它会对 ReactElement 进行一些处理,它提供的逻辑如下:
这段代码的逻辑可以在 instantiateReactComponent.js 中看到,它是这样一个流程:当对象是 ReactElement 时,如果是元组件,就直接根据之前传入 ReactElement 的type生成实例;否则就生成一个 ReactCompositeComponent (即复合组件)。
判断ReactElement是否 元组件 ,它的type需要提供以下三个API:
mountComponent 在首次渲染组件时调用; receiveComponent 更新组件内容、属性时调用; unmountComponent 移除组件时调用。 这三个API是React核心库中约定好的API,他们在 ReactReconciler 中被使用,在这其中会处理Mount/Diff的逻辑。对于 复合组件 ,即 ReactCompositeComponent ,也提供了这三个API。它对应type是一个用户自定义Class,提供了 render 方法来渲染出自定义视图。 ReactCompositeComponent 就会在对应的API内,递归对所有的child进行 instantiateReactComponent.js ,并且对实例进行mount/update。
此处更多代码细节可以参考 深入理解react(源码分析) 。这个过程可能绕了点,在后面中我会举例辅助理解。
上面说到 ReactReconciler / instantiateReactComponent ,可能没看过的人会觉得有点懵逼。之前说明过,React应用分两块:React核心层与渲染层。它们都是在核心层中提供的API,但是调用却是在渲染层用的。我们先以ReactNative为例,讲述一下它是怎么调用React核心层来进行渲染。
渲染是一个流程,它不会平白无故发生,总是有触发时机。第一次渲染在启动的时候,也是最容易把控到痕迹的时候,我们可以通过它去一步步剖析这个流程:
首先,ReactNative的js代码都需要通过 AppRegistry.registerComponent 注册React Component,我们看看它干了啥:
//AppRegistry.js
registerComponent: function(appKey: string, getComponentFunc: ComponentProvider):string{
runnables[appKey] = {
run: (appParameters) =>
renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
};
return appKey;
}
这段代码里很简洁,注册了一个对应输入key的回调到runnables里面。RN4A中,生命周期的启动是由Android带起来的,这个 AppRegistry 是一个js module,在Java中会通过jsbridge调用 AppRegistry.runApplication 启动js渲染流程,在js中会调用对应runnable,由上面的代码可以看出,它调用了 renderApplication 。(关于RN4A启动流程与jsBridge初始化可以参考: 【ReactNative For Android】框架启动核心路径剖析 与 【React Native for Android】jsBridge实现原理 ) 。
在这之后如上述流程图中一步步走了下去,没什么其他分支,走到 ReactNativeMount 中就会有料出现了,我们来看看:
//ReactNativeMount.js
renderComponent: function(
nextElement: ReactElement<*>,
containerTag: number,
callback?: ?(() => void)
): ?ReactComponent<any, any, any> {
// 将Element使用相同顶层Wrapper包裹,render方法返回child(即nextElement)
var nextWrappedElement = React.createElement(
TopLevelWrapper,
{ child: nextElement }
);
// 检查之前是否有节点已mount到目标节点上,若有则进行比较处理
var instance = instantiateReactComponent(nextWrappedElement, false);
// 将mount任务提交入回调Queue
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
instance,
containerTag
);
// ...
return component;
}
这里我们可以看到它对直接调用了 instantiateReactComponent ,它首先碰见 TopLevelWrapper 会认为它是一个复合组件,所以直接生成了一个 ReactCompositeComponent 。之后将 batchedMountComponentIntoNode() 方法提交入回调Queue,它里面最终会走到 ReactNativeMount.mountComponentIntoNode ,我们看一下它做了什么:
//ReactNativeMount.js
functionmountComponentIntoNode(
componentInstance,
containerTag,
transaction) {
var markup = ReactReconciler.mountComponent(
componentInstance,
transaction,
null,
ReactNativeContainerInfo(containerTag),
emptyObject,
0 /* parentDebugID */
);
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
ReactNativeMount._mountImageIntoNode(markup, containerTag);
}
而 ReactReconciler.mountComponent 里面做的事情也很简单,就是对 componentInstance 调用 mountComponent 方法。在此处,就是调用了 ReactCompositeComponent.mountComponent 。它会对 render() 返回的组件调用 instantiateReactComponent / mountComponent ,即会 递归调用 mountComponent 直到为元组件为止。
不同平台有 不同的元组件实现 ,我们就先看看ReactNative的元组件是怎么实现的?
要找到这个答案比较简单,我们可以拿任意一个UI控件来看,就拿最简单的 View 来看一下,它的代码在Libraries/Components/View/View.js下,其实是一个复合组件,但是它的 render 方法返回的是一个元组件,看一下相关代码:
// View.js
const View = React.createClass({
// 属性声明...
render: function(){
return <RCTView{...this.props} />;
},
});
const RCTView = requireNativeComponent('RCTView', View, {
nativeOnly: {
nativeBackgroundAndroid: true,
nativeForegroundAndroid: true,
}
});
那这个 requireNativeComponent 做的什么呢?紧接着来看看:
// requireNativeComponent.js
functionrequireNativeComponent(
viewName: string,
componentInterface?: ?ComponentInterface,
extraConfig?: ?{nativeOnly?: Object},
): Function {
const viewConfig = UIManager[viewName]; // 由Native传入的对应ViewModule配置
viewConfig.uiViewClassName = viewName;
viewConfig.validAttributes = {};
viewConfig.propTypes = componentInterface && componentInterface.propTypes;
// 所有React视图控件的prop都继承View的prop
const nativeProps = {
...UIManager.RCTView.NativeProps,
...viewConfig.NativeProps,
};
return createReactNativeComponentClass(viewConfig);
}
在 createReactNativeComponentClass.js 中,我们可以看到它是返回了一个构造 ReactNativeBaseComponent 的 构造函数 。为什么是一个构造函数而不是直接生成实例呢?这就要回到上一个话题了。这些组件在babel转码后会被作为type放在 ReactElement 中, instantiateReactComponent 调用的时候,如果type为函数,并且是元组件,它就会用new一个这个函数实例。
上面说得稍微绕了点,我们用之前的的生成来描述一下。
用户自定义了 Sample 类,它渲染的节点是一个 View ,内嵌 Text 。记住他们转码后都会变成普通的 ReactElement ,当渲染引擎碰见 Sample 时,它会走这样一个流程:
instantiateReactComponent.js 判断到 Sample 是一个函数,但是它不具备元组件API,则生成一个复合组件 ReactCompositeComponent ; Sample 被 ReactReconciler mount时,调用自身的 mountComponent 。在这里面调用 render 获得渲染节点,并对它调用 instantiateReactComponent 与 ReactReconciler.mountComponent 。这时候发现被渲染的节点是一个 View ,它仍然算是一个复合组件(它的render节点返回的才是元组件ReactNativeBaseComponent); View 执行流程2,最终走入 ReactNativeBaseComponent.mountComponent 。 React有核心层和渲染层,其中核心层提供一些基础的组件创造、生命周期、Mount/Diff(入口 ReactReconciler )、复合组件( ReactCompositeComponent )等API,渲染层通过实现不同的元组件来实现不同平台上的渲染。
在ReactNative上,所有的原生视图的添加、修改都在 ReactNativeBaseComponent 中处理,这块的逻辑,请听之后分解。