React Native 核心渲染流程分析(1) - 初识组件系统

本文分析了React Native的js启动流程,从React组件的创建到元组件的生成。通过React.createElement转化JSX,然后在ReactNative启动时注册组件并执行渲染。重点讨论了如何生成React组件,包括元组件和复合组件的区别,并以View组件为例展示了元组件的工作方式。最后总结了整个渲染流程,为后续的UIManager和Native视图结构构建奠定基础。
摘要由CSDN通过智能技术生成

本文基于源码: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启动流程

渲染是一个流程,它不会平白无故发生,总是有触发时机。第一次渲染在启动的时候,也是最容易把控到痕迹的时候,我们可以通过它去一步步剖析这个流程:

首先,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

接下来就有两处关键地方要理解了:

  1. instantiateReactComponent利用输入的ReactElement生成了什么东西?
  2. 利用instance.mountComponent怎么进行渲染?

如何生成React组件

先解释一下instantiateReactComponent的作用。

从一定角度上来说,React的组件可以分为两种:

  • 元组件 框架内置的,可以直接用的组件。在RN上可以理解为就是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中依次判断了如下流程:

  1. 对象的type如果是string或function(至于为何是function,后面ReactNative的元组件中有解释)时走第一层逻辑。当type为string时,渲染平台可以通过ReactHostComponent.injectGenericComponentClass这个API来注入生成组件逻辑,ReactDOM注入了这层处理,生成ReactDOMComponent,而RN不处理。若type函数的原型链中具有元组件API时,则new一个type实例;否则就生成一个ReactCompositeComponent(即复合组件);
  2. 当对象为string时(此种情况对应直接写string,而不是用jsx包裹起来的语法),也交由渲染平台处理,可以通过ReactHostComponent.injectTextComponentClass来注入组件生成逻辑;
  3. 以上都不符合,则报错。

判断函数对象是否元组件,它的原型链中需要提供以下两个API:

  • mountComponent 在首次渲染组件时调用;
  • receiveComponent 更新组件内容、属性时调用;

ReactCompositeComponent中也包含有这两个API供调用,如在mountComponent时:

它会获取render()方法返回的渲染节点,并对它继续走instantiate/mountComponent的流程。如果render()返回的节点还是自定义的复合组件,那这个流程还会向下走,也即:mount流程会递归向下调用直到最后一个元组件

ReactNative的元组件

我们可以拿任意一个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的视图结构,敬请期待。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果 react-native-echarts-pro 组件React Native 中的图表溢出容器,可以通过以下两种方法进行解决: 1.设置容器的大小 可以在容器组件中设置宽度和高度,然后将宽度和高度作为 props 传递给 react-native-echarts-pro 组件。例如: ``` import React from 'react'; import { View, Dimensions } from 'react-native'; import Echarts from 'react-native-echarts-pro'; const windowWidth = Dimensions.get('window').width; const windowHeight = Dimensions.get('window').height; export default function App() { return ( <View style={{ width: windowWidth, height: windowHeight }}> <Echarts option={option} width={windowWidth} // 设置宽度 height={windowHeight} // 设置高度 /> </View> ); } ``` 2.使用 onLayout 事件设置图表的大小 可以在 react-native-echarts-pro 组件中使用 onLayout 事件来获取容器组件的宽度和高度,然后将宽度和高度设置给图表。例如: ``` import React, { useState } from 'react'; import { View, Dimensions } from 'react-native'; import Echarts from 'react-native-echarts-pro'; export default function App() { const [chartWidth, setChartWidth] = useState(0); const [chartHeight, setChartHeight] = useState(0); const onLayout = event => { const { width, height } = event.nativeEvent.layout; setChartWidth(width); setChartHeight(height); }; return ( <View style={{ flex: 1 }} onLayout={onLayout}> <Echarts option={option} width={chartWidth} height={chartHeight} /> </View> ); } ``` 以上两种方法均可将 react-native-echarts-pro 组件的大小设置为自适应容器的大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值