本文基于源码:0.40.0
最近把yoga源码看了一遍,它是一个按照Flexbox ( https://www.w3.org/TR/css3-flexbox )规范,利用Web熟悉的API做高效measure的库。本来想做个它在React Native for Android (以下简称RN4A)中的应用与分析,但是在这之前应该先将RN渲染流程搞懂,我们才能去进一步分析如何应用yoga去辅助测量。
如果只是开发Web应用,都要给babel添加React preset ( https://babeljs.io/docs/plugins/preset-react/ ),如果是开发RN,那么它都内置做好了,使用者体会不到。这个preset的作用,就是将JSX语法转化为纯js的代码。转码的结果可以参考React Without JSX ( https://facebook.github.io/react/docs/react-without-jsx.html )。
我拿了一个最普通的文件来做转码示例,原始的React代码如下:
export default class Sample extends Component {
render() { return ( <View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native! </Text>
</View>
);
}
}
AppRegistry.registerComponent('Sample', () => Sample);
转码后:(经过部分精简)
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: function render() { 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);
_reactNative.AppRegistry.registerComponent('Sample', function () { return Sample;
});
我们可以看到,原先JSX的控件都被React.createElement ( https://facebook.github.io/react/docs/react-api.html#createelement )转化为ReactElement
。在ReactElement
中使用type字段存放原始对象(在此处就是ReactNative.View
/ReactNative.Text
),使用props存放childrens、其他传入属性等。
JSX仅是一个语法糖,实际上它们最终都是React.createElement
这样的原始写法。
渲染是一个流程,它不会平白无故发生,总是有触发时机。第一次渲染在启动的时候,也是最容易把控到痕迹的时候,我们可以通过它去一步步剖析这个流程:
首先,ReactNative的js代码都需要通过AppRegistry.registerComponent
注册对应appkey的Component才能被启动。我们可以在AppRegistry.js
中看到它注册了一个对应的回调,在Native启动过程中会通过jsbridge调用AppRegistry.runApplication
启动js渲染流程,在js中会调用对应runnable,即后面的renderApplication
。(关于RN4A启动流程与jsBridge初始化可以参考:【ReactNative For Android】框架启动核心路径剖析 ( https://zhuanlan.zhihu.com/p/20807406?refer=magilu )与【React Native for Android】jsBridge实现原理 ( http://blog.desmondyao.com/rn-bridge/ ))
在renderApplication
时会将传入的Component变成ReactElement
,包裹在AppContainer
中,这个AppContainer
主要用于外面包围一些Debug用的工具(如红盒)。在这之后如上述流程图中一步步走了下去,没什么其他分支,走到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;
}
这里将传入的Element都用TopLevelWrapper
进行封装,但是它直接透传目标给render
函数,可以暂时忽略这层。这里通过instantiateReactComponent
生成了一个渲染对象实例,将batchedMountComponentIntoNode()
方法提交入回调Queue,它里面最终会走到ReactReconciler.mountComponent
里面,直接调用instance.mountComponent
。
接下来就有两处关键地方要理解了:
instantiateReactComponent
利用输入的ReactElement
生成了什么东西?instance.mountComponent
怎么进行渲染?先解释一下instantiateReactComponent
的作用。
从一定角度上来说,React的组件可以分为两种:
View
/Image
这种。不同平台有不同的元组件实现,在Web上的组件实现可以参考深入理解react(源码分析) ( https://github.com/lanjingling0510/blog/issues/1 ),在RN上见后续分析ReactNative的组件 。React.createClass
来构建,提供render()
方法返回渲染目标(ES6中可以继承React.Component
)。在React核心库中提供了instantiateReactComponent.js
,供渲染平台调用。它在碰见ReactElement
时会根据其中的type生成元组件或者复合组件,逻辑如下:
在React早先的一个版本中,将代码拆分为React与ReactDOM(见Two Packages: React and React DOM ( https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#two-packages-react-and-react-dom ))。核心的React包中包含了基础的createElement/createClass/生命周期等React相关、渲染平台无关的代码。其中绿色的部分是React核心库提供的,蓝色部分是需要渲染平台提供的。
instantiateReactComponent
中依次判断了如下流程:
ReactHostComponent.injectGenericComponentClass
这个API来注入生成组件逻辑,ReactDOM注入了这层处理,生成ReactDOMComponent
,而RN不处理。若type函数的原型链中具有元组件API时,则new一个type实例;否则就生成一个ReactCompositeComponent
(即复合组件);ReactHostComponent.injectTextComponentClass
来注入组件生成逻辑;判断函数对象是否元组件,它的原型链中需要提供以下两个API:
mountComponent
在首次渲染组件时调用;receiveComponent
更新组件内容、属性时调用;在ReactCompositeComponent
中也包含有这两个API供调用,如在mountComponent
时:
它会获取render()
方法返回的渲染节点,并对它继续走instantiate
/mountComponent
的流程。如果render()
返回的节点还是自定义的复合组件,那这个流程还会向下走,也即:mount流程会递归向下调用直到最后一个元组件。
我们可以拿任意一个UI控件来入手看,就拿最简单的View
来看一下,它的代码在Libraries/Components/View/View.js下,其实是一个复合组件,但是它的render
方法返回的是一个元组件,看一下相关代码:
// View.jsconst View = React.createClass({ // 属性声明...
render: function() { return <RCTView {...this.props} />;
},
});const RCTView = requireNativeComponent('RCTView', View, {
nativeOnly: {
nativeBackgroundAndroid: true,
nativeForegroundAndroid: true,
}
});
那这个requireNativeComponent
做的什么呢?紧接着来看看:
// requireNativeComponent.jsfunction requireNativeComponent(
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
的构造函数。这下知道了为什么上面instantiateReactComponent
里面使用的是“type是否为函数”的判断了吧。如果type为函数,并且原型链中含有元组件API,它就会用new一个这个函数实例。
ReactNativeBaseComponent
会在mountComponent
/receiveComponent
时对自己的child进行递归调用,以mount为例:
其中ReactMultiChild
负责对节点的children进行递归调用、instantiate/mount,UIManager
负责所有生成Native组件的操作。
整个过程就是这样,RN将代码由JSX转化为JS组件,启动过程中利用instantiateReactComponent
将ReactElement转化为复合组件ReactCompositeComponent
与元组件ReactNativeBaseComponent
,利用ReactReconciler
对他们进行渲染。
下一篇中会讲述ReactNative如何利用UIManager组建Native的视图结构,敬请期待。