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

发布于 2025-01-26 13:39:52 字数 1715 浏览 4 评论 0 原文

考虑下面的摘要。使用React 18, Count 在每个渲染上都将两次打印到控制台,但使用React 17仅打印一次。

反应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>

反应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>

我知道这与 strictmode 有关,但我不确定是什么。而且,我一直不清楚严格模式的工作原理以及其目的是什么,因此,如果有人也能强调这一点,我将不胜感激。

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 Example:

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

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. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乖乖哒 2025-02-02 13:39:52

tl; dr

当组件用 strictmode 包裹时,React将两次运行某些功能,以帮助开发人员在其代码中捕获错误。

这既是在React 18和React 17中都发生的,但是您之所以没有使用后者遇到的原因是因为在React 17中,React自动在第二个呼叫中自动沉默日志。

如果您提取 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>

注意:

在React 17中,React会自动修改控制台方法,例如 console.log(),以使第二个调用生命周期功能的日志沉默。但是,在某些情况下,在某些情况下,可能会导致不希望的行为>。

从React 18开始,React不会抑制任何日志。但是,如果已安装了React DevTools,则第二个呼叫的日志将显得微弱。 React DevTools还提供了设置(默认情况下)以完全抑制它们。

现在让我们深入了解严格模式中实际发生的事情及其有用。


严格模式

严格模式是一种工具,可以帮助识别与React一起使用时可能引起问题的编码模式,例如不纯粹的渲染。

在开发中严格模式下,React在两次中运行以下功能:

  • 功能组件
  • 初始化器
  • 更新器

,这是因为您的组件,初始化器&amp;更新器需要为 纯函数 ,但是如果不是,那么< em>双击它们可能有助于遇到这个错误。如果它们是纯净的,那么您的代码中的逻辑不会以任何方式影响。

注意: React仅使用一个呼叫的结果,而忽略了另一个呼叫的结果。

在下面的示例中,观察到组件,初始化器&amp;在开发过程中,更新器在 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>

上面的示例中有几笔注释:

  • 您可能已经注意到,当您第一次单击按钮时,更新程序运行两次 log仅一次打印一次,但随后单击后续单击它打印两次。但是您可以忽略此行为,并假设它总是打印两次,但是如果您想要更多有关相同的详细信息,则可以按照此操作 github问题

  • 我们必须将 console.log 提取到一个单独的变量中,以获取打印的调用的日志,这是因为react 17自动沉默了第二个呼叫的日志(如TL中提到; )。如果将CDN链接更新为React 18,则不需要此提取。

  • 调用 setCount updater函数两次并不意味着它现在每次点击两次都会增加 count no ,因为它在两次时都将更新程序调用相同状态。因此,只要您的更新器是纯函数,您的应用程序就不会受到NO的影响。被称为。

  • “ Updaters”&amp; “初始化器”是React中的通用术语。状态更新者&amp;状态初始化只是众多。其他更新程序是传递给 usememo 和“ reducers”的“回调”。另一个初始化器是用户eDucer initializer等。所有这些 纯粹的函数如此严格的模式double double调用所有功能。结帐此示例:

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>

严格模式如何有帮助?

让我们看一个示例,严格模式将帮助我们找到一个严重的错误。

// 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>

上面的示例有什么问题?

您会注意到按钮无法正常工作,它们不会切换 iscomplete boolean,问题是更新程序函数传递给 settodos 不是纯函数,因为它在 todos 状态中突变对象。而且由于更新程序被称为两次,并且不是纯函数,因此第二个调用将 iscomplete 布尔值逆转为原始值。

注意:仅仅是因为严格的双重调用,我们才能够捕获这个错误。如果我们选择退出严格的模式,那么组件幸运的是 的工作原理,但这并不意味着代码是正确撰写的,仅由于组件的隔离程度,并且在现实世界中的情况下起作用这样的突变会导致严重的问题。即使您幸运地摆脱了这样的突变,您仍然可能会遇到问题,因为当前更新程序依赖于每次点击仅调用一次,但这是不是 反应保证的东西(考虑并发功能)。

如果将更新程序作为纯函数,它将解决问题:

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

React 18中的React 18中的新事物是什么,'

react 18,' strictmode &nbsp;是否有一个额外的行为,以确保其与可重复使用的状态兼容。当启用严格模式时,&nbsp; 有意反应新安装的组件

考虑下面的示例( 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>

App 上面的组件声明了一些要在安装座上运行的效果。在React 18之前,设置函数只能运行一次(在最初安装组件之后),并且清理功能也只能运行一次(在组件未填充之后)。但是在 strictmode 中的React 18中,将发生以下情况:

  • React呈现组件(两次,没有什么新的
  • REACT安装组件
    • 布局效果设置代码运行
    • 效果设置代码运行
  • React模拟组件被隐藏或未填充
    • 布局效果清理代码运行
    • 效果清理代码运行
  • React模拟组件再次显示或重新启动
    • 布局效果设置代码运行
    • 效果设置代码运行

建议的读数

TL;DR

When components are wrapped in StrictMode, React runs certain functions twice in order to help developers catch mistakes in their code.

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.

If you extract out console.log and use the extracted alias to log, then you would get similar behavior with both versions.

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. 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. However, if you have React DevTools installed, the logs from the second call will appear slightly dimmed. React DevTools also offers a setting (off by default) to suppress them completely.

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.

In Strict Mode in development, React runs the following functions twice:

  • 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.

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).

// 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. 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.

  • 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). If you update the CDN link to React 18, then this extraction wouldn't be required.

  • 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. So, as long as your updaters are pure functions, your application wouldn’t get affected by the no. of times it’s called.

  • "Updaters" & "Initializers" are generic terms in React. State updaters & state initializers are just one amongst many. Other updaters are "callbacks" passed to useMemo and "reducers". Another initializers is useReducer initializer etc. And all of these should be pure functions so strict mode double invokes all of them. 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. 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.

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).

If you make the updater a pure function, it would solve the issue:

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

In React 18, StrictMode gets an additional behavior to ensure it's compatible with reusable state. When Strict Mode is enabled, React intentionally double-invokes effects (mount -> unmount -> mount) for newly mounted components. 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.

Consider the example below (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. 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). But in React 18 in StrictMode, the following would happen:

  • React renders the component (twice, nothing new)
  • React mounts the component
    • Layout effect setup code runs
    • Effect setup code runs
  • React simulates the component being hidden or unmounted
    • Layout effect cleanup code runs
    • Effect cleanup code runs
  • React simulates the component being shown again or remounted
    • Layout effect setup code runs
    • Effect setup code runs

Suggested Readings

ま昔日黯然 2025-02-02 13:39:52
  • React 17严格模式也是双重渲染的,但它抑制了日志。这就是为什么我们没有看到双日志的原因。来自React 17文档:

从React 17开始,React会自动修改控制台
诸如Console.log()之类的方法是在第二个调用中沉默的日志
生命周期功能。但是,这可能会导致不希望的行为
某些可以使用解决方法的情况。

React 18的区别在于它现在显示了双效率。如果使用“ React Developer Tools” Chrome扩展名,则可以看到来自严格模式的日志:

“在此处输入图像说明”

  • React 18的主要补充之一是并发,因此严格的模式也将帮助我们请参阅开发过程中与并发相关的错误。
  • React 17 Strict Mode was also double rendering but it was suppressing the logs. That is why we were not seeing double logs. From react 17 docs:

Starting with React 17, React automatically modifies the console
methods like console.log() to silence the logs in the second call to
lifecycle functions. 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. If you are using "React Developer Tools" chrome extension with, you can see that which logs are coming from strict mode:

enter image description here

  • One of the major additions to React 18 is Concurrency so Strict Mode will also help us see concurrency-related bugs during development.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文