最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react-native中onlayout导致的bug

    正文概述 掘金(lulu酱)   2021-03-21   1007

    最近在解决一个rn项目中ios标题抖动问题,最后发现原因是onlayout是宏任务导致的。

    一.标题组件

    标题组件是这样的。所有页面的标题都共用一个标题实例a,进入页面的时候给a更新状态,组件内有一个对象containLayout,用来记录标题左中右部件的宽度。左中右部件是3个组件,根据状态渲染内容,各自绑定了一个onLayout事件,它有两个参数,第一个是event,第二个是left/center/right。对应左中右部件。 onLayout的逻辑为,在event中获取当前部件的宽度,并赋值给onLayout的left/center/right。如果containLayout的left、center、right属性都有值,就根据left、right、屏幕宽度确定center宽度,然后this.setState(containLayout)。render重新渲染,左中右部件的宽度为containLayout的left/center/right的值

    二.抖动发生流程

    根据console.log输出,抖动发生的流程如下:

    在a页面,containLayout的left/right/center都有确定了值,然后进入了b页面。b页面中,render触发,由于左边和中间部件的内容都发生了改变,左边从50宽度变成10,中间从70宽度变成了74。所以触发了onLayout(event, 'left')和onLayout(event, 'center')。

    ps:ios中onLayout的触发顺序是不确定的。这个结论是测试得出的,找不到官方文档,但是一下两篇文章均有提及:

    • React Native 总结
    • Why does react-native's onLayout occur in a random order in ios?

    1.onLayout(event, 'left')先触发,就会发生抖动。流程如下:

    1. onLayout(event, 'left')触发,从event获取left部件的宽度,为10,containLayout.left更新,containLayout此时如下:
    {
    	// 从50更新为10
    	left: 10, 
    	// 上个页面计算的
    	center: 70,
    	// 上个页面计算的
    	right:10 
    }
    

    因为left、right、center都有值。进入center宽度计算逻辑,根据left、right、屏幕宽度计算得出,center宽度不需要裁剪。还是70,最后执行setState({ containLayout })

    1. setState后直接触发了render函数,onLayout(event, 'center')并没有马上触发

    2. render之后center 从74变成了70, 又触发了一个onLayout(event, 'center')。方便区分,称之为onLayout2(event, 'center')

    3. onLayout(event, 'center') 触发,event.nativeEvent.layout.width 取值为74!因为left、right、center都有值。进入center宽度计算逻辑,根据left、right、屏幕宽度计算得出,center宽度不需要裁剪。还是74,最后执行setState({ containLayout })

    4. 第四步的setState又触发了render函数,因为第3步center宽度已经变成了70了,现在又变成74,又触发了一个onLayout3(event, 'center')

    5. 执行onLayout2(event, 'center'),因为onLayout2是第3步触发的,event.nativeEvent.layout.width取值为70!然后还是不用裁剪,

      执行setState({ containLayout }),又执行了render,把center宽度从74变为70!

    6. 然后onLayout3执行,又把宽度变成74,执行render之后同时又产生一个onLayout4。。。。。

    标题的center部分宽度就一直在70和74之间重复徘徊。由于<Text>组件宽度不够的时候会显示...省略号,所以看起来闪烁的特别明显

    2.onLayout(event, 'center')先触发,就不会发生抖动。流程如下:

    1. onLayout(event, 'center')触发,从event获取center部件的宽度,为74,containLayout.center更新,containLayout此时如下:

      {
         // 上个页面计算的
         left: 50, 
         // 从70更新为74
         center: 74,
         // 上个页面计算的
         right:10 
       }
      

      因为left、right、center都有值。进入center宽度计算逻辑,根据left、right、屏幕宽度计算得出,center宽度不需要裁剪。还是74,最后执行setState({ containLayout })

    2. setState后直接触发了render函数,onLayout(event, 'left')并没有马上触发

    3. render之后 center 从 74 变成了 74, 没触发onLayout。

    4. onLayout(event, 'left') 触发,event.nativeEvent.layout.width 取值为10。left从50变为10。因为left、right、center都有值。进入center宽度计算逻辑,根据left、right、屏幕宽度计算得出,center宽度不需要裁剪。还是74,执行setState({ containLayout })。

    5. render 后发现left 和 center 的宽度没有任何改变,没触发onLayout。

    3.解决

    从上面两点看出,抖动是因为onLayout(event, 'left')的时候不能第一时间获取当前页面的center部件的宽度,而是使用了上个页面的center宽度,才导致的循环更新宽度。因为center部分的宽度是由title决定的,这样的话只需要在更新title的时候判断一下title是否和旧值一样,不一样就把containLayout.center就清空即可。

    三.顺序问题

    从上面发生抖动的流程中可以推测出,onLayout函数是一个宏任务,state状态变更是一个微任务。所以每次出发setState之后,都直接运行了render,然后才运行上一轮render触发的onLayout。下面写点demo证明一下

    1.setState

    虽然官方明确说明state的更新是异步的,但是没有详细说明;也没看过源码,所以不知道是微任务还是宏任务。这里跟vue类比一下,setState其实就是相当于data里面的更改,vue data里面的变更就是使用nextTick,nextTick首选promise.then对收集的update事件进行一次批量运行。所以状态的更改都应该是在微任务里面,这样才能保证界面渲染在状态更新之后。我找了一下react setState方法相关的文章,没找到明确的说明。但是我觉得以下测试能说明state变更是微任务

    import React from 'react';
    
    function App() {
      const [count, setCount] = React.useState(0);
      function add () {
        setTimeout(() => {
          console.log(2)
          //while (true) {
          //  ;
          //}
        })
        Promise.resolve().then(() => {
          console.log(1)
          //while (true) {
          //  ;
          //}
        })
        setCount(count + 1);
      }
     
      return (
        <div className="App" onClick={() => add()}>
          点击{count}
        </div>
      );
    }
    
    export default App;
    
    1. 如上代码,控制台显示12,页面显示点击1。这一步首先确定了宏任务和微任务的运行顺序正常。
    2. 现在我们把then里面的while注释去掉。控制台显示1,页面显示点击0。这一步确认了渲染确实在微任务执行完毕之后才执行。
    3. 最后我们把then里面的注释加上,把setTimeout里面的while注释去掉。如果state的更新是宏任务,他会在setTimeout之后才运行,而setTimeout里面是一个死循环,所以控制台输出应该是12,页面显示点击0。否则,控制台显示12,页面显示点击1
    4. 第三步的运行结果是控制台输出12,页面显示点击1。所以state的变更是微任务。

    2.onlayout

    接下来证明一下onlayout是一个宏任务。首先在rn文档onlayout说明中,表示onlayout会在mount和布局更改的时候触发。以下demo中,在componentDidMount的时候设定了一个宏任务和一个微任务。观察其输出

    import React, { Component } from "react";
    import {
      Text
    } from "react-native";
    
    class TestPage extends Component {
    
      constructor(props) {
        super(props);
        this.state = {
          name: 'lujiajian'
        }
        this.onLayoutHandel.bind(this);
      }
    
      componentDidMount () {
        console.warn('componentDidMount')
        setTimeout(() => {
          console.warn('setTimeout')
        }, 0)
        Promise.resolve().then(res => {
          console.warn('first then')
        })
      }
    
      onLayoutHandel (event) {
        console.warn('onLayoutHandel')
        event.nativeEvent.layout
      }
    
      render() {
        console.warn('render')
        const { name } = this.state;
        return (
          <Text onLayout={this.onLayoutHandel}>{name}</Text>
        );
      }
    }
    
    export default TestPage
    

    输出结果为:render、componentDidMount、first then、setTimeout、onLayoutHandel。onLayoutHandel输出在setTimeout之后,说明不可能是微任务。除非他的触发时机在componentDidMount之后,但这个时机我暂时没想到办法确认。


    起源地下载网 » react-native中onlayout导致的bug

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元