【问题标题】:React component render twice using useStateReact 组件使用 useState 渲染两次
【发布时间】:2020-09-27 05:29:13
【问题描述】:

当没有任何东西用于触发重新渲染组件时,我很难弄清楚发生了什么。

Events.js 当我从 Event.js 中删除 useState() 时,组件会呈现两次,但我需要保留它。当我在 Event 组件中使用 useEffect() 时,第四次渲染。

我只是保留了虚拟数据给你填补空白,并试图删除React.memo,没有任何反应。我相信问题出在Event.js 组件上。我也在使用 Context API,但是第四次渲染太多了。

App.js 中的 useEffect 正在从 localStorage 中获取一些值,我无法直接访问该值,因为默认情况下该值未定义

这里的沙盒代码:https://codesandbox.io/s/event-manager-reactjs-nbz8z?file=/src/Pages/Events/Events.js Events.js 文件位于 /Pages/Events/Events.js

示例代码如下

Event.js(子组件)

function Events() {

    // Sate Managing
    const [allEvents, setAllEvents]   = React.useState(null);
    console.log('Rendering EventsJs', allEvents);

    React.useEffect(() => {
         setAllEvents(['apple', 'banana']);
    }, []);



    return (
        <div className="events">

                { console.log('Event Rendered.js =>') }

        </div>
    )
}


export default React.memo(Events, (prevProps, nextProps) => {
        return true;
} );

App.js(父组件)

import { BrowserRouter, Route, Redirect } from 'react-router-dom';

function App() {

  const [userId, setUserId] = React.useState(null);

  React.useEffect(() => {
    setUserId(1);
  }, []);


  // Login

  return (
    <BrowserRouter>

      <Navigation />

      <Route  path='/events' component={Events} />

      {console.log('App Rendered')}

    </BrowserRouter>
  );
}

export default App;

错误:

【问题讨论】:

  • 当您删除 which useState 时,您为什么要以这种方式使用 useEffect ?您可以通过 useState(1) 直接将 userId 设置为 1
  • 您是否启用了StrictMode
  • setAllEvents 在效果中是没有意义的,你可以做React.useState(['apple', 'banana']); 并防止挂载后重新渲染。很多人已经告诉过你,你使用 React.memo 是错误的(我建议省略第二个参数)。除此之外,没有显示会导致 6 次重新渲染的代码,因此如果没有 minimal, reproducible example(不是带有调试请求的代码转储),任何人都无能为力。
  • @AjeetShah 我将代码部署在沙箱codesandbox.io/s/nbz8z?file=/src/App.js
  • index 中移除strictmode,从Event 中移除strictmode 并移除对mount 的影响,Events 只会渲染一次。

标签: javascript reactjs react-router-dom


【解决方案1】:

其实这是因为你使用了React.memo,它的第二个参数叫做areEqual,你传入了() =&gt; false,所以你基本上是在告诉React props 总是在变化的。因此,每当 App 重新渲染时,Events 也会重新渲染。

【讨论】:

  • 我已经尝试了所有可能的方法,但没有帮助我,我更新了问题!
【解决方案2】:

您应该让React.memo 检查道具更改。通过传递 () =&gt; false,您实际上是在告诉它的 props 总是在变化(它们从不相等)。

export default React.memo(Events);

这是一个有效的example

【讨论】:

  • 谢谢,非常感谢您的辛勤工作,但这并没有解决我的问题!
  • @BloodyLogic,请您通过codepen 或其他环境复制您的代码。
  • @MenaiAlaEddine Event.js 文件位于 codesandbox.io/s/nbz8z?file=/src/App.js 上的 Pages/Events/Events.js 中
【解决方案3】:

您的应用运行良好。它正在渲染。众所周知:

每当它的 propsstate 改变时,React 组件都会重新渲染。

react component lifecycle order 是:

  1. 初始道具/状态 --> 渲染 --> DOM 更新 --> 挂载
  2. props/state 改变 --> 渲染 --> DOM 更新 --> 更新...等等

在下面的示例中,它正在渲染 2 次​​strong>,这是正确的:

  • 第一个(第一个 console.log)是由于初始渲染,state[]
  • 第二个(第二个 console.log)是由于 state 更改(由 useEffect 引起)到 ['apple', 'banana']
function Events() {
  const [allEvents, setAllEvents] = React.useState([]);
  console.log('Event Rendered', allEvents);

  useEffect(() => {
    setAllEvents(['apple', 'banana']);
  }, []);

  return <>Events</>;
}

关于使用 React.memo:

React.memo 仅检查 props 更改。如果你包裹在 React.memo 中的函数组件在其实现中有一个 useStateuseContext Hook,它仍然会在状态或上下文改变时重新渲染

由于状态的变化,您不能使用React.memo 跳过重新渲染。您只能优化以跳过由 props 更改引起的重新渲染。

但是在上面的示例中,您没有从父组件传递的 props,传递给 Events 的唯一 props 是由 react-router 传递的那些,即路由 props。所以,没有必要使用 React.memo。

这里是sandbox,查看console.logs。您只会看到 3 条日志:“App render”、“Event render with initial state”、“Event render with new state”。


编辑:

如果我们从 index.html 中删除 StrictMode,并在组件中添加以下 console.logs:

App.js --> console.log('App rendered')
Evenets.js --> console.log('Event rendered', allEvents, isLoading) // (allEvents and isLoading are state variables here)

然后转到 http://localhost:3000,我们看到 1 个日志:

App Rendered

现在点击“事件”,我们看到 3 个日志:

1: Event Rendered, [], true
2: Event Rendered, [{}, ... 54 items], true
3: Event Rendered, [{}, ... 54 items], false

这是正确的行为(参考上面写的生命周期顺序):

  • 第一个日志:以初始状态渲染([]true
  • 第二条日志:使用新的 allEvents (54 items) 和旧的 isLoading (true) 进行渲染
  • 第三条日志:使用旧的 allEvents (54 items) 和新的 isLoading (false) 进行渲染

以下是现在要问的正确问题:

问题1:

为什么第 2 次和第 3 次渲染(日志)是分开的,不应该将它们批处理(合并)并一起应用,因为它们是写在同一个函数中的吗?

fetch('url').then(() => {
  // ... code here
  setAllEvents([...events])
  setLoading(false)
})

答案:

不,它们不会在上面的代码中批处理。正如Dan Abramov所解释的那样:

这是实现细节,可能会在未来的版本中发生变化。

在当前版本中,如果您在 React 事件处理程序中,它们将被一起批处理。 React 批处理在 React 事件处理程序期间完成的所有 setState,并在退出其自己的浏览器事件处理程序之前应用它们。

在当前版本中,事件处理程序之外的几个 setState(例如在网络响应中)将不会被批处理。所以在这种情况下你会得到两次重新渲染。

存在一个强制批处理的临时 API。如果你写ReactDOM.unstable_batchedUpdates(() =&gt; { this.fn1(); });,那么这两个调用都会被批处理。但我们希望将来删除此 API,而是默认批处理所有内容。

所以,你可以写(然后在 fetch 中),如果你愿意,它将节省 1 个渲染

ReactDOM.unstable_batchedUpdates(() => {
  setAllEvents([...events])
  setLoading(false)
})

问题2:

上面引用的 React 事件处理程序是什么?

答案: foo 在下面的示例中。这 2 个集合状态将被批处理。

const foo = () => {
  setAllEvents([
    { _id: '5ede5af03915bc469a9d598e', title: 'jfklsd', },
  ])
  setLoading(false) 
}

<button onClick={foo}>CLICK</button>

问题3:

它更新 HTML DOM 的次数是否与它呈现的一样多(打印 console.log)?

答案: 。 React 在更新真实 DOM 之前会比较计算的虚拟 DOM,因此只有那些更改会应用于更新 UI 所需的真实 DOM。

问题4:

为什么当我们使用StrictMode 时渲染会加倍?

答案: 是的,StrictMode 会故意将“render”和其他一些生命周期方法双重调用到detect side-effects。严格模式检查仅在开发模式下运行;它们不会影响生产构建。

【讨论】:

  • 我认为您更接近答案,您现在可以查看我的代码。感谢您的回答,谢谢! codesandbox.io/s/event-manager-reactjs-nbz8z?file=/src/Pages/…
  • 谢谢,真的很感谢你,你才是真正的解决者!
  • 希望在未来的 react 版本中,我们甚至不需要使用 ReactDOM.unstable_batchedUpdates,因为在 react 事件处理程序之外的 setStates 也会被批处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 2020-07-11
  • 2020-08-03
  • 2017-11-05
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多