react-native几个问题追查原因

1、原生端导出模块方法到js,怎么没有区分静态方法和对象方法?怎么导出单例对象?
追查:原生端导出的模块会添加到一个配置表中间。通过断点,发现程序启动时就会遍历该模块,将必须的模块以同步的方式在主线程依次初始化完毕,也就是说程序启动时就将所有导出模块的对象创建好了,并且只有一个对象,所有的导出模块都是单例对象。

以下是程序启动时,模块初始化方法的调用顺序图

第一张图
第二张图

2、PureComponent 为什么比Component高效?
追查:这两个组件的实现在ReactBaseClasses.js中间,除掉注释后如下

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.isReactComponent = {};

ReactComponent.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ReactPureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

function ComponentDummy() {}
ComponentDummy.prototype = ReactComponent.prototype;
ReactPureComponent.prototype = new ComponentDummy();
ReactPureComponent.prototype.constructor = ReactPureComponent;
_assign(ReactPureComponent.prototype, ReactComponent.prototype);
ReactPureComponent.prototype.isPureReactComponent = true;

module.exports = {
  Component: ReactComponent,
  PureComponent: ReactPureComponent
};

发现Component就只实现了构造方法,定义了setState方法就完了。而ReactPureComponent更狠,就只是用js原型模拟继承的方法继承了Component,然后定义属性isPureReactComponent为true。全局搜索isPureReactComponent属性,发现在ReactCompositeComponent.js中有使用,这个类就是管理组件的更新、加载等的。关键代码在updateComponent方法中,如下

    var shouldUpdate = true;    // 这个变量决定是否需要重新渲染组件
    if (!this._pendingForceUpdate) {
      var prevState = inst.state;
      shouldUpdate = willReceive || nextState !== prevState;
      // inst代表组件实例,这个判断条件就是组件是否自己实现了shouldComponentUpdate方法
      if (inst.shouldComponentUpdate) {
        if (__DEV__) {
          shouldUpdate = measureLifeCyclePerf(
            () => inst.shouldComponentUpdate(nextProps, nextState, nextContext),
            this._debugID,
            'shouldComponentUpdate',
          );
        } else {
          shouldUpdate = inst.shouldComponentUpdate(
            nextProps,
            nextState,
            nextContext,
          );
        }
      } else {// 组件没有实现shouldComponentUpdate方法,且是PureComponent,采用shallowEqual浅比较
        if (this._compositeType === ReactCompositeComponentTypes.PureClass) {
          shouldUpdate = !shallowEqual(prevProps, nextProps) ||
            !shallowEqual(inst.state, nextState);
        }
      }
    }

shallowEqual的实现在shallowEqual.js中,大意就是作浅比较,也就是对象数组等只比较对象所处的地址是否相等,而不比较具体的内容,因为深层次递归比较对象内容是否一致很耗费性能。

以上,结论:PureComponent是Component的子类,当PureComponent手动实现了shouldComponentUpdate方法时两个组件没有区别,但若没有手动实现该方法,则PureComponent采用默认的shallowEqual比较对象是否相等性能更佳。由此可能引发的页面不刷新现象可以采用别的办法解决,如重新生成新的对象、采用immutable.js对象等

3、TextInput首次focus时键盘出现缓慢,卡顿
追查:原因就是键盘是懒加载模式,初次出现时需要先初始化键盘视图耗费时间,要想缩减首次耗时间隔,可以事先就让键盘初始化完毕。js端没想到如何做,但是原生端可以在didFinishLaunchingWithOptions方法中写:

  UITextField *textField = [[UITextField alloc] init];
  [self.window addSubview:textField];
  [textField becomeFirstResponder];
  [textField resignFirstResponder];
  [textField removeFromSuperview];

TextInput聚焦时弹出了键盘,点击非TextInput空白处键盘是不会消失的,若想实现该功能只需要让TextInput嵌入在ScrollView中即可。
那么问题又来了,这样做之后,除了TextInput外屏幕上任意地方点击键盘都会先消失,导致例如页面上有个按钮A,点击A时会先退下键盘,再次点击才能触发A的事件,很扯淡。解决方法大体如下:

   _addEvent = (event) => {
        this.events.push(event.nativeEvent.target)
    };

    _onStartShouldSetResponder(event) {
        let target = event.nativeEvent.target;
        if (!this.events.includes(target)) {
            Keyboard.dismiss()
        }
        return false;
    }

      render() {
        return (
            <ScrollView  keyboardShouldPersistTaps='always' >
                <View
                    style={{alignItems:'center',flex:1,height:SCREEN_HEIGHT}}
                    onStartShouldSetResponder={(event)=>this._onStartShouldSetResponder(event)} >
                    <Button
                        text='登陆'
                        onLayout={event=>this._addEvent(event)}
                    />
                </View>
            </ScrollView>
        )
    }

ScrollView的keyboardShouldPersistTaps属性设置为always,则键盘不再拦截点击事件,点击空白处键盘不会自动消失。onStartShouldSetResponderCapture是点击事件发生时调用,询问该视图是否要拦截事件,自定义处理,当点击屏幕除了指定位置外都退下键盘。指定位置A(比如登录按钮)点击时,键盘不退下。A的onLayout在视图布局完成回调,event.nativeEvent.target能唯一的标识该组件。

4、redux中的reducer为何要返回Object.assign
redux-devtools中,我们可以查看到redux下所有通过reducer更新state的记录,每一个记录都对应着内存中某一个具体的state,让用户可以追溯到每一次历史操作产生与执行时,当时的具体状态,这也是使用redux管理状态的重要优势之一.
若不创建副本,redux的所有操作都将指向内存中的同一个state,我们将无从获取每一次操作前后,state的具体状态与改变,若没有副本,redux-devtools列表里所有的state都将被最后一次操作的结果所取代.我们将无法追溯state变更的历史记录.
创建副本也是为了保证向下传入的this.props与nextProps能得到正确的值,以便我们能够利用前后props的改变情况以决定如何render组件(摘自http://www.jianshu.com/p/8287a1dd8ae9

5、使用redux时,不能在componentWillReceiveProps方法中使用action。
原因:有时候一个action改变数据后,我们希望拿到改变后的数据做另外一个action,比如初始化action读取硬盘中的数据到内存,然后用该参数进行请求网络数据action。此时我们可以在componentWillReceiveProps方法中拿到参数,若此时发出再发出action,则数据返回后改变reducer会再次进入componentWillReceiveProps方法,又继续发出action,陷入死循环。可以如下解决

  componentWillReceiveProps(nextProp) {
        if(nextProp.app.user && nextProp.app.sessionId && !this.isFirstLoad){
            this.props.action(nextProp.app);   // action操作
            this.isFirstLoad = true;
        }
    }

6、navigation导航栏下方那根黑线是什么?
追查:发现在iphone7plus模拟器中黑线看不到,但是iphone6模拟器能看见。查看源代码,在navigation组件中的Header.js第300行找到了黑线样式定义,

let platformContainerStyles;
if (Platform.OS === 'ios') {
    platformContainerStyles = {
        borderBottomWidth: StyleSheet.hairlineWidth,  // hairlineWidth为当前分辨率下能显示的最小宽度,模拟器下可能看不见
        borderBottomColor: 'rgba(0, 0, 0, .3)',
    };
} else {
    platformContainerStyles = {
        shadowColor: 'black',
        shadowOpacity: 0.1,
        shadowRadius: StyleSheet.hairlineWidth,
        shadowOffset: {
            height: StyleSheet.hairlineWidth,
        },
        elevation: 4,
    };
}

可见在ios中下方黑线使用边框的形式实现,而安卓则是设置图层阴影。若想隐藏该线,ios中设置headerStyle的borderBottomWidth为0,安卓中设置elevation/shadowOpacity为0.
同上,可在TabBarBottom.js中180行找到tabbar上方那跟线的默认设置,更改则可在TabNavigator中的tabBarOptions的style中设置borderTopWidth和borderTopColor

7、为何需要render的组件被保存到数组中需要设置key?
追查:跟虚拟DOM和Diff算法有关。(http://www.jianshu.com/p/616999666920)
一次DOM操作流程包括,拿到页面所有DOM节点,拿到css样式表,生成render树,布局计算节点位置,渲染等操作。 传统应用,一个操作如果需要改变10个DOM节点,则会相应的进行10次DOM操作,很多重复浪费性能。
虚拟DOM就是刚开始就将所有的DOM节点转换成js的相关代码保存到内存中,一个操作改变10次DOM节点全部在内存中完成,再将内存中的js转换为实际的DOM节点渲染,性能高。
虚拟DOM一个操作中10次改变DOM节点,每次只是改变了必要的那一个节点,不需要全部改变,为了减少时间复杂度,引入Diff算法,只比较节点改变了的那一点,进行增删改操作等。比如现在的render树是A、B、C节点,想再A节点后面插入D节点,若没有key,React无法区分各个节点,只能根据渲染树的排列依次卸载B、装载D、卸载C、装载B、装载C,效率低下。如果ABC节点都有key,则React就能根据key找出对应的节点,直接渲染A、D、B、C,效率高。

8、Diffing算法相关
在任何一个单点时刻 render() 函数的作用是创建 React 元素树。在下一个 state 或props 更新时,render() 函数将会返回一个不同的 React 元素树。 React 通过Diffing算法找出两颗元素树的差异,更新必须的部分,其假定规则是:
a、DOM 节点跨层级的移动操作特别少,可以忽略不计。
b、拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
c、对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
具体的比较如下:
a、tree diff,DOM 节点跨层级的移动操作少到可以忽略不计,针对这一现象,React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。若有节点跨层级的移动,性能会受到影响
2、component diff,如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
3、element diff,当节点处于同一层级时,默认情况下,当递归一个 DOM 节点的子节点时,React 只需同时遍历所有的孩子节点并更改不同点,如在列表组件追加几个item时,性能不错。但是当如下

<ul>
  <li>1</li>
  <li>2</li>
</ul>
<ul>
  <li>3</li>
  <li>1</li>
  <li>2</li>
</ul>

React 将会改变每一个子节点而没有意识到需要保留 <li>1</li> 和 <li>2</li> 两个子树。这很低效。为了解决这个问题,React 支持一个 key 属性(attributes)。当子节点有了 key ,React 使用这个 key 去比较原来的树的子节点和之后树的子节点。例如,添加一个 key 到我们上面那个低效的例子中可以使树的转换变高效

<ul>
  <li key="2015">1</li>
  <li key="2016">2</li>
</ul>
<ul>
  <li key="2014">3</li>
  <li key="2015">1</li>
  <li key="2016">2</li>
</ul>

现在 React 知道有'2014' key 的元素是新的, key为'2015' 和'2016'的两个元素仅仅只是被移动而已,效率变高很多。要注意key必须具备唯一性。若将数组中的索引作为 key ,如果存在重新排序时,性能将会很差,应该避免这种情况。(http://www.css88.com/react/docs/reconciliation.htmlhttps://zhuanlan.zhihu.com/p/20346379?refer=purerender)

9、高阶组件(HOC)
高阶组件是重用组件逻辑的一项高级技术。高阶组件并不是React API的一部分。高阶组件源自于React生态。具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件,例如Redux的connect函数。
HOC存在的问题:
1、组件的静态方法不会被传递,需要自行传递处理
2、refs不会被传递,应该避免此,或者用自定义属性传递
(http://www.css88.com/react/docs/higher-order-components.html)

6、react-native-fetch-blob的POST请求不成功。
7、js传到原生端的函数(ios中叫block)只能执行一次,否则崩溃。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容