繁体   English   中英

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

[英]Is it possible to share states between components using the useState() hook in React?

我正在试验 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之间的状态是不可共享的。 例如, HookComponent2count的更改不会呈现HookComponent中的更改。

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

如果您指的是组件状态,那么钩子将无法帮助您在组件之间共享它。 组件状态是组件本地的。 如果您的状态存在于上下文中,那么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 之外的存储中,并且组件可以连接/订阅存储以接收更新。

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

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或提升状态

这可以使用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>
  );
}

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

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

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

文档指出:

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

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

我已经创建了 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>
  );
}

用钩子它不可能直接。 我建议你看看 react-easy-state。 https://github.com/solkimicreb/react-easy-state

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

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

我要为此下地狱:

// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'

let message = undefined

export default function useMessagePipe() {
  const triggerRender = useReducer((bool) => !bool, true)[1]
  function update(term: string) {
    message = term.length > 0 ? term : undefined
    triggerRender()
  }

  return {message: message, sendMessage: update}
}

完整解释在: https ://stackoverflow.com/a/72917627/1246547


是的,这是我能想到的解决特定用例的最肮脏和最简洁的方法。 是的,对于一种简洁的方式,您可能想学习如何使用useContext ,或者查看react-easy-stateuseBetween以获取低占用空间的解决方案,以及使用flux或redux来获取真实的东西。

暂无
暂无

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

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