React组件使用useState渲染两次

时间:2020-06-08 09:22:35

标签: javascript reactjs react-router-dom

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

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

我只是保留了虚拟数据以填补空缺,并尝试删除React.memo,什么都没有发生。我认为是Event.js组件的问题。我也在使用Context API,但是第四次渲染时间太多了。

{p {1}}中的

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

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

示例代码如下

Event.js(子组件)

/Pages/Events/Events.js

App.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;
} );

错误:

enter image description here

3 个答案:

答案 0 :(得分:3)

您的应用运行正常。它正在渲染应有的状态。我们知道:

只要它的 props state 发生变化,React组件就会重新渲染。

react component lifecycle order是:

  1. 初始道具/状态->渲染-> DOM更新->安装
  2. 道具/状态已更改->渲染-> DOM更新->已更新...依此类推

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

  • 第一个(第一个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仅检查道具的更改。如果包装在React.memo中的功能组件在其实现中具有 useState useContext 钩子,则当状态或上下文更改时,它仍将重新呈现

由于状态的更改,您不能使用React.memo跳过重新渲染。您只能进行优化,以跳过由于 props 的更改而导致的重新渲染。

但是在上面的示例中,您没有从父级组件传递的道具,传递给Events的道具只有react-router传递的道具,即路线道具。因此,无需使用React.memo。

这里是sandbox,请检查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)渲染
  • 第三个日志:使用旧的allEvent(54 items)和新的isLoading(false)渲染

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

问题1:

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

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

答案:

否,它们不会按上述代码批量处理。如Dan Abramov所述:

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

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

在当前版本中,事件处理程序外部(例如,网络响应中)的几个setState将不会被批处理。因此,在这种情况下,您将获得两次重新展示。

存在一个临时批处理API 。如果您写ReactDOM.unstable_batchedUpdates(() => { this.fn1(); });,则两个呼叫将被分批处理。但是我们希望将来会删除此API,而是默认情况下对所有内容进行批处理。

因此,您可以编写(在fetch的内部),如果需要,它将保存1个渲染

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

问题2:

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

答案: foo在下面的示例中。这两个设置状态将被分批处理。

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

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

问题3:

它会更新HTML DOM渲染次数(打印console.log)吗?

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

问题4:

为什么在使用StrictMode时渲染翻了一番?

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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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

export default React.memo(Events);

这是工作中的example