reactjs 为什么useState在不同的props路由之间共享?

rlcwz9us  于 2023-03-22  发布在  React
关注(0)|答案(2)|浏览(118)

我有一个应用程序,有两个标签“苹果”和“香蕉”。每个标签都有一个计数器,用useState实现。

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [ count, setCount ] = useState(0);

  const onClick = e => {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    <section style={style}>
      <h2>{name} Tab</h2>
      <p>Render ID: {id}</p>
      <p>Counter: {count}</p>
      <button onClick={onClick}>+1</button>
      {children}
    </section>
  );
};

令人困惑的是,计数器状态在两个选项卡之间共享!
如果我在一个选项卡上增加计数器,然后切换到另一个选项卡,那里的计数器也会改变。
这是为什么呢?
以下是我的完整app:

import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { v4 as uuid } from "uuid";
import { HashRouter as Router, Switch, Route, Link } from "react-router-dom";

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [ count, setCount ] = useState(0);

  const onClick = e => {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    <section style={style}>
      <h2>{name} Tab</h2>
      <p>Render ID: {id}</p>
      <p>Counter: {count}</p>
      <button onClick={onClick}>+1</button>
      {children}
    </section>
  );
};

const App = () => {
  const id = uuid();

  return (
    <Router>
      <h1>Hello world</h1>
      <p>Render ID: {id}</p>
      <ul>
        <li>
          <Link to="/apple">Apple</Link>
        </li>
        <li>
          <Link to="/banana">Banana</Link>
        </li>
      </ul>
      <Switch>
        <Route
          path="/apple"
          exact={true}
          render={() => {
            return <Tab name="Apple" />;
          }}
        />
        <Route
          path="/banana"
          exact={true}
          render={() => {
            return <Tab name="Banana" />;
          }}
        />
      </Switch>
    </Router>
  );
};

const container = document.getElementById("root");
const root = createRoot(container);

root.render(<App />);

版本:

"dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router": "5.2.1",
    "react-router-dom": "5.2.1",
    "uuid": "^9.0.0"
  },
xkftehaa

xkftehaa1#

它与Switch在react-router-dom中的工作方式有关
最终,组件树保持相同,即使在切换路由时也是如此。
始终是路由器-〉交换机-〉路由-〉选项卡
由于Switch的工作方式,React从不“挂载”新组件,它只是重用旧的树,因为它可以。
我以前遇到过这个问题,解决方法是在某个地方添加一个密钥,比如在TabRoute上。我通常将其添加到Route,因为在我看来这更有意义:

<Switch>
        <Route
          key={'apple'}
          path="/apple"
          exact={true}
          render={() => {
            return <Tab name="Apple" />;
          }}
        />
        <Route
          key={'banana'}
          path="/banana"
          exact={true}
          render={() => {
            return <Tab name="Banana" />;
          }}
        />
      </Switch>

看看这个stackblitz:
https://stackblitz.com/edit/react-gj5mcv?file=src/App.js
当然,当它们被卸载时,你的状态会在每个选项卡中重置,这可能是可取的,也可能是不可取的。但是解决这个问题的方法当然是(如果这对你来说是个问题的话),像往常一样,提升状态。

7y4bm7vi

7y4bm7vi2#

Adam对这里发生的事情有很好的解释和回答,并且不会因为URL路径改变而拆除并重新挂载相同的React组件是一种优化。使用React密钥肯定会通过***强制***React重新挂载Tab组件并因此“重置”count状态来解决这个问题。
我建议另一种方法,保持路由组件安装,并简单地重置count状态时,name prop 从"apple""banana",反之亦然。

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(0); // <-- reset to 0 when name prop updates
  }, [name, setCount]);

  const onClick = e => {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    <section style={style}>
      <h2>{name} Tab</h2>
      <p>Render ID: {id}</p>
      <p>Counter: {count}</p>
      <button onClick={onClick}>+1</button>
      {children}
    </section>
  );
};

这将使RRD优化为您工作,而不是对您不利。
如果你没有一个像name这样的传递属性来作为提示,那么可以使用location.pathname。注意,这确实将一些内部组件逻辑耦合到外部细节。
示例:

const { pathname } = useLocation();
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(0);
}, [pathname, setCount]);

相关问题