【问题标题】:hook change state doesn't update context provider's value?挂钩更改状态不会更新上下文提供者的值?
【发布时间】:2020-09-05 07:51:35
【问题描述】:

我有 2 个组件和一个上下文提供程序,当我在父级别调用我的钩子时,我可以毫无问题地更改状态并让这 2 个组件通过上下文获取值

contex api 使用的工作演示,但我在父级别调用更改状态,这不是我想要的 https://stackblitz.com/edit/react-51e2ky?file=index.js

我想用钩子改变内部组件的状态,但是当我点击navbar login 时我没有看到值被改变。

https://stackblitz.com/edit/react-rgenmi?file=Navbar.js

父母:

const App = () => {
  const {user} = loginHook()
    return (
      <UserContext.Provider value={user}>
        <Navbar />
        <Content />
      </UserContext.Provider>
    );

}

Navbar.js

const Navbar = () => {
  const user = React.useContext(userContex)
  const {setUser} = loginHook()

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}

自定义钩子

const loginHook = () => {
  const [user, setUser] = React.useState(null)

  return {
    user,
    setUser
  }
}

我可以将 setUser 从父级传递给子级,但我想避免这种情况,我希望我可以使用上下文 api 并无缝响应钩子。

【问题讨论】:

    标签: javascript reactjs react-context


    【解决方案1】:

    目前,您只是在上下文中设置 user 值,这就是为什么可以获取正确值的原因。

    但是,在您的 Navbar.js 组件中,您正在调用 loginHook,这将创建该挂钩的新“实例”,有效地拥有自己的状态。

    我建议您也将更新功能添加到您的上下文中

    const App = () => {
      const {user, setUser} = loginHook()
        return (
          <UserContext.Provider value={{ user, setUser}}>
            <Navbar />
            <Content />
          </UserContext.Provider>
        );
    
    }
    

    这样您也可以在您的孩子中访问setUser,例如

    const Navbar = () => {
      const {user, setUser} = React.useContext(userContex)
    
      return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
        setUser({
          name: 'jane'
        })
      }}>navbar Login</button>}</div>
    }
    

    另外,请注意:最好使用 use 开始自定义钩子,因为这是编写自己的钩子时的最佳实践。

    但是,重要的警告是,这并不是一个真正的好习惯。如果您的user 发生更改,所有只收听setUser 的组件也将获得更新,从而进行无用的重新渲染。您可以通过使用两种不同的上下文来解决此问题,一种用于值,一种用于更新程序。你可以阅读更多关于这个here

    【讨论】:

      【解决方案2】:

      您不能从孩子那里更改父母的上下文信息,不。您需要从父级传递一些内容给子级,子级可以使用这些内容让父级知道上下文需要更新(例如父级的setUser 副本)。您可以通过道具或将setUser 添加到上下文中来做到这一点,尽管我倾向于将其作为需要能够设置用户的组件的道具,而不是他们都可以访问的上下文。

      在两个地方都使用loginHook 不起作用的原因是每个组件(AppNavbar)都有自己的user 副本。这是钩子如何工作的基础。 (如果它以某种方式让他们共享状态信息,useState 将根本不起作用——所有状态将在所有组件之间共享。)Dan Abramov 的A Complete Guide to useEffect 可能是一个有用的读物​​(它更多地是关于钩子和组件如何工作的而不是专门针对useEffect)。

      【讨论】:

        【解决方案3】:

        您必须注意自定义挂钩不共享实例引用,因此如果您在 App 中使用 loginHook 而在 Navbar 中使用另一个,它们将创建 2 个独立的状态和更新程序

        现在使用自定义钩子中的 setter 将更新上下文中的状态。

        您可以通过编写 loginHook 来重构它,以便它在内部使用上下文,然后使用它

        const App = () => {
          const [user, setUser] = useState();
            return (
              <UserContext.Provider value={{user, setUser}}>
                <Navbar />
                <Content />
              </UserContext.Provider>
            );
        
        }
        
        const Navbar = () => {
          const {user, setUser} = loginHook();
        
          return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
            setUser({
              name: 'jane'
            })
          }}>navbar Login</button>}</div>
        }
        const loginHook = () => {
          const {user, setUser} = React.useContext(UserContext)
        
          return {
            user,
            setUser
          }
        }
        

        现在有多种方法可以编写此代码,但是在上述情况下最好的方法是根本不使用自定义钩子,因为它无论如何都没有用

        const App = () => {
          const [user, setUser] = useState();
            return (
              <UserContext.Provider value={{user, setUser}}>
                <Navbar />
                <Content />
              </UserContext.Provider>
            );
        
        }
        
        const Navbar = () => {
          const {user, setUser} = React.useContext(UserContext);
        
          return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
            setUser({
              name: 'jane'
            })
          }}>navbar Login</button>}</div>
        }
        

        【讨论】:

        • 所以当我使用上下文时不需要钩子?那么什么时候使用钩子呢?是上下文 api 来替换钩子吗?现在我很困惑何时使用钩子和上下文,因为两者都可以作为多个组件之间的管理状态。
        • 当您有一个在多个地方使用的通用功能时,您可以将内容放入自定义挂钩中。例如,来自不同页面的简单获取请求。自定义钩子也不管理组件之间的公共状态,但上下文可以。将上下文视为像 redux 一样的数据存储 API,它的最低版本
        • Also custom hooks do not manage a common state between components,这句话是真的吗?听起来不对..
        • @AndreThonas,是的,这是真的,如果您在自定义钩子中有一个状态,则使用它的每个实例都有自己的状态引用,并且不会引用同一个
        【解决方案4】:

        在您的Navbar.js 中,您使用您的loginHook 钩子,它将创建一个与App.js 中使用的状态不同的新独立状态。您需要编写您的钩子,以便使用上下文而不是 useState:

        /* UserContext.js */
        
        const UserContext = createContext();
        
        export const UserProvider = ({children}) => {
            const [user, setUser] = useState(null);
        
            return (
                <UserContext.Provider value={{user, setUser}}>
                    {children}
                </UserContext.Provider>
            );
        }
        
        export const useLogin = () => useContext(UserContext);
        

        然后像这样使用它:

        /* App.js */
        
        import {UserProvider} from './UserContext';
        
        const App = () => (
            <UserProvider>
                <Navbar />
                <Content />
            </UserProvider>
        );
        

        /* Navbar.js */
        
        import {useLogin} from './UserContext';
        
        const Navbar = () => {
          const {user, setUser} = useLogin();
        
          return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
            setUser({
              name: 'jane'
            })
          }}>navbar Login</button>}</div>
        }
        

        【讨论】:

        • 所以当我使用上下文时不需要钩子?
        • @AndreThonas 好吧,它隐藏了使用钩子的内部结构,这通常是一个更好的设计。在这种情况下,它会对用户隐藏实际的UserContext。从技术上讲,在这种简单的情况下,您可以直接使用useContext(UserContext),但我提出的解决方案更简洁。
        • 您的解决方案不错,但不够灵活。想象一下,有一天我有另一个组件想要添加一个与用户无关的 setState,然后我必须复制另一组 context.js
        • @AndreThonas 好吧,您没有指定这是一项要求。此外,您不应将太多不相关的状态置于相同的上下文中,因为这可能会导致您的应用程序中出现许多不必要的重新渲染。如果你需要一个通用的全局状态管理器,你最好使用redux。也许我不明白你的意思。我的解决方案怎么不灵活?你能详细说明一下吗?
        猜你喜欢
        • 2019-08-23
        • 2011-11-19
        • 2022-01-21
        • 1970-01-01
        • 2022-08-08
        • 1970-01-01
        • 2020-01-21
        • 2021-03-22
        • 2022-01-18
        相关资源
        最近更新 更多