• 软件:1159
  • 资讯:38676|
  • 收录网站:95940|

IT精英团

深入浅出 setNativeProps:从实践窥视原理

深入浅出 setNativeProps:从实践窥视原理

浏览次数:
评论次数:
编辑: mob604756fdc4e1
信息来源: 51CTO博客
更新日期: 2021-05-13 16:04:05
摘要

深入浅出 setNativeProps:从实践窥视原理,mob604756fdc4e1的博客原创的Java文章。

  • 资讯详情

概述

有时候我们需要直接改动组件并触发局部的刷新,但不使用 state 或是 props。譬如在浏览器中使用React库,有时候会需要直接修改一个 DOM 节点,而在手机 App 中操作 View 时也会碰到同样的情况。在 React Native 中,setNativeProps 就是等价于直接操作 DOM 节点的方法。

  • 什么时候使用 setNativeProps 呢?在(不得不)频繁刷新而又遇到了性能瓶颈的时候。 

    • 直接操作组件并不是应该经常使用的工具。一般来说只是用来创建连续的动画,同时避免渲染组件结构和同步太多视图变化所带来的大量开销。setNativeProps 是一个“简单粗暴”的方法,它直接在底层(DOM、UIView 等)而不是 React 组件中记录 state,这样会使代码逻辑难以理清。所以在使用这个方法之前,请尽量先尝试用 setState 和 shouldComponentUpdate 方法来解决问题。 


  • 复合组件并不是单纯的由一个原生视图构成,所以你不能对其直接使用 setNativeProps。

  • 避免和 render 方法的冲突 

    • 如果要更新一个由 render 方法来维护的属性,则可能会碰到一些出人意料的 bug。因为每一次组件重新渲染都可能引起属性变化,这样一来,之前通过 setNativeProps 所设定的值就被完全忽略和覆盖掉了。

  • 通过巧妙运用 shouldComponentUpdate 方法,可以避免重新渲染那些实际没有变化的子组件所带来的额外开销,此时使用 setState 的性能已经可以与 setNativeProps 相媲美了。

  • 另外需要注意的是:文中为了更清晰的说明问题,提高关注点,会把大量的说明放到代码注释中。

demo实例

我们在 onScroll 中根据滚动的偏移量修改 _SearchViewContainerRef 的高度:

<View style={[styles.SearchViewContainer, {height: firstSearchBar - offset}]}      ref={(ref) => {          this._SearchViewContainerRef = ref;      }}></View>


this._SearchViewContainerRef && this._SearchViewContainerRef.setNativeProps({    style: {        height: Math.max(350 - heightDiff - height2Decrease, 0),    }});

setNativeProps

结论很重要,记住下面我们是用 View 组件来举例

如果你看过 NativeMethodsMixin.js 中的 setNativeProps 方法的实现,你就会发现它实际是对 RCTUIManager.updateView 的封装 —— 而这正是重渲染所触发的函数调用,具体可以参看 ReactNativeBaseComponent.js 中的 receiveComponent.

/*-----------------------------------------------------*//* 拿View组件举例,setNativeProps的实现其实就在下面这个文件中 *//* ReactNativeStack-prod.js                            *//*-----------------------------------------------------*/NativeMethodsMixin = {    measure: function(callback) {        UIManager.measure(findNumericNodeHandle$1(this),               mountSafeCallback$2(this, callback)        );    },    measureInWindow: function(callback) {        UIManager.measureInWindow(findNumericNodeHandle$1(this),               mountSafeCallback$2(this, callback)        );    },    measureLayout: function(relativeToNativeNode, onSuccess, onFail) {        UIManager.measureLayout(                findNumericNodeHandle$1(this),                 relativeToNativeNode, mountSafeCallback$2(this, onFail),                    mountSafeCallback$2(this, onSuccess)        );    },    // 这里是重点    setNativeProps: function(nativeProps) {        // injectedSetNativeProps$1的实现就看下面        injectedSetNativeProps$1(this, nativeProps);    },    focus: function() {        TextInputState.focusTextInput(findNumericNodeHandle$1(this));    },    blur: function() {        TextInputState.blurTextInput(findNumericNodeHandle$1(this));    }};/*----------------------------------------------------*//* 我们来看看injectedSetNativeProps$1的具体是怎么实现的呢?*//*----------------------------------------------------*/function setNativePropsStack$1(componentOrHandle, nativeProps) {    var maybeInstance = findNodeHandle_1(componentOrHandle);    if (null != maybeInstance) {        var viewConfig = void 0;        if (void 0 !== maybeInstance.viewConfig)             viewConfig = maybeInstance.viewConfig;         else if (void 0 !== maybeInstance._instance && void 0 !== maybeInstance._instance.viewConfig)             viewConfig = maybeInstance._instance.viewConfig;         else {            for (;void 0 !== maybeInstance._renderedComponent; )                   maybeInstance = maybeInstance._renderedComponent;            viewConfig = maybeInstance.viewConfig;        }        var tag = "function" == typeof maybeInstance.getHostNode ? maybeInstance.getHostNode() : maybeInstance._rootNodeID, updatePayload = ReactNativeAttributePayload_1.create(nativeProps, viewConfig.validAttributes);        // 这里是重点        UIManager.updateView(tag, viewConfig.uiViewClassName, updatePayload);    }}

必要时,扒一扒源码,理清源码,对于提高我们的全局把控能力非常有效!

/*----------------------------------------------------*//*我们继续看看NativeMethodsMixin是如何变为View组件的函数的?*//*----------------------------------------------------*//*我们通常使用View的时候需要这样引入View组件*/import {    View} from 'react-native';/*----------------------------------------------------*//*按下command,同时点击'react-native',我们扒一扒代码      *//*目前我们不需要关心@providesModule后面的名字为什么长这个样子*//*----------------------------------------------------*/@providesModule react-native-implementationconst ReactNative = {  // Components  get View() { return require('View'); },  // QAdd Start  // QAdd End  // APIs  // Plugins  // Prop Types  // Deprecated };module.exports = ReactNative;/*-----------------------------------------------------------*//*我们在下面这个文件中找到View组件的实现                           *//*node_modules/react-native/Libraries/Components/View/View.js*//*-----------------------------------------------------------*/@providesModule Viewconst View = createReactClass({  displayName: 'View',  mixins: [NativeMethodsMixin],//setNativeProps就在这个mixin里面  render: function() {    return <RCTView {...this.props} />;  },});

关键地方,我们插两句,希望对大家理解这篇文章有帮助:

  • 关于 mixin,我们已经在另一篇文章《深入浅出 JavaScript 核心概念(快速提升篇)》中介绍过了,这里不再重复介绍。

  • mixins 的 NativeMethodsMixin 中有 setNativeProps 方法。

  • 另外 RCTView 组件是通过 requireNativeComponent('RCTView', View, {...} 引入的,通过一步步追踪 requireNativeComponent 的实现,可以追到 ReactNativeBaseComponent, 其中也依然是 NativeMethodsMixin 那段代码。下面我们分别从这两个角度分别扒一下代码,从而让大家更加熟悉 RN-JS 部分的实现。

首先我们从 mixins 中扒一扒代码,看看 createReactClass 是怎么使用 mixins 的:

/*-------------------------------------------------------*//* 我们继续扒一扒createReactClass的代码                     *//* node_modules/create-react-class/create-react-class.js *//*-------------------------------------------------------*/{    else if(typeof exports === 'object')        exports["createReactClass"] = factory(require("react"));    else        root["createReactClass"] = factory(root["React"]);}function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) {    ...    return createClass;}/*--------------------------------------------*//* node_modules/create-react-class/factory.js *//*--------------------------------------------*/function createClass(spec) {//spec就是createReactClass()的参数,里面有一个mixins    var Constructor = identity(function(props, context, updater) {      // Wire up auto-binding      if (this.__reactAutoBindPairs.length) {        bindAutoBindMethods(this);      }      var initialState = this.getInitialState ? this.getInitialState() : null;      this.state = initialState;    });    /*这里是重点:ReactClassComponent*/    Constructor.prototype = new ReactClassComponent();    Constructor.prototype.constructor = Constructor;    Constructor.prototype.__reactAutoBindPairs = [];    injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));    mixSpecIntoComponent(Constructor, IsMountedPreMixin);    mixSpecIntoComponent(Constructor, spec);// 这里很重要,我们下面就要分析    mixSpecIntoComponent(Constructor, IsMountedPostMixin);    // Initialize the defaultProps property after all mixins have been merged.    if (Constructor.getDefaultProps) {        Constructor.defaultProps = Constructor.getDefaultProps();    }    return Constructor;}

关键地方,我们插两句,希望对大家理解这篇文章有帮助:

  • 关于 prototype,我们已经在另一篇文章《深入浅出 JavaScript 核心概念(快速提升篇)》中介绍过了,这里不再重复介绍。

  • mixSpecIntoComponent 函数做了大量非常重要的事情,但是我们需要记住我们现在的目标,我们是在理 setNativeProps 的逻辑,其他的我们无需理会。

  • 前面这段代码中有我们平时用的两个方法 this.getInitialState() 和 getDefaultProps(),不知道你有没有印象,在这里单独提一句是为了加深大家的理解。

/*其实mixSpecIntoComponent函数中做了很多非常重要的事情,我们暂时只关注其冰山一角*/function mixSpecIntoComponent(Constructor, spec) {     //处理mixins数组,我们可以先直接看下面对RESERVED_SPEC_KEYS.mixins的源码。    if (spec.hasOwnProperty(MIXINS_KEY)) {      RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);    }    // 处理单个的    for (var name in spec) {      var property = spec[name];      var isAlreadyDefined = proto.hasOwnProperty(name);      if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {        RESERVED_SPEC_KEYS[name](Constructor, property);      } else {        // Setup methods on prototype:        // The following member methods should not be automatically bound:        // 1. Expected ReactClass methods (in the "interface").        // 2. Overridden methods (that were mixed in).        var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);        var isFunction = typeof property === 'function';        var shouldAutoBind =          isFunction &&          !isReactClassMethod &&          !isAlreadyDefined &&          spec.autobind !== false;        if (shouldAutoBind) {          autoBindPairs.push(name, property);          proto[name] = property;        } else {          if (isAlreadyDefined) {            var specPolicy = ReactClassInterface[name];            // For methods which are defined more than once, call the existing            // methods before calling the new property, merging if appropriate.            if (specPolicy === 'DEFINE_MANY_MERGED') {              proto[name] = createMergedResultFunction(proto[name], property);            } else if (specPolicy === 'DEFINE_MANY') {              proto[name] = createChainedFunction(proto[name], property);            }          } else {            proto[name] = property;//这里这里,请看这里            if (process.env.NODE_ENV !== 'production') {              // Add verbose displayName to the function, which helps when looking              // at profiling tools.              if (typeof property === 'function' && spec.displayName) {                proto[name].displayName = spec.displayName + '_' + name;              }            }          }        }      }    }}/* RESERVED_SPEC_KEYS非常重要,但我们暂时依然只关注目前的重点内容*/var RESERVED_SPEC_KEYS = {    mixins: function(Constructor, mixins) {      if (mixins) {        for (var i = 0; i < mixins.length; i++) {          /*----------------------------------------*/          /* 又返回到上面mixSpecIntoComponent这个函数了 */          /* 至此,所有的mixins的东西都被挂到View组件里了 */          /*----------------------------------------*/          mixSpecIntoComponent(Constructor, mixins[i]);        }      }    },};

细心的你会不会有个疑问,View 组件中引用的 NativeMethodsMixin 就是上面提到的 minin 吗? const NativeMethodsMixin = require('NativeMethodsMixin'); 人家是这样引用的啊,你是不是该分析一下这里的 NativeMethodsMixin 是如何和最开始的 NativeMethodsMixin 是如何关联起来的呢?

/*------------------------------*//* NativeMethodsMixin又是什么鬼?*//*------------------------------*/@providesModule NativeMethodsMixinconst {  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,} = require('ReactNative');import type {NativeMethodsMixinType} from 'ReactNativeTypes';const {NativeMethodsMixin} = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;


  1. /*-----------------------------------------------*/


  2. /* 上面require('ReactNative')就是ReactNativeStack */

  3. /*-----------------------------------------------*/

  4. @providesModule ReactNative

  5. if (__DEV__) {

  6.  ReactNative = ReactNativeFeatureFlags.useFiber

  7.    ? require('ReactNativeFiber-dev')

  8.    : require('ReactNativeStack-dev');

  9. } else {

  10.  ReactNative = ReactNativeFeatureFlags.useFiber

  11.    ? require('ReactNativeFiber-prod')

  12.    : require('ReactNativeStack-prod');

  13. }


/*----------------------------------------------------*//* __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED *//*----------------------------------------------------*/@providesModule ReactNativeStack-prodvar NativeMethodsMixin_1 = NativeMethodsMixin;//这里的NativeMethodsMixin就是最开始提到的NativeMethodsMixin。ReactNativeStack = {    NativeComponent: ReactNativeComponent_1,    hasReactNativeInitialized: !1,    findNodeHandle: findNumericNodeHandleStack,    render: render,    unmountComponentAtNode: ReactNativeMount_1.unmountComponentAtNode,    unstable_batchedUpdates: ReactUpdates_1.batchedUpdates,    unmountComponentAtNodeAndRemoveContainer: ReactNativeMount_1.unmountComponentAtNodeAndRemoveContainer,    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {        NativeMethodsMixin: NativeMethodsMixin_1,//这个是重点        ReactGlobalSharedState: ReactGlobalSharedState_1,        ReactNativeComponentTree: ReactNativeComponentTree_1,        ReactNativePropRegistry: ReactNativePropRegistry_1,        TouchHistoryMath: TouchHistoryMath_1,        createReactNativeComponentClass: createReactNativeComponentClass,        takeSnapshot: takeSnapshot_1,        // QRN BEGIN        ReactNativeTagHandles: ReactNativeTagHandles_1,        ReactNativeBaseComponent: ReactNativeBaseComponent_1,        ReactNativeMount: ReactNativeMount_1,        ReactChildReconciler: ReactChildReconciler_1,        shouldUpdateReactComponent: shouldUpdateReactComponent_1,        ReactReconciler: ReactReconciler_1,        instantiateReactComponent: instantiateReactComponent,        ReactCompositeComponent: ReactCompositeComponent_1        // QRN END    }}var ReactNativeStackEntry = ReactNativeStack;module.exports = ReactNativeStackEntry;

换个角度来看问题,我们再从 requireNativeComponent 扒一扒 setNativeProps

  • 我们应该清楚也应该猜到,其实下面也有说明 ReactNativeBaseComponent 中就有 setNativeProps 方法。

  • 我们需要看看 RCTView 是不是继承自 ReactNativeBaseComponent,猜着应该是。

/*------------*//*分析从这里开始*//*------------*/const RCTView = requireNativeComponent('RCTView', View, {  nativeOnly: {    nativeBackgroundAndroid: true,    nativeForegroundAndroid: true,  }});


/*---------------------------------------------------*//*我们来看看View是如何继承ReactNativeBaseComponent方法的?*//*不过我们这次从上至下的分析                             *//*---------------------------------------------------*/var NativeMethodsMixin_1 = NativeMethodsMixin;Object.assign(    ReactNativeBaseComponent.prototype,// 这个是重点     ReactMultiChild_1,     ReactNativeBaseComponent.Mixin,     NativeMethodsMixin_1);
  • 我们看到 ReactNativeBaseComponent 的原型中确实有 NativeMethodsMixin。

var ReactNativeBaseComponent_1 = ReactNativeBaseComponentcreateReactNativeComponentClassStack = function(viewConfig) {    var Constructor = function(element) {        this._currentElement = element    };    return (        Constructor.displayName = viewConfig.uiViewClassName,        Constructor.viewConfig = viewConfig,         Constructor.propTypes = viewConfig.propTypes,         Constructor.prototype = new ReactNativeBaseComponent_1(viewConfig),         Constructor.prototype.constructor = Constructor, Constructor    );}, createReactNativeComponentClassStack_1 = createReactNativeComponentClassStack;createReactNativeComponentClass = ReactNativeFeatureFlags$3.useFiber ?                                   DevOnlyStubShim :                                   createReactNativeComponentClassStack_1
  • createReactNativeComponentClassStack 的中 Constructor 的 prototype 是 ReactNativeBaseComponent1,而 ReactNativeBaseComponent1 就是 ReactNativeBaseComponent。

ReactNativeStack = {    NativeComponent: ReactNativeComponent_1,    hasReactNativeInitialized: !1,    findNodeHandle: findNumericNodeHandleStack,    render: render,    unmountComponentAtNode: ReactNativeMount_1.unmountComponentAtNode,    unstable_batchedUpdates: ReactUpdates_1.batchedUpdates,    unmountComponentAtNodeAndRemoveContainer: ReactNativeMount_1.unmountComponentAtNodeAndRemoveContainer,    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {        NativeMethodsMixin: NativeMethodsMixin_1,        ReactGlobalSharedState: ReactGlobalSharedState_1,        ReactNativeComponentTree: ReactNativeComponentTree_1,        ReactNativePropRegistry: ReactNativePropRegistry_1,        TouchHistoryMath: TouchHistoryMath_1,        createReactNativeComponentClass: createReactNativeComponentClass,        takeSnapshot: takeSnapshot_1,        // QRN BEGIN        ReactNativeTagHandles: ReactNativeTagHandles_1,        ReactNativeBaseComponent: ReactNativeBaseComponent_1,        ReactNativeMount: ReactNativeMount_1,        ReactChildReconciler: ReactChildReconciler_1,        shouldUpdateReactComponent: shouldUpdateReactComponent_1,        ReactReconciler: ReactReconciler_1,        instantiateReactComponent: instantiateReactComponent,        ReactCompositeComponent: ReactCompositeComponent_1        // QRN END    }};
  • 仔细看看上面代码,createReactNativeComponentClass 给了谁。

  • 下面的这些代码就比较清晰了,我们继续:

@providesModule createReactNativeComponentClassconst {  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,} = require('ReactNative');module.exports =__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.createReactNativeComponentClass;


@providesModule requireNativeComponenfunction requireNativeComponent(  viewName: string,  componentInterface?: ?ComponentInterface,  extraConfig?: ?{nativeOnly?: Object},): ReactClass<any> | string {  const viewConfig = UIManager[viewName];  if (!viewConfig || !viewConfig.NativeProps) {    warning(false, 'Native component for "%s" does not exist', viewName);    return UnimplementedView;  }  viewConfig.uiViewClassName = viewName;  viewConfig.validAttributes = {};  let baseModuleName = viewConfig.baseModuleName;  let nativeProps = { ...viewConfig.NativeProps };  while (baseModuleName) {    const baseModule = UIManager[baseModuleName];    if (!baseModule) {      warning(false, 'Base module "%s" does not exist', baseModuleName);      baseModuleName = null;    } else {      nativeProps = { ...nativeProps, ...baseModule.NativeProps };      baseModuleName = baseModule.baseModuleName;    }  }  return createReactNativeComponentClass(viewConfig);}

纵然有一万种方法能把 View 和 setNativeProps 联系起来,其实就是想让大家明白 setNativeProps 的是怎么做到这么高效的,它的本质是什么而已;另外,通过一层一层的扒代码是为了让大家对 RN-JS 部分的代码更加熟悉,仅此而已,不必纠结能不能看懂,重要的是亲自动手扒一扒,理解一下。

写在最后

作为一篇学习 ReactNative-JS 端实现的总结性文章,里面难免有一些错误或者片面的理解。其实 UIManager.updateView 在 natvie 端的实现也有一大堆逻辑,限于篇幅,本篇到此为止,希望给客户端同学学习RN提供一些帮助。


标签: Java
反爬虫探索(2)--反爬虫
« 上一篇
返回列表
下一篇 »
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
你会是第一个来这里评论的人吗?
最近发布资讯
更多