简体   繁体   English

为什么通过 useContext 使用另一个钩子值的自定义钩子只显示其初始值?

[英]Why does a custom hook that uses another hooks value via useContext only shows its initial value?

I'm trying to reuse a bunch of custom hooks without re-invoking them and without maintaining an order through which I'll have to pass cascading parameters from one hook to the other.我试图重用一堆自定义钩子而不重新调用它们,也没有维护我必须将级联参数从一个钩子传递到另一个的顺序。

A working example: https://codesandbox.io/s/laughing-firefly-mlhdw?file=/src/App.js:0-1158一个工作示例: https://codesandbox.io/s/laughing-firefly-mlhdw?file=/src/App.js:0-1158

Given the following code:给定以下代码:

import React, { useContext, useEffect, useState } from "react";

const globalContext = React.createContext({
  user: null,
  pet: null
});

const usePet = () => {
  const [pet, setPet] = useState(null);
 
  useEffect(() => {
    setTimeout(() => {
      setPet("Dog");
    }, 3000);
  }, []);

  return pet;
};

const useUser = () => {
  const [user, setUser] = useState(null);
  // I want to proxy pet via the context so that I won't have to re-invoke its side-effects again
  const { pet } = useContext(globalContext);

  useEffect(() => {
    setTimeout(() => {
      setUser("john");
    }, 500);
  }, []);

  // This is only called once with the default value (null)
  useEffect(() => {
    console.log("Called from user!", { pet });
  }, [pet]);

  return user;
};

export const StateProvider = ({ children }) => {
  const user = useUser();
  const pet = usePet();

  useEffect(() => {
    console.log("StateProvider", { user, pet });
  }, [user, pet]);

  return (
    <globalContext.Provider value={{ user, pet }}>
      {children}
    </globalContext.Provider>
  );
};

export default function App() {
  const { user, pet } = useContext(globalContext);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>
        {pet} {user}
      </h2>
    </div>
  );
}

// imagine an index.js that's wrapping the App component like this:
const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <StateProvider>
      <App />
    </StateProvider>
  </StrictMode>,
  rootElement
);

What I'm expecting to see in the console output is the following我期望在控制台 output 中看到的内容如下


Called from user! {pet: null}
StateProvider {user: null, pet: null}
StateProvider {user: "john", pet: null}
StateProvider {user: "john", pet: "Dog"}
Called from user! {pet: "Dog"}

But I'm not getting any updates inside useUser other than the initial state:但是除了最初的useUser之外,我在 useUser 内部没有得到任何更新:

Called from user! {pet: null}
StateProvider {user: null, pet: null}
StateProvider {user: "john", pet: null}
StateProvider {user: "john", pet: "Dog"}
<!-- no update here, pet is still null for the useUser hook -->

My questions are:我的问题是:

  1. Is it possible to achieve that?有可能实现吗? if it is, what am I missing here?如果是的话,我在这里想念什么?
  2. If it's not possible, is there a more elegant way of passing data between custom hooks without re-invoking them (creating a new state context for each invocation) and without passing parameters from one another, which will force me to also maintain order between everything?如果不可能,是否有一种更优雅的方式在自定义挂钩之间传递数据而不重新调用它们(为每次调用创建一个新的 state 上下文)并且不相互传递参数,这将迫使我也保持一切之间的秩序?

To answer your question:要回答您的问题:

1. Yes, this is possible to achieve. 1.是的,这是可以实现的。

The thing that was missing here is that your useUser() is called inside StateProvider component, ie on the same level with the context provider, whereas for useContext() to work, it has to be called one level down the context provider ( StateProvider as a wrapper).这里缺少的是您的useUser()StateProvider组件内被调用,即与上下文提供者处于同一级别,而useContext()工作,它必须在上下文提供者的下一级被调用( StateProvider为一个包装器)。 In this case, it would be your App component.在这种情况下,它将是您的App组件。

A working code would be as follow:工作代码如下:

export default function App() {
  const { user, pet } = useContext(globalContext);

  // this one will print out in console
  // Called from user!  {pet: "Dog"}
  // as expected
  const userWithInContext = useUser();

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>
        {pet} {user}
      </h2>
    </div>
  );
}

I tested it and it worked.我测试了它并且它有效。 I did not make any other changes, merely setting a new userWithInContext variable up there, using the same useUser() logic you provided.我没有进行任何其他更改,只是使用您提供的相同useUser()逻辑在那里设置了一个新的userWithInContext变量。 In console it will print these outputs:在控制台中,它将打印这些输出:

// this is from userWithInContext inside App component
Called from user! {pet: "Dog"}
// this is from user variable inside StateProvider component
Called from user! {pet: null}

2. Since it is possible to achieve what you want, this is just a side note about elegance and readability as of why you may not want to call useContext() inside a custom hook . 2. 由于可以实现你想要的,这只是关于优雅和可读性的一个侧面说明, 因为你可能不想在自定义钩子中调用 useContext()

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

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