壹 ❀ 引

下午前端大佬突然私聊我,说发现了一个很有趣的bug,问我有没有兴趣,因为我平时会记录一些自认为有意思的问题,所以毫不犹豫就答应了,问题表现如下,当我们系统进入到某个页面下时,接口居然无止境的不断请求,跟陷入了死循环一样。

问题简单排查下来其实也不算复杂,算是react router理解不够深刻使用不当造成的问题,处于好奇在项目里搜了下这种不当写法,统计来看应该有不少同学对于这块也不太熟悉,所以这里就做个简单记录。

贰 ❀ 排查思路

因为接口在不断请求,我们自然要排查这个接口是谁发起的,从而定位出发请求的问题组件。点击上图中的data接口,选择Initiator,在这里我们就能看到这个接口从发起到结束整个完整的调用栈,因为我是点击这个页面就出现这个问题,说明这个数据极大可能是在页面初始化的请求,初始化请求一般放在哪?当然是componentDidMout,于是我们查找调用找中的componentDidMout,于是成功定位到了如下文件:

点击文件,可以看到具体的代码确实是在初始化拿数据:

那么问题来了,组件渲染理论上只会执行一次componentDidMount,如果它一直在卸载挂载,那说明出问题的不是组件自身,而是使用了此组件的上游组件,于是我拿这个组件名在项目里搜索了一番,运气还算好,只有一个路由页用到,大致代码如下:

<Route component={() => <A {...this.props} />} />

而这种写法,其实就引发了一个很尴尬的问题,打开react router官方,有如下这段描述:

When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).

总结来说,如果我们使用了component,路由会使用React.createElement帮你创建一个新的react组件,而且是卸载现有组件以及挂载你设置的新组件,但是上述写法使用了箭头函数,导致只要路由这段代码render执行一次,即便路由地址没发生变化,component都会认定这是一个新组件,从而每次都完整执行生命周期钩子,那写在didMount中的请求自然每次都会请求。

那为啥包含路由相关代码逻辑的父组件一直在render呢?这里需要提一提我们项目中所使用的stamp接口机制,前端每次拿数据,除了告诉后端要拿什么数据之外,都会附带一个时间戳。

比如第一次请求前端时间戳带过去的肯定是0,后端返回了数据以及一个此数据对应的时间戳;

第二次再请求时,前端会带上上次后端给的时间戳与后端做对比,假设数据没变化,后端对于数据层就会返回null以及还是相同的时间戳,前端接到null自然知道数据没变化了,还是走缓存,甚至组件都不会有更新的必要。

但这个问题巧就巧在后端在数据层返回出了问题,带了数据但是没给时间戳,导致前端每次请求的时间戳都是默认的0,从而后端每次都返回新数据,新数据被存入store,数据引用发生变化导致路由所在组件渲染,路由渲染又引发下层组件渲染以及didMout执行,于是请求死循环就诞生了。

虽然后端数据返回有问题是前提,但是前端也不应该发起无意义的请求,说到底就是不应该重复的didMout,怎么修改呢?将component改为render即可:

<Route render={() => <A {...this.props} />} />

叁 ❀ 一个例子加深印象

为了更好的理解上述问题,以及componentrender的使用对比,这里我准备了一个例子,先看component:

class B extends React.Component {

    componentDidMount() {
// 用于判断子组件didMout是否重复执行
console.log("componentDidMount")
} render() {
return (
<div>B组件,此时num是{this.props.num}</div>
)
}
} class Echo extends React.Component { state = { num: 1 } componentDidMount() {
// 定时器,模拟后端不断返回新数据,引发render变化
setInterval(() => {
this.setState({ num: this.state.num + 1 });
}, 1000)
} render() {
return (
<div>
<BrowserRouter>
<Route component={() => (<B num={this.state.num} />)} />
</BrowserRouter>
</div>
);
}
}

点击在线体验这个例子

可以看到组件B不仅render在不断执行,连componentDidMout也在不断执行,若大家有兴趣,可以再给B组件嵌套一个C组件,同时也监听componentDidMout,你会发现component所接收组件下的整个组件树,都在完整的被重新卸载挂载,抛开本文提到的请求死循环,单站在react角度性能也存在一定问题。

现在我们将上述代码中的component改成render,效果如下,可以看到子组件正常渲染,且componentDidMout只会初始化一次,后续不会重复执行。

肆 ❀ 总结

我们在路由写法上,常见写法如下:

<Route component={B} />

但是上述写法并没办法传递props以及其它属性,所以有同学可能就习惯使用箭头函数的做法,如下:

<Route component={() => <B {...this.props} />} />

但是我们通过一个bug分析,以及例子演示得知,假设当前路由未变化但是触发了render,这些用法会导致路由下子组件完整的重复挂载卸载,非常影响性能,解决办法也很简单,改用render即可。

那么componentrender又有什么区别呢?这里我去简单看了下路由源码:

// react-router源码
if (component)
return match ? React.createElement(component, props) : null if (render)
return match ? render(props) : null

源码层面,创建组件的方式不同,component 使用的是 React.createElement,箭头函数情况下由于每次返回的都是一个新组件,所以每次都会触发完整的生命周期;而 render 可以理解执行了一个匿名函数,得到了一个组件,自始至终都是这一个组件,后续更新只是diff比较,就没有额外繁琐的生命周期处理,性能更佳。

对于component,它的调用更像下面代码:

<Route component={B} />
// 你可以理解为
<Route>
<B />
</Route>

而对于使用render的场景,它更像下方这样:

<Route>
{B()}
</Route>

那么到这里,本文结束。

react router component与render有什么区别?提升渲染性能,记一个react router component 误用导致请求死循环的有趣bug的更多相关文章

  1. React爬坑秘籍(一)——提升渲染性能

    React爬坑秘籍(一)--提升渲染性能 ##前言 来到腾讯实习后,有幸八月份开始了腾讯办公助手PC端的开发.因为办公助手主推的是移动端,所以导师也是大胆的让我们实习生来技术选型并开发,他来做code ...

  2. 记一个react拖动排序中的坑:key

    在做一个基于react的应用的时候遇到了对列表拖动排序的需求.当使用sortable对列表添加排序支持后发现一个问题:数据正确排序了,但是dom的顺序却乱了,找了一会儿原因后发现是因为在渲染数据的时候 ...

  3. react+redux渲染性能优化原理

    大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't ha ...

  4. 从性能角度看react组件拆分的重要性

    React是一个UI层面的库,它采用虚拟DOM技术减少Javascript与真正DOM的交互,提升了前端性能:采用单向数据流机制,父组件通过props将数据传递给子组件,这样让数据流向一目了然.一旦组 ...

  5. [React Router v4] Conditionally Render a Route with the Switch Component

    We often want to render a Route conditionally within our application. In React Router v4, the Route ...

  6. React源码 ReactDOM.render

    在 react 当中,主要创建更新的有三种方式 1.ReactDOM.render || hydrate  这两个api都是要把这个应用第一次渲染到我们页面上面,展现出来整个应用的样子的过程,这就是初 ...

  7. [React] Use React.ReactNode for the children prop in React TypeScript components and Render Props

    Because @types/react has to expose all its internal types, there can be a lot of confusion over how ...

  8. nuxt框架Universal和Spa两种render mode的区别

    如下图,官网上对于Universal 和 Spa 两种render mode的区别,并没有加以说明,相信大多数人跟我一样有点懵,不知道选什么好.虽然两个模式创建的项目看不出区别. 先给出推荐选项: U ...

  9. ElementUI(vue UI库)、iView(vue UI库)、ant design(react UI库)中组件的区别

    ElementUI(vue UI库).iView(vue UI库).ant design(react UI库)中组件的区别: 事项 ElementUI iView ant design 全局加载进度条 ...

  10. react route使用HashRouter和BrowserRouter的区别-Content Security Policy img-src 404(Not found)

    踩坑经历 昨天看了篇关于react-route的文章,说BrowserRouter比HashRouter好一些,react也是推荐使用BrowserRouter,毕竟自己在前端方面来说,就是个小白,别 ...

随机推荐

  1. com.alibaba.fastjson.JSONException: create instance error

    很早之前在使用FashJson进行实体类转化的时候,如果json参数是多层都是一层对应一个单独的实体类, 今天在项目中想,使用内部类是不是也可以实现,且使用内部类封装性更好.当将json串使用fast ...

  2. NewStarCTF 2023 公开赛道 WEEK2|CRYPTO全解

    一.滴啤 题目信息 from Crypto.Util.number import * import gmpy2 from flag import flag def gen_prime(number): ...

  3. [转帖]PolarDB和Oceanbase的区别和联系

    PolarDB-X 和 OceanBase 都是阿里云提供的分布式关系型数据库产品,它们都具有高可用.高性能.分布式等特点.但是两者也存在一些差异. 数据库理论基础不同 PolarDB-X 基于传统的 ...

  4. [转帖]Kafka生产者——重要参数配置

    https://www.cnblogs.com/luckyhui28/p/12001798.html 目录 acks max.request.size retries和retry.backoff.ms ...

  5. [转帖]BIS出口管制新规说明会,进一步明确十大问题

    https://zhuanlan.zhihu.com/p/573765504 10月13日晚9点,BIS就出口管制新规举行电话会议简报,出口执法助理副部长Thea Kendler主持会议.小白总结要点 ...

  6. [转帖] jq实现json文本对比

      原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 近期,为了给一个核心系统减负,组内决定将一些调用量大的查询接口迁移到另一个系统,由于接口逻辑比较复杂,为了保 ...

  7. CentOS7 上面升级git 2.24的方法

    本来想使用tar包进行安装 但是发现tar包安装时总是报错如下: [root@centos76 git-2.25.0]# make LINK git-imap-send imap-send.o: In ...

  8. 除了Adobe之外,还有什么方法可以将Excel转为PDF?

    前言 Java是一种广泛使用的编程语言,它在企业级应用开发中发挥着重要作用.而在实际的开发过程中,我们常常需要处理各种数据格式转换的需求.今天小编为大家介绍下如何使用葡萄城公司的的Java API 组 ...

  9. 每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!

    写在开头 请聊一聊Java中方法的重写和重载? 这个问题应该是各大厂面试时问的最多的话题之一了,它们几乎贯穿了我们日常的开发工作,在过往的博客中我们多多少少都提到过重载与重写,而今天我们就一起来详细的 ...

  10. 【JS 逆向百例】层层嵌套!某加速商城 RSA 加密

    声明 本文章中所有内容仅供学习交流,敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 逆向目标 目标:某加速商城登录接口 ...