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

ee7vknir  于 2023-05-12  发布在  Java
关注(0)|答案(9)|浏览(207)

我正在尝试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之间的状态是不可共享的。例如,HookComponent2中的count的变化不会导致HookComponent的变化。
是否可以使用useState()钩子在组件之间共享状态?

thigvfpy

thigvfpy1#

如果您引用的是组件状态,那么钩子将不会帮助您在组件之间共享它。组件状态是组件的本地状态。如果你的状态存在于上下文中,那么useContext钩子会很有帮助。
从根本上说,我认为你误解了“在组件之间共享有状态逻辑”这句话。状态逻辑不同于状态。状态逻辑是你修改状态的东西。例如,一个组件订阅componentDidMount()中的一个存储并取消订阅componentWillUnmount()。这种订阅/取消订阅行为可以在钩子中实现,需要这种行为的组件可以只使用钩子。
如果你想在组件之间共享状态,有多种方法可以做到这一点,每种方法都有自己的优点:

1.提升状态

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

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

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

2.上下文

如果子体在组件层次结构中太深,并且您不想将状态传递到太多层,则可以使用API。
有一个useContext钩子,您可以在子组件中利用它。

3.外部状态管理方案

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

q8l4jmvw

q8l4jmvw2#

不需要任何外部状态管理库也是可能的。只需使用一个简单的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,
  };
}

然后创建一个store,并通过在useEffect中使用subscribe将其挂钩以进行React:

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

wgmfuz8q

wgmfuz8q3#

这可以使用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 hooks有状态逻辑从HookComponent迁移到useShareableState。我们在每个组件中使用useBetween调用useShareableState
useBetween是一种调用任何钩子的方法。但是这样状态就不会存储在React组件中。对于同一个钩子,调用的结果将是相同的。因此,我们可以在不同的组件中调用一个钩子,并在一个状态下一起工作。当更新共享状态时,使用它的每个组件也将被更新。

  • 免责声明:我是use-between软件包的作者。*
y1aodyip

y1aodyip4#

doc声明:
我们从React中导入useState Hook。它允许我们在函数组件中保持局部状态。
没有提到状态可以在组件之间共享,useState钩子只是给予你一个更快的方法来声明一个状态字段和它对应的setter在一条指令中。

c0vxltue

c0vxltue5#

我创建了一个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>
  );
}
hm2xizp9

hm2xizp96#

用钩子是不可能的。我建议你看看react-easy-state。https://github.com/solkimicreb/react-easy-state
我在大型应用程序中使用它,它的工作原理就像一个魅力。

r1wp621o

r1wp621o7#

我要下地狱了:

// 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的真实的的解决方案。

atmip9wb

atmip9wb8#

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

scyqe7ek

scyqe7ek9#

这真的很酷,但我遇到了一个问题,我希望@streletss可能能够帮助解决。下面是一个例子的问题。
用户没有使用foo更新。然后在setLastName中设置存储,并将其设置回user。你知道为什么吗?

const userStore = makeObservable({ firstname: "user", lastname "user" });
const useUser = () => {
  const [user, setUser] = React.useState(userStore.get());

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

  const actions = React.useMemo(() => {
    return {
      setFirstName: async () => {
          const firstname = api.getFirstName() // returns "foo"
          userStore.set({ ...user, firstname })
      },
      setLastName: async () => {
          const lastname = api.getLastName() // returns "bar"
          userStore.set({ ...user, lastname })
      },
    }
  }, [user])

  return {
    state: user,
    actions
  }
}

setFirstName();
setLastName();

console.log(state)
// out puts the following { firstname: "user", lastname: "bar" }

相关问题