为什么 React Context Provider 组件会渲染两次?

3
我在尝试使用React Context API时发现,每次值改变时提供程序都会渲染两次。
以下是我的操作步骤:
1. 创建了一个上下文,然后创建了一个组件来渲染其提供程序。 2. 提供程序提供了一个状态值及其setter。 3. 创建了一个直接子级,直接渲染其子级。 4. 创建了一个消费者,该消费者读取上下文值并添加一个按钮来更改它。 5. 每个组件在渲染时都会执行console.log。 6. 在提供程序中添加了一个effect而没有deps,以记录每次渲染。 7. 在提供程序中添加了一个ref,在每次渲染时递增,并在渲染和效果中进行记录。
问题是,每次我点击按钮更改上下文值时,提供程序都会渲染两次,但效果仅执行一次。
因此,ref始终递增两次,但效果每次仅获取最后一个值(跳过一个值!)。
此外,在提供程序的第一次呈现中,它记录了两次相同的ref值,这对我来说非常奇怪。
有人能告诉我为什么会出现这种行为吗?
以下是代码:
const MyContext = React.createContext(0);

function Provider({ children }) {
  const state = React.useState("Yes");
  const ref = React.useRef(0);
  ref.current += 1;
  console.log("Context provider ", ref.current);
  React.useEffect(() => {
    console.log("effect on provider, ref value = ", ref.current);
  });

  return <MyContext.Provider value={state}>{children}</MyContext.Provider>;
}

两个孩子
function DirectChild({ children }) {
  console.log("provider direct child");
  return children;
}

function Consumer() {
  console.log("consumer");
  const [state, setState] = React.useContext(MyContext);
  return (
    <div>
      <span>State value: {state}</span>
      <br />
      <button onClick={() => setState(old => old + "Yes")}>Change</button>
    </div>
  );
}

这个应用程序

export default function App() {
  console.log("APP");
  return (
    <Provider>
      <DirectChild>
        <Consumer />
      </DirectChild>
    </Provider>
  );
}

这里有一个codesandbox演示


将状态逻辑移至应用级别,行为也在App中,导致渲染两次,记录两次,并且只有一个效果。这里是演示 - undefined
据我理解,refs是用于引用DOM对象的,而不是状态引用(即useState)。虽然我猜你可以用它们来引用任何东西,但不确定这是否符合其意图:"避免使用refs来处理可以以声明方式完成的事情" - undefined
是的,引用(refs)的目的是创建一个可变对象,您可以随意修改。我找到了这背后的原因,我现在将发布答案;它是在 CodeSandbox 中的 React.strictMode。 - undefined
只影响开发模式 - undefined
是的,我猜到了这个情况 :) 大约上周我回答过一个类似的问题... - undefined
看到了重复标志并接受了它,尽管我已经发布了自己的答案。谢谢你的帮助。 - undefined
1个回答

6

找到了原因,

没有bug,也没有奇怪的行为,我只是漏掉了codesandbox默认在一个React.StrictMode父级中渲染应用程序。

首先,我将代码移植到本地项目中,然后观察到没有任何问题。

搜索codesandbox仓库问题,并发现它与strict mode上的react行为有关:

在开发中,预期setState更新器会在严格模式下运行两次。这有助于确保代码不依赖于它们仅运行一次(如果异步渲染被中止并重新启动,则情况将不同)。如果您的setState更新器是纯函数(就像它们应该是的),那么这不应影响应用程序的逻辑。

Codesandbox问题

React问题

但是,效果只执行一次,并错过了我的ref更新。

编辑

React 18引入了严格效果,在开发的严格模式下也会运行效果两次。


如果你想追踪 ref 值,可以使用 React.useCallback。以下是示例代码:const ref = React.useRef(0); let setRef = React.useCallback(setValue => { ref.current = setValue(ref.current); console.log(ref.current) }, []) React.useEffect(() => { setRef(old => old + 1) }, [state[0]]); - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接