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

Abr*_*rar 20 javascript reactjs react-hooks

我正在试验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>
  );
}
Run Code Online (Sandbox Code Playgroud)

钩权利要求来解决的共享部件之间的有状态逻辑的问题,但我发现之间的状态HookComponentHookComponent2不共享.例如,countin 的改变HookComponent2不会导致改变HookComponent.

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

Yan*_*Tay 31

如果您指的是组件状态,那么钩子将无法帮助您在组件之间共享它.组件状态是组件的本地状态.如果你的国家生活在上下文中,那么useContext钩子会有所帮助.

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

如果要在组件之间共享状态,有多种方法可以实现,每种方法都有自己的优点:

1.提升状态

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

function Ancestor() {
    const [count, setCount] = useState(999);
    return <>
      <DescendantA count={count} />
      <DescendantB count={count} />
    </>;
  }
Run Code Online (Sandbox Code Playgroud)

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

2.背景

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

useContext你可以在子组件中使用一个钩子.

3.外部国家管理解决方案

像Redux或Mobx这样的状态管理库.然后,您的州将居住在React之外的商店中,组件可以连接/订阅商店以接收更新.

  • 状态逻辑与状态不同 (7认同)

str*_*tss 21

没有任何外部状态管理库是可能的。只需使用一个简单的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,
  };
}
Run Code Online (Sandbox Code Playgroud)

然后创建一个 store 并使用subscribein钩住它以做出反应useEffect

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
  }
}
Run Code Online (Sandbox Code Playgroud)

这应该有效。不需要React.Context或提升状态

  • 我对此进行了改进,并在此处创建了 Observable 的打字稿版本:https://gist.github.com/SgtPooki/477014cf16436384f10a68268f86255b (2认同)
  • @MichaelJosephAubry这种方法具有与使用上下文相同的缺点:每次更新**任何**属性时,**所有**订阅的组件都将重新渲染。 (2认同)

Sla*_*rch 6

使用useBetween钩子可以做到这一点。

在代码和框中查看

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

我们将 React hooks 有状态逻辑从HookComponentuseShareableState。我们在每个组件中调用useShareableStateusing useBetween

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

  • 但是您可以使用 useBetween 而不是 Redux 来组织应用程序的状态和控制它的逻辑。 (3认同)
  • 我最喜欢这个,替换我的可观察实现,因为已经订阅事件并设置本地状态以便重新渲染。也比 useContext 更好,后者是过度设计的。 (2认同)
  • @SlavaBirch 这个库拥有迄今为止最好的 API,它就像 `immer`,非常简单但功能强大。但遗憾的是,它还不稳定。到目前为止,我自己遇到了 3 个错误。我很乐意与您联系以了解有关该项目原始架构的更多信息,以便我可以做出贡献。 (2认同)

Kar*_*rim 5

文档指出:

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

没有提到状态可以跨组件共享,useState钩子只是给你一种更快的方法来在一条指令中声明一个状态字段及其对应的 setter。