挂钩更改状态不会更新上下文提供者的值?
Posted
技术标签:
【中文标题】挂钩更改状态不会更新上下文提供者的值?【英文标题】: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 并无缝响应钩子。
【问题讨论】:
【参考方案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
不起作用的原因是每个组件(App
和Navbar
)都有自己的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
。也许我不明白你的意思。我的解决方案怎么不灵活?你能详细说明一下吗?以上是关于挂钩更改状态不会更新上下文提供者的值?的主要内容,如果未能解决你的问题,请参考以下文章
无法读取上下文提供程序中的 useReducer 挂钩更新的状态
当提供者组件父状态发生变化时,为啥不能更新子组件中的值(带有反应上下文)?
useContext 未在子组件中显示更新的状态(当从全局文件中的 useEffect 挂钩中的 firebase 检索数据时)