是否可以使用 React 中的 useState() 钩子在组件之间共享状态?

Posted

技术标签:

【中文标题】是否可以使用 React 中的 useState() 钩子在组件之间共享状态?【英文标题】:Is it possible to share states between components using the useState() hook in React? 【发布时间】:2019-04-26 08:31:26 【问题描述】:

我正在试验 React 中的新 Hook 功能。考虑到我有以下两个组件(使用 React Hooks) -

const HookComponent = () => 
  const [username, setUsername] = useState('Abrar');
  const [count, setState] = useState();
  const handleChange = (e) => 
    setUsername(e.target.value);
  

  return (
    <div>
      <input name="userName" value=username onChange=handleChange/>
      <p>username</p>
      <p>From HookComponent: count</p>
    </div>
  )



const HookComponent2 = () => 
  const [count, setCount] = useState(999);
  return (
    <div>
      <p>You clicked count times</p>
      <button onClick=() => setCount(count + 1)>
        Click me
      </button>
    </div>
  );

Hooks 声称解决了组件之间共享状态逻辑的问题,但我发现HookComponentHookComponent2 之间的状态是不可共享的。例如,countHookComponent2 中的更改不会导致HookComponent 中的更改。

是否可以使用useState() 钩子在组件之间共享状态?

【问题讨论】:

【参考方案1】:

这可以使用useBetween 挂钩。

See in codesandbox

import React,  useState  from 'react';
import  useBetween  from 'use-between';

const useShareableState = () => 
  const [username, setUsername] = useState('Abrar');
  const [count, setCount] = useState(0);
  return 
    username,
    setUsername,
    count,
    setCount
  



const HookComponent = () => 
  const  username, setUsername, count  = useBetween(useShareableState);

  const handleChange = (e) => 
    setUsername(e.target.value);
  

  return (
    <div>
      <input name="userName" value=username onChange=handleChange/>
      <p>username</p>
      <p>From HookComponent: count</p>
    </div>
  )



const HookComponent2 = () => 
  const  count, setCount  = useBetween(useShareableState);

  return (
    <div>
      <p>You clicked count times</p>
      <button onClick=() => setCount(count + 1)>
        Click me
      </button>
    </div>
  );

我们将 React 钩子状态逻辑从 HookComponent 移至 useShareableState。 我们在每个组件中使用 useBetween 调用useShareableState

useBetween 是一种调用任何钩子的方法。但是这样状态就不会存储在 React 组件中。 对于同一个钩子,调用的结果是一样的。所以我们可以在不同的组件中调用一个钩子,并在一个状态下协同工作。更新共享状态时,每个使用它的组件也会更新。

免责声明:我是 use-between 包的作者。

【讨论】:

感谢您的贡献!还请在代码中添加一些解释并引用与您链接的内容最相关的内容,因为链接可能会过期! :) 我最喜欢这个,替换我的 observable 实现,因为已经订阅了事件并设置了本地状态以便重新渲染。也比过度设计的 useContext 更好。 谢谢!有一个。不可能在里面使用 React Context。 (不支持 useContext,因为共享逻辑不在一个 React 组件内部,而是在多个组件之间使用)因此,您不能在 ShareableState 逻辑内部使用例如 Redux 中的 useSelector。 但是你可以使用 useBetween 代替 Redux 来组织你的应用程序的状态和控制它的逻辑。 @SlavaBirch 这个库有迄今为止最好的API,就像immer,非常简单但功能强大。但是我很遗憾地说它还不稳定。到目前为止,我自己遇到了 3 个错误。很想与您联系以了解有关该项目的原始架构的更多信息,以便我做出贡献。【参考方案2】:

如果您指的是组件状态,那么钩子将无法帮助您在组件之间共享它。组件状态是组件本地的。如果您的状态存在于上下文中,那么 useContext 钩子会很有帮助。

从根本上说,我认为您误解了“在组件之间共享状态逻辑”这一行。有状态逻辑不同于状态。有状态的逻辑是你所做的修改状态的东西。例如,订阅componentDidMount() 中的商店并取消订阅componentWillUnmount() 中的组件。这种订阅/退订行为可以在钩子中实现,需要此行为的组件只需使用钩子即可。

如果你想在组件之间共享状态,有多种方法可以做到,各有各的优点:

1。提升状态

将状态提升到两个组件的共同祖先组件。

function Ancestor() 
    const [count, setCount] = useState(999);
    return <>
      <DescendantA count=count onCountChange=setCount />
      <DescendantB count=count onCountChange=setCount />
    </>;
  

这种状态共享方式与传统的状态使用方式没有本质区别,钩子只是给了我们一种不同的方式来声明组件状态。

2。上下文

如果后代在组件层次结构中太深并且您不想将状态向下传递太多层,则可以使用Context API。

有一个useContext 挂钩,您可以在子组件中利用它。

3。外部状态管理解决方案

状态管理库,如 Redux 或 Mobx。然后,您的状态将存在于 React 之外的存储中,组件可以连接/订阅存储以接收更新。

【讨论】:

Stateful logic is different from state ? 从技术上讲,您可以创建一个 useGlobalState 函数,它按照 OP 表面上想要的方式工作(它使用幕后的上下文):github.com/dai-shi/react-hooks-global-state 在您的示例 1 中,useState 适用于传递给下游组件,但它不允许双向状态设置 是的。您可以将 setState 回调向下传递到组件中,但这不在此问题的范围内。 已更新我的答案以包括它以防万一它对读者有所帮助。谢谢 Ridhwaan!【参考方案3】:

doc 状态:

我们从 React 中导入 useState Hook。它让我们可以将本地状态保存在函数组件中。

没有提到状态可以跨组件共享,useState 钩子只是为您提供了一种更快的方法来在一条指令中声明状态字段及其对应的设置器。

【讨论】:

有没有一种钩子方式可以跨组件共享状态?不一定是useState() 看看@Yangshun Tay 非常精确的答案,这些是您必须在组件之间共享状态的选项【参考方案4】:

没有任何外部状态管理库也是可能的。只需使用简单的observable 实现:

function makeObservable(target) 
  let listeners = []; // initial listeners can be passed an an argument aswell
  let value = target;

  function get() 
    return value;
  

  function set(newValue) 
    if (value === newValue) return;
    value = newValue;
    listeners.forEach((l) => l(value));
  

  function subscribe(listenerFunc) 
    listeners.push(listenerFunc);
    return () => unsubscribe(listenerFunc); // will be used inside React.useEffect
  

  function unsubscribe(listenerFunc) 
    listeners = listeners.filter((l) => l !== listenerFunc);
  

  return 
    get,
    set,
    subscribe,
  ;

然后创建一个商店并通过在useEffect 中使用subscribe 来挂钩它以做出反应:

const userStore = makeObservable( name: "user", count: 0 );

const useUser = () => 
  const [user, setUser] = React.useState(userStore.get());

  React.useEffect(() => 
    return userStore.subscribe(setUser);
  , []);

  const actions = React.useMemo(() => 
    return 
      setName: (name) => userStore.set( ...user, name ),
      incrementCount: () => userStore.set( ...user, count: user.count + 1 ),
      decrementCount: () => userStore.set( ...user, count: user.count - 1 ),
    
  , [user])

  return 
    state: user,
    actions
  

这应该可行。无需React.Context 或提升状态

【讨论】:

这很可靠,应该有更多的支持...有人看到这有什么问题吗? 同意,这是一个可靠、可靠的答案。我将把它加入书签以备将来使用。 @MichaelJosephAubry 确实非常优雅!但是很高兴注意到,如果您在服务器端渲染,它将无法工作,因为useEffect 我希望我能投票一千次:D 我对此进行了改进并在gist.github.com/SgtPooki/477014cf16436384f10a68268f86255b创建了 Observable 的打字稿版本@【参考方案5】:

我已经创建了 hooksy,可以让你做到这一点 - https://github.com/pie6k/hooksy

import  createStore  from 'hooksy';

interface UserData 
  username: string;


const defaultUser: UserData =  username: 'Foo' ;

export const [useUserStore] = createStore(defaultUser); // we've created store with initial value.
// useUserStore has the same signature like react useState hook, but the state will be shared across all components using it

以后在任何组件中

import React from 'react';

import  useUserStore  from './userStore';

export function UserInfo() 
  const [user, setUser] = useUserStore(); // use it the same way like useState, but have state shared across any component using it (eg. if any of them will call setUser - all other components using it will get re-rendered with new state)

  function login() 
    setUser( username: 'Foo' )
  

  return (
    <div>
      !user && <strong>You're logged out<button onPress=login>Login</button></strong>
      user && <strong>Logged as <strong>user.username</strong></strong>
    </div>
  );

【讨论】:

好吧,让我们试一试。【参考方案6】:

使用钩子是不可能的。 我建议你看看 react-easy-state。 https://github.com/solkimicreb/react-easy-state

我在大型应用程序中使用它,它就像一个魅力。

【讨论】:

【参考方案7】:

您仍然需要将状态提升到 HookComponent1 和 HookComponent2 的祖先组件。这就是你之前共享状态的方式,最新的钩子 API 并没有改变它。

【讨论】:

在钩子 RFC - github.com/reactjs/rfcs/blob/master/text/0068-react-hooks.md 中,它说“在组件之间重用逻辑”,钩子是否提供了将状态提升到祖先组件的替代方法?

以上是关于是否可以使用 React 中的 useState() 钩子在组件之间共享状态?的主要内容,如果未能解决你的问题,请参考以下文章

React 用 useReducer 替换 useState,同时还使用自定义钩子

react中的useState与setState的异步问题

react usestate 重新设置数据后,不渲染页面

如何动态编写 React.useState 函数,以便您可以从 cms 获得尽可能多的输入类型

useState hook如何知道react中的调用上下文

useState 变量不是删除函数中的最新版本,React