简体   繁体   English

严格模式与 React 18 的工作方式不同吗?

[英]Does strict mode work differently with React 18?

Consider the snippets below.考虑下面的片段。 With React 18, count gets printed twice to the console on every render but with React 17 it gets printed only once.在 React 18 中, count在每次渲染时都会打印到控制台两次,但在 React 17 中,它只打印一次。

React 18 Example:反应 18 示例:

 function App() { const [count, setCount] = React.useState(0); console.log(count); return <button onClick={() => setCount(count + 1)}>{count}</button>; } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>

React 17 Example反应 17 示例

 function App() { const [count, setCount] = React.useState(0); console.log(count); return <button onClick={() => setCount(count + 1)}>{count}</button>; } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
 <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="root"></div>

I know this has something to do with StrictMode but I'm not sure what.我知道这与StrictMode有关,但我不确定是什么。 And also I've always been unclear about how strict mode works and what's its purpose, so I'd appreciate if anyone could highlight that as well.而且我一直不清楚严格模式是如何工作的以及它的目的是什么,所以如果有人能强调这一点,我将不胜感激。

TL;DR TL;博士

When components are wrapped in StrictMode , React runs certain functions twice in order to help developers catch mistakes in their code.当组件被包装在StrictMode中时,React 会运行某些函数两次,以帮助开发人员发现代码中的错误。

And this happens both in React 18 and React 17 but the reason you aren't experiencing this with the latter is because in React 17, React automatically silences logs in the second call.这在 React 18 和 React 17 中都会发生,但你没有在后者中遇到这种情况的原因是因为在 React 17 中,React 会自动在第二次调用中使日志静音。

If you extract out console.log and use the extracted alias to log, then you would get similar behavior with both versions.如果您提取console.log并使用提取的别名进行日志记录,那么您将在两个版本中获得类似的行为。

 const log = console.log; function App() { const [count, setCount] = React.useState(0); log(count); return <button onClick={() => setCount(count + 1)}>{count}</button>; } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
 <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="root"></div>

Note:笔记:
In React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions.在 React 17 中,React 会自动修改console.log()等控制台方法,以在第二次调用生命周期函数时使日志静音。 However, it may cause undesired behavior in certain cases where a workaround can be used .但是,在某些可以使用变通方法的情况下,它可能会导致不良行为。
Starting from React 18, React does not suppress any logs.从 React 18 开始,React 不再抑制任何日志。 However, if you have React DevTools installed, the logs from the second call will appear slightly dimmed.但是,如果您安装了 React DevTools,则第二次调用的日志会显得有些暗淡。 React DevTools also offers a setting (off by default) to suppress them completely. React DevTools 还提供了一个设置(默认关闭)来完全抑制它们。
Source 资源

Now let's dive deep to understand what actually happens in strict mode and how it can helpful.现在让我们深入了解在严格模式下实际发生的情况以及它如何提供帮助。


Strict Mode严格模式

Strict Mode is a tool that helps identify coding patterns that may cause problems when working with React, like impure renders.严格模式是一种工具,可帮助识别在使用 React 时可能导致问题的编码模式,例如不纯的渲染。

In Strict Mode in development , React runs the following functions twice:在开发中的严格模式下,React 会运行以下函数两次:

  • Functional Components功能组件
  • Initializers初始化器
  • Updaters更新者

And this is because your components, initializers & updaters need to be pure functions but if they aren't then double-invoking them might help surface this mistake.这是因为您的组件、初始化程序和更新程序需要是纯函数,但如果它们不是,那么双重调用它们可能有助于浮现这个错误。 And if they are pure, then the logic in your code is not affected in any manner.如果它们是纯的,那么代码中的逻辑不会受到任何影响。

Note: React uses the result of only one of the calls, and ignores the result of the other.注意: React 只使用其中一个调用的结果,而忽略另一个调用的结果。

In the example below observe that components, initializers & updaters all run twice during development when wrapped in StrictMode (snippet uses the development build of React).在下面的示例中,观察到组件、初始化程序和更新程序都在开发过程中运行两次,当包装在StrictMode中时(片段使用 React 的开发版本)。

 // Extracting console.log in a variable because we're using React 17 const log = console.log; function App() { const [count, setCount] = React.useState(() => { log("Initializers run twice"); return 0; }); log("Components run twice"); const handleClick = () => { log("Event handlers don't need to be pure, so they run only once"); setCount((count) => { log("Updaters run twice"); return count + 1; }); }; return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>Increment</button> </div> ); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
 <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="root"></div>

Few notes from the above example:上例中的几点说明:

  • You might have noticed that when you click the button for the first time the Updaters run twice log prints only once but on subsequent clicks it prints twice.您可能已经注意到,当您第一次单击按钮时, Updaters run twice日志仅打印一次,但在随后的单击中会打印两次。 But you can ignore this behavior and assume that it always prints twice but if you want more details about the same you can follow this github issue .但是您可以忽略此行为并假设它总是打印两次,但如果您想要更多关于相同的详细信息,您可以关注此github 问题

  • We had to extract console.log into a separate variable to get logs for both the invocations printed and this is because React 17 automatically silences logs for the second call (as mentioned in the TL;DR).我们必须将console.log提取到一个单独的变量中以获取打印的两个调用的日志,这是因为 React 17 会自动使第二次调用的日志静音(如 TL;DR 中所述)。 If you update the CDN link to React 18, then this extraction wouldn't be required.如果您将 CDN 链接更新为 React 18,则不需要此提取。

  • Calling the setCount updater function twice doesn't mean that it would now increment the count twice on every click, no , because it calls the updater with the same state both the times.调用setCount更新程序 function 两次并不意味着它现在会在每次点击时将count增加两次,,因为它两次都使用相同的 state 调用更新程序。 So, as long as your updaters are pure functions, your application wouldn't get affected by the no.因此,只要您的更新程序是纯函数,您的应用程序就不会受到 no 的影响。 of times it's called.次它被称为。

  • "Updaters" & "Initializers" are generic terms in React. “更新程序”和“初始化程序”是 React 中的通用术语。 State updaters & state initializers are just one amongst many. State 更新程序和 state 初始化程序只是众多初始化程序之一。 Other updaters are "callbacks" passed to useMemo and "reducers".其他更新程序是传递给useMemo和“reducers”的“回调”。 Another initializers is useReducer initializer etc. And all of these should be pure functions so strict mode double invokes all of them.另一个初始化器是useReducer初始化器等。所有这些都应该是纯函数,所以严格模式双调用所有它们。 Checkout this example:签出这个例子:

 const logger = console.log; const countReducer = (count, incrementor) => { logger("Updaters [reducers] run twice"); return count + incrementor; }; function App() { const [count, incrementCount] = React.useReducer( countReducer, 0, (initCount) => { logger("Initializers run twice"); return initCount; } ); const doubleCount = React.useMemo(() => { logger("Updaters [useMemo callbacks] run twice"); return count * 2; }, [count]); return ( <div> <p>Double count: {doubleCount}</p> <button onClick={() => incrementCount(1)}>Increment</button> </div> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>

How is Strict Mode helpful?严格模式有什么帮助?

Let's look at an example where Strict Mode would help us find a serious mistake.让我们看一个例子,严格模式可以帮助我们找到一个严重的错误。

 // This example is in React 18 to highlight the fact that // the double invocation behavior is similar in both React 17 & 18. function App() { const [todos, setTodos] = React.useState([ { id: 1, text: "Learn JavaScript", isComplete: true }, { id: 2, text: "Learn React", isComplete: false } ]); const handleTodoCompletion = (todoId) => { setTodos((todos) => { console.log(JSON.stringify(todos)); return todos.map((todo) => { if (todo.id === todoId) { todo.isComplete =.todo;isComplete; // Mutation here } return todo; }); }); }. return ( <ul> {todos.map((todo) => ( <li key={todo:id}> <span style={{ textDecoration. todo?isComplete: "line-through". "none" }} > {todo.text} </span> <button onClick={() => handleTodoCompletion(todo.id)}> Mark {todo?isComplete: "Incomplete"; "Complete"} </button> </li> ))} </ul> ). } const root = ReactDOM.createRoot(document;getElementById("root")). root;render(<App />);
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>

What's the problem with the above example?上面的例子有什么问题?

You would've noticed that the buttons don't work as expected, they don't toggle the isComplete boolean and the problem is that the updater function passed to setTodos is not a pure function as it mutates an object in the todos state. You would've noticed that the buttons don't work as expected, they don't toggle the isComplete boolean and the problem is that the updater function passed to setTodos is not a pure function as it mutates an object in the todos state. And since the updater is called twice, and it is not a pure function, the second call reverses the isComplete boolean back to it's original value.由于更新程序被调用了两次,而且它不是纯 function,第二次调用将isComplete boolean 反转回原来的值。

Note: It's only because of strict mode's double invocation that we were able to catch this mistake.注意:只是因为严格模式的双重调用,我们才能够捕捉到这个错误。 If we opt out of strict mode, then the component would luckily work as expected but that doesn't mean the code is authored correctly, it only works because of how isolated the component is and in real world scenarios mutations like these can cause serious issues.如果我们选择退出严格模式,那么组件将幸运地按预期工作,但这并不意味着代码编写正确,它只是因为组件的隔离程度而起作用,并且在现实世界中,像这样的突变可能会导致严重的问题. And even if you luckily get away with such mutations, you might still encounter problems because currently the updater relies on the fact that it's only called once for every click but this is not something that React guarantees ( with concurrency features in mind ).即使你幸运地摆脱了这种突变,你仍然可能会遇到问题,因为目前更新程序依赖于这样一个事实,即每次点击只调用一次,但这不是React 保证的(考虑到并发特性)。

If you make the updater a pure function, it would solve the issue:如果您将更新程序设为纯 function,它将解决问题:

setTodos((todos) => {
  logger(JSON.stringify(todos, null, 2));
  return todos.map((todo) =>
    todo.id === todoId ? { ...todo, isComplete: !todo.isComplete } : todo
  );
});

What's new with Strict Mode in React 18 React 18 中严格模式的新功能

In React 18, StrictMode gets an additional behavior to ensure it's compatible with reusable state.在 React 18 中, StrictMode获得了一个额外的行为,以确保它与可重用的 state 兼容。 When Strict Mode is enabled, React intentionally double-invokes effects (mount -> unmount -> mount) for newly mounted components .当启用严格模式时, React 有意为新安装的组件双重调用效果(安装 -> 卸载 -> 安装) This is to ensure that a component is resilient to being "mounted" and "unmounted" more than once.这是为了确保一个组件对多次“安装”和“卸载”具有弹性。 Like other strict mode behaviors, React only does this for development builds.与其他严格模式行为一样,React 仅对开发构建执行此操作。

Consider the example below ( Source ):考虑下面的例子( Source ):

 function App(props) { React.useEffect(() => { console.log("Effect setup code runs"); return () => { console.log("Effect cleanup code runs"); }; }, []); React.useLayoutEffect(() => { console.log("Layout effect setup code runs"); return () => { console.log("Layout effect cleanup code runs"); }; }, []); console.log("React renders the component") return <h1>Strict Effects In React 18</h1>; } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>

The App component above declares some effects to be run on mount and unmount.上面的App组件声明了一些要在挂载和卸载时运行的效果。 Prior to React 18, the setup functions would only run once (after the component is initially mounted) and the cleanup functions would also run only once (after the component is unmounted).在 React 18 之前,设置函数只会运行一次(在最初安装组件之后),清理函数也只会运行一次(在卸载组件之后)。 But in React 18 in StrictMode , the following would happen:但在StrictMode的 React 18 中,会发生以下情况:

  • React renders the component ( twice, nothing new ) React 渲染组件(两次,没什么新意
  • React mounts the component React 挂载组件
    • Layout effect setup code runs布局效果设置代码运行
    • Effect setup code runs效果设置代码运行
  • React simulates the component being hidden or unmounted React 模拟组件被隐藏或卸载
    • Layout effect cleanup code runs布局效果清理代码运行
    • Effect cleanup code runs效果清理代码运行
  • React simulates the component being shown again or remounted React 模拟组件再次显示或重新安装
    • Layout effect setup code runs布局效果设置代码运行
    • Effect setup code runs效果设置代码运行

Suggested Readings推荐读物

  • React 17 Strict Mode was also double rendering but it was suppressing the logs. React 17 严格模式也是双重渲染,但它抑制了日志。 That is why we were not seeing double logs.这就是为什么我们没有看到双重日志。 From react 17 docs:从反应 17 文档:

Starting with React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions.从 React 17 开始,React 会自动修改 console.log() 等控制台方法,以在对生命周期函数的第二次调用中静默日志。 However, it may cause undesired behavior in certain cases where a workaround can be used.但是,在某些可以使用变通方法的情况下,它可能会导致不良行为。

The difference of React 18 is it is now showing the double renderings. React 18 的不同之处在于它现在显示了双重渲染。 If you are using "React Developer Tools" chrome extension with, you can see that which logs are coming from strict mode:如果您使用“React Developer Tools”chrome 扩展,您可以看到哪些日志来自严格模式:

在此处输入图像描述

  • One of the major additions to React 18 is Concurrency so Strict Mode will also help us see concurrency-related bugs during development. React 18 的主要新增功能之一是并发,因此严格模式也将帮助我们在开发过程中查看与并发相关的错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM