React-Native-源码分析二-JSX如何渲染成原生页面(上)

本文跳过了React-Native 的通讯过程,详细请参考大头鬼写的Java和JS的通讯原理,虽然0.33版本加入了懒加载,原来配置表生成的时机和方式发生了改变,但是原理还是没有改变:通过约定的JSON,解析出moduleName,function name,然后通过本地找到对应的模块中的方法,然后通过反射执行这些方法,实现调用。

这篇将从Android原生反推JSX如何最终变化为原生控件的过程。

博主使用的环境是(版本很重要,RN发展飞快,不同的版本之间可能有差别)

“react”: “15.3.1”,

“react-native”: “^0.33.0”,

React-Native 源码分析一-如何启动JS页面的最后一步,我们看到XReactInstanceManagerImpl.java的attachMeasuredRootViewToInstance方法中有设置View的逻辑

private void attachMeasuredRootViewToInstance(
    ...
    UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
    int rootTag = uiManagerModule.addMeasuredRootView(rootView);
    rootView.setRootViewTag(rootTag);
    ...
  }

可以看到uiManagerModule.addMeasuredRootView(rootView)这个方法好像很厉害的样子,进去看看

public int addMeasuredRootView(final SizeMonitoringFrameLayout rootView) {
    //去掉了宽高赋值的代码

    mUIImplementation.registerRootView(rootView,tag,width,height,themedRootContext);
    //忽略了setOnSizeChangedListener

    return tag;
  }

去掉无关代码之后,可以看到 mUIImplementation.registerRootView(rootView,themedRootContext)方法传递了view和相关宽,高,theme信息进去,进去看代码发现利用这些数据构造了一个ReactShadowNode,然后add到了mOperationsQueue中,一看到Queue立马想到肯定有个UI相关的轮循在处理UI绘制事务。

public void registerRootView(
      SizeMonitoringFrameLayout rootView,int tag,int width,int height,ThemedReactContext context) {
    final ReactShadowNode rootCSSNode = createRootShadowNode();
    rootCSSNode.setReactTag(tag);
    rootCSSNode.setThemedContext(context);
    rootCSSNode.setStyleWidth(width);
    rootCSSNode.setStyleHeight(height);

    mShadowNodeRegistry.addRootNode(rootCSSNode);

    // register it within NativeViewHierarchyManager
    mOperationsQueue.addRootView(tag,rootView,context);
  }

所以我们先放下这里,回到上一个方法addMeasuredRootView的注释

/** * Registers a new root view. JS can use the returned tag with manageChildren to add/remove * children to this view. * * Note that this must be called after getWidth()/getHeight() actually return something. See * CatalystApplicationFragment as an example. * * TODO(6242243): Make addMeasuredRootView thread safe * NB: this method is horribly not-thread-safe. */

js能根据tag,使用manageChildren 来添加,删除 rootview中的子view

那么可以猜想manageChildren 可能是js直接控制原生代码增删布局的入口,来看下

@ReactMethod
  public void manageChildren(
      int viewTag,@Nullable ReadableArray moveFrom,@Nullable ReadableArray moveTo,@Nullable ReadableArray addChildTags,@Nullable ReadableArray addAtIndices,@Nullable ReadableArray removeFrom) {

    mUIImplementation.manageChildren(
        viewTag,moveFrom,moveTo,addChildTags,addAtIndices,removeFrom);
  }

果然这是个用ReactMethod注解过的方法,代表这他要被JS直接调用,从注释:Interface for adding/removing/moving views within a parent view from JS也能知道js通过这个方法增删改view,同样有@ReactMethod注解的类还有:createView,removeRootView,updateView,setChildren,replaceExistingNonRootView,removeSubviewsFromContainerWithID,measure,measureInWindow。。。等等方法,随便找了一个方法看一下,比如createView

@ReactMethod
  public void createView(int tag,String className,int rootViewTag,ReadableMap props) {
    if (DEBUG) {
      FLog.d(
          ReactConstants.TAG,"(UIManager.createView) tag: " + tag + ",class: " + className + ",props: " + props);
    }
    mUIImplementation.createView(tag,className,rootViewTag,props);
  }

继续进mUIImplementation.createView,

public void createView(int tag,ReadableMap props) {
    ReactShadowNode cssNode = createShadowNode(className);
    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
    cssNode.setReactTag(tag);
    cssNode.setViewClassName(className);
    cssNode.setRootNode(rootNode);
    cssNode.setThemedContext(rootNode.getThemedContext());

    mShadowNodeRegistry.addNode(cssNode);

    ReactStylesDiffMap styles = null;
    if (props != null) {
      styles = new ReactStylesDiffMap(props);
      cssNode.updateProperties(styles);
    }

    handleCreateView(cssNode,styles);
  }

构造一个ReactShadowNode,其中createShadowNode 是通过className 找到之前注册的ViewManager比如ReactTextInputManager,再设置他的rootNode,最后handleCreateView

protected void handleCreateView(
      ReactShadowNode cssNode,@Nullable ReactStylesDiffMap styles) {
    if (!cssNode.isVirtual()) {
      mNativeViewHierarchyOptimizer.handleCreateView(cssNode,cssNode.getThemedContext(),styles);
    }
  }

  public void handleCreateView(
      ReactShadowNode node,ThemedReactContext themedContext,@Nullable ReactStylesDiffMap initialProps) {
    if (!ENABLED) {
      int tag = node.getReactTag();
      mUIViewOperationQueue.enqueueCreateView(
          themedContext,node.getViewClass(),initialProps);
      return;
    }
  }

public void enqueueCreateView(
      ThemedReactContext themedContext,int viewReactTag,String viewClassName,@Nullable ReactStylesDiffMap initialProps) {
    synchronized (mNonBatchedOperationsLock) {
      mNonBatchedOperations.addLast(
        new CreateViewOperation(
          themedContext,viewReactTag,viewClassName,initialProps));
    }
  }

这样一路跟下去,我们会发现,如果要createView的一个View,最后只是在ArrayDeque mNonBatchedOperations中add了一个CreateViewOperation(),很敏感的会发现UIOperation 是抽象的接口

public interface UIOperation {
    void execute();
  }

果然只有一个接口execute,那自然的还有很多实现了UIOperation的类比如:RemoveRootViewOperation,ChangeJSResponderOperation,ShowPopupMenuOperation等等,
之前我们好像隐约的感觉到有个UI轮询在不停的执行这些UIOperation,也就是业务方只需要往池子里面添加就行,这样的队列在Android很多系统中都有遇到,比如Handle还有EventBus,有兴趣的读者可以看一下我之前的一个总结
这个类的名字com/facebook/react/uimanager/UIViewOperationQueue.java 所以大胆的在里面找轮训的代码,很快我们发现了dispatchViewUpdates方法

void dispatchViewUpdates(final int batchId) {
                ...
                 if (nonBatchedOperations != null) {
                   for (UIOperation op : nonBatchedOperations) {
                     op.execute();
                   }
                 }

                 ...
           });
    }

在一个线程数组中添加了一个线程,专门for循环调用各自的execute()方法,这里举个例子CreateViewOperation

private final class CreateViewOperation extends ViewOperation {

    private final ThemedReactContext mThemedContext;
    private final String mClassName;
    private final @Nullable ReactStylesDiffMap mInitialProps;

    public CreateViewOperation(
        ThemedReactContext themedContext,@Nullable ReactStylesDiffMap initialProps) {
      super(tag);
      mThemedContext = themedContext;
      mClassName = className;
      mInitialProps = initialProps;
      Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW,"createView",mTag);
    }

    @Override
    public void execute() {
      Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW,mTag);
      mNativeViewHierarchyManager.createView(
          mThemedContext,mTag,mClassName,mInitialProps);
    }
  }

执行execute方法也就是 执行mNativeViewHierarchyManager.createView

public void createView(
      ThemedReactContext themedContext,int tag,@Nullable ReactStylesDiffMap initialProps) {
        ...
    try {
      ViewManager viewManager = mViewManagers.get(className);

      View view = viewManager.createView(themedContext,mJSResponderHandler);
      mTagsToViews.put(tag,view);
      mTagsToViewManagers.put(tag,viewManager);

      view.setId(tag);
      if (initialProps != null) {
        viewManager.updateProperties(view,initialProps);
      }
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
    }
  }

这里的mViewManagers.get(className) 是根据className找到之前MainReactPackage里面添加的各种ViewManagers,然后调用ViewManager的createView方法,因为ViewManager是父类,他的createView里调用抽象方法createViewInstance,看下面代码

public final T createView(
      ThemedReactContext reactContext,JSResponderHandler jsResponderHandler) {
    T view = createViewInstance(reactContext);
    addEventEmitters(reactContext,view);
    if (view instanceof ReactInterceptingViewGroup) {
      ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
    }
    return view;
  }

 protected abstract T createViewInstance(ThemedReactContext reactContext);

createViewInstance 抽象方法是每个子类必须要实现的方法,也是正在构造View的方法,还是举个例子:ReactTextInputManager

public class ReactTextInputManager extends BaseViewManager<ReactEditText,LayoutShadowNode> {

  /* package */ static final String REACT_CLASS = "AndroidTextInput";


  @Override
  public String getName() {
    return REACT_CLASS;
  }

  @Override
  public ReactEditText createViewInstance(ThemedReactContext context) {
    ReactEditText editText = new ReactEditText(context);
    int inputType = editText.getInputType();
    editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
    editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
    editText.setTextSize(
        TypedValue.COMPLEX_UNIT_PX,(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)));
    return editText;
  }
}

他的createViewInstance方法就是new ReactEditText(context),到这里一个View已经创建完成,那么他的属性在哪里设置?放心JS已经将生成一个View要的数据都带了回来,initialProps中就是jsx中的style,viewManager.updateProperties(view,initialProps);再下面就是解析,设置属性,然后在在rootView中测量大小,确定位置,原生的UI渲染就完成了,期间细节太繁琐,不容易都写出来,只是描述一个流程,如果真正了解绘制细节的,还有好几个重要的类需要慢慢解析,请需求的同学自行解读。

前文中这次会反推JSX如何最终变化为原生控件的过程,上面这部分算是原生的绘制已经结束,下面开始到JS代码中找,JSX布局如何传达到原生的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom