React 组件使用 useState 渲染两次

IT技术 javascript reactjs react-router-dom
2021-05-14 05:00:33

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

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

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

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

沙盒代码在这里:https : //codesandbox.io/s/event-manager-reactjs-nbz8z?Events.js file =/ src/Pages/Events/ 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;

错误:

在此处输入图片说明

3个回答

您的应用运行良好。它正在渲染它应该的样子。据我们所知:

React 组件在其propsstate发生变化时重新渲染

react组件生命周期顺序是:

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

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

  • 第一个(第一个 console.log)是由于初始渲染状态[]
  • 第二个(第二个 console.log)是由于状态改变(由 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,则它仍会在 state 或 context change 时重新渲染

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

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

这是沙箱,检查console.logs。您将只看到 3 个日志:“应用程序渲染”、“具有初始状态的事件渲染”、“具有新状态的事件渲染”。


编辑:

如果我们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:

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

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

回答:

不,它们不会在上面的代码中批处理正如丹·阿布拉莫夫所解释的那样

这是实现细节,可能会在未来版本中更改。

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

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

存在一个临时 API 来强制批处理如果你写,ReactDOM.unstable_batchedUpdates(() => { 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”和其他一些生命周期方法来检测副作用严格模式检查仅在开发模式下运行;它们不会影响生产构建。

好吧,实际上这是由您使用 引起的React.memo,它的第二个参数被称为areEqual,并且您传入了() => false,所以您基本上是在告诉 React,props总是在变化。因此,每当 App 重新渲染时,事件也会重新渲染。

您应该让React.memo检查props更改。通过传递,() => false您实际上是在告诉它的props总是在变化(它们永远不会相等)。

export default React.memo(Events);

这是一个工作示例