推送新 URL 时多次调用 React 组件渲染
Posted
技术标签:
【中文标题】推送新 URL 时多次调用 React 组件渲染【英文标题】:React component render is called multiple times when pushing new URL 【发布时间】:2016-05-10 06:37:51 【问题描述】:我正在构建一个 PhotoViewer,它可以在用户按左或右时更改照片。我正在使用 React、Redux、react-router 和 react-router-redux。当用户按下左键或右键时,我会做两件事,使用this.context.replace()
更新url,并发送一个动作来更新当前查看的照片this.props.dispatch(setPhoto(photoId))
。我正在订阅状态更改以进行调试。
上面的每一行都会触发一个新的状态变化。由于我更新了currentlyViewedPhoto
,因此调度操作会更新商店,并且更新 url 会更新商店,因为 react-router-redux 会更新商店中的 url。当我调度操作时,在第一个重新渲染周期中,组件的 render
函数被调用两次。在第二个重新渲染周期中,组件的 render
函数被调用一次。这是正常的吗?以下是相关代码:
class PhotoViewer extends Component
pressLeftOrRightKey(e)
... code to detect that left or right arrow was pressed ...
// Dispatching the action triggers a state update
// render is called once after the following line
this.props.dispatch(setPhoto(photoId)) // assume photoId is correct
// Changing the url triggers a state update
// render is called twice
this.context.router.replace(url) // assume url is correct
return
render()
return (
<div>Test</div>
)
function select(state)
return state
export default connect(select)(PhotoViewer)
render 被调用 3 次是正常的吗?这似乎有点矫枉过正,因为 React 必须对 DOM 进行 3 次 diff。我想这并不重要,因为什么都没有改变。我是这个工具集的新手,所以请随时询问有关此问题的更多问题。
【问题讨论】:
我遇到了链接中没有斜杠的问题。如果我有<Link to="foo">
,React 路由器(不使用 redux)会从 #foo
“重定向”到 #/foo
并导致额外的渲染。将斜线添加到链接修复它。这是使用 hashHistory。
【参考方案1】:
删除
【讨论】:
重复回答【参考方案2】:如果使用 >v16.3,然后尝试删除
和它 应该可以正常工作。
为什么?自 v16.3 用于类组件和 v16.8 用于钩子以来,React 引入了
以下文章值得一读:
React Components rendered Twice
React Components render twice and drive me crazy
Wait, you're not using <React.StrictMode>?!
注意:这仅适用于开发环境。
【讨论】:
【参考方案3】:这是一个老问题,我明白了,但我也注意到了这个问题。就像唐纳德的回答中指出的那样,这似乎很正常。 我尝试了三件事来解决我的具体案例:
我关闭了 React DevTools 插件以查看是否是重复消息的原因。 我检查了其他浏览器,看看是否是问题所在。 我创建了一个简短的测试来检查它是否发生在简单的组件上。前两种方法
禁用 React DevTools 插件不会改变登录到控制台的消息数量。
更换浏览器也没有效果。我试过 Chromium 和 Firefox。
第三种方法
即使是最简单的组件似乎也会多次调用生命周期方法。
给定两个文件,App.js
和 components/TestComponent.js
,如下:
// App.js
import React from 'react';
import './App.css';
import Test from './components/TestComponent';
function App()
console.log('App render');
return (
<div>
<Test />
</div>
);
export default App;
// ./components/TestComponent.js
import React, Component from 'react';
class Test extends Component
constructor(props)
console.log('Test constructed');
super(props);
componentDidMount()
console.log('Test mounted');
componentDidUpdate()
console.log('Test updated');
render()
console.log('Test rendered');
return (
<div>
<h1>Hello, world!</h1>
</div>
);
export default Test;
Test的生命周期方法被多次调用。
注意事项
我使用 create-react-app 来设置应用程序。 react 版本是 16.13.1。结果与讨论
使用yarn start
,我在控制台中得到以下信息:
App render
TestComponent.js:7 Test constructed
TestComponent.js:7 Test constructed
TestComponent.js:20 Test rendered
TestComponent.js:20 Test rendered
TestComponent.js:12 Test mounted
至少对我来说,yarn
指出开发构建没有优化,运行yarn build
然后使用python -m http.server --directory build
提供它会得到一个没有重复消息的页面。
至于为什么你得到三个渲染而不是两个,我不能说。我的建议是尝试构建应用程序并查看问题在生产构建中是否仍然存在。
如果你仍然遇到问题,你也可以按照 Donald 的建议在 shouldComponentUpdate
中实现逻辑。
【讨论】:
【参考方案4】:我认为这是正常的。如果您没有明显的性能问题,我不会出汗。如果性能开始出现问题,如果您确定某些状态更改不会更改渲染的组件,则可以考虑覆盖 shouldComponentUpdate
生命周期方法。
编辑:如果您只需要在 shouldComponentUpdate
生命周期方法中进行浅层比较,您也可以考虑扩展 React.PureComponent
而不是 React.Component
。更多信息here。
【讨论】:
很高兴知道这是正常的。这是我第一次使用这种架构,我真的很担心渲染会被多次触发。 完全可以理解 - 对于这样的情况,在您拥有非常复杂的组件之前,您真的不会开始看到性能问题。 我也是 React 的新手,很惊讶render()
被调用了两次,哦顺便说一句,切换到 PureComponent
没有任何区别 :(【参考方案5】:
如果您在三个不同的阶段设置状态,那么组件也将重新渲染三次。
setState() 将始终触发重新渲染,除非有条件 渲染逻辑在 shouldComponentUpdate() 中实现。
(source)
如果您遇到性能问题,您可以在 shouldComponentUpdate() 中实现逻辑以防止不必要的重新渲染。
【讨论】:
我可以理解是否有两个重新渲染(来自调度操作和 url 替换),但三个对我来说有点担心。 url 替换会导致两次更新,而不是一次。 react-router-redux 是否对商店进行了两次更新? 我个人没有使用过 react-router-redux,所以很遗憾无法回答这个问题。您可以尝试在您的商店或shouldComponentUpdate
中记录状态变化,看看发生了什么变化。
我认为这是站不住脚的。满足于“只要运行相同的参数和结果多次,除非你遇到问题”。以上是关于推送新 URL 时多次调用 React 组件渲染的主要内容,如果未能解决你的问题,请参考以下文章
如何在 React Native 的初始渲染中多次调用组件的 prop 方法?
从组件中的 useState 多次调用 state updater 会导致多次重新渲染
React useEffect 调用 API 的时间太多,当我的组件渲染时,如何将 API 调用限制为一次?