是否可以使用 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 声称可以解决组件之间共享状态逻辑的问题,但我发现 HookComponent
和 HookComponent2
之间的状态不可共享。例如,HookComponent2
中 count
的更改不会呈现 HookComponent
中的更改。
是否可以使用 useState()
挂钩在组件之间共享状态?
doc 状态:
We import the useState Hook from React. It lets us keep local state in a function component.
没有提到状态可以跨组件共享,useState
钩子只是给你一个更快的方法来声明一个状态字段及其对应的 setter 在一条指令中。
您仍然需要将您的状态提升到 HookComponent1 和 HookComponent2 的祖先组件。这就是你之前共享状态的方式,最新的钩子 api 并没有改变它的任何内容。
如果您指的是组件状态,那么挂钩不会帮助您在组件之间共享它。组件状态是组件本地的。如果您的状态存在于上下文中,那么 useContext
钩子会有所帮助。
从根本上说,我认为您误解了“在组件之间共享有状态逻辑”这一行。状态逻辑不同于状态。有状态逻辑是您所做的修改状态的事情。例如,一个组件在 componentDidMount()
中订阅商店并在 componentWillUnmount()
中取消订阅。这种 subscribing/unsubscribing 行为可以在钩子中实现,需要这种行为的组件可以直接使用钩子。
如果你想在组件之间共享状态,有多种方法可以做到,每种方法都有自己的优点:
1。提升状态
将状态提升到两个组件的共同祖先组件。
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
这种状态共享的方式与传统的状态使用方式没有本质区别,hooks 只是给了我们一种不同的方式来声明组件状态。
2。上下文
如果后代在组件层次结构中太深并且您不想将状态向下传递太多层,则可以使用 Context API.
有一个 useContext
挂钩,您可以在子组件中利用它。
3。外部状态管理解决方案
状态管理库,如 Redux 或 Mobx。然后你的状态将存在于 React 之外的商店中,组件可以 connect/subscribe 到商店接收更新。
用钩子它不是直接可能的。
我建议你看看 react-easy-state。
https://github.com/solkimicreb/react-easy-state
我在大型应用程序中使用它,它非常有用。
我已经创建了 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>
);
}
没有任何外部状态管理库也是可以的。只需使用一个简单的 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
或提升状态
这可以使用 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 hooks 状态逻辑从 HookComponent
移动到 useShareableState
。
我们在每个组件中使用 useBetween
调用 useShareableState
。
useBetween
是一种调用任何钩子的方法。但是这样状态就不会存储在 React 组件中。
对于同一个钩子,调用的结果会是一样的。所以我们可以在不同的组件中调用一个钩子并在一种状态下协同工作。更新共享状态时,使用它的每个组件也会更新。
免责声明:我是 use-between
软件包的作者。
我正在试验 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 声称可以解决组件之间共享状态逻辑的问题,但我发现 HookComponent
和 HookComponent2
之间的状态不可共享。例如,HookComponent2
中 count
的更改不会呈现 HookComponent
中的更改。
是否可以使用 useState()
挂钩在组件之间共享状态?
doc 状态:
We import the useState Hook from React. It lets us keep local state in a function component.
没有提到状态可以跨组件共享,useState
钩子只是给你一个更快的方法来声明一个状态字段及其对应的 setter 在一条指令中。
您仍然需要将您的状态提升到 HookComponent1 和 HookComponent2 的祖先组件。这就是你之前共享状态的方式,最新的钩子 api 并没有改变它的任何内容。
如果您指的是组件状态,那么挂钩不会帮助您在组件之间共享它。组件状态是组件本地的。如果您的状态存在于上下文中,那么 useContext
钩子会有所帮助。
从根本上说,我认为您误解了“在组件之间共享有状态逻辑”这一行。状态逻辑不同于状态。有状态逻辑是您所做的修改状态的事情。例如,一个组件在 componentDidMount()
中订阅商店并在 componentWillUnmount()
中取消订阅。这种 subscribing/unsubscribing 行为可以在钩子中实现,需要这种行为的组件可以直接使用钩子。
如果你想在组件之间共享状态,有多种方法可以做到,每种方法都有自己的优点:
1。提升状态
将状态提升到两个组件的共同祖先组件。
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
这种状态共享的方式与传统的状态使用方式没有本质区别,hooks 只是给了我们一种不同的方式来声明组件状态。
2。上下文
如果后代在组件层次结构中太深并且您不想将状态向下传递太多层,则可以使用 Context API.
有一个 useContext
挂钩,您可以在子组件中利用它。
3。外部状态管理解决方案
状态管理库,如 Redux 或 Mobx。然后你的状态将存在于 React 之外的商店中,组件可以 connect/subscribe 到商店接收更新。
用钩子它不是直接可能的。 我建议你看看 react-easy-state。 https://github.com/solkimicreb/react-easy-state
我在大型应用程序中使用它,它非常有用。
我已经创建了 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>
);
}
没有任何外部状态管理库也是可以的。只需使用一个简单的 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
或提升状态
这可以使用 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 hooks 状态逻辑从 HookComponent
移动到 useShareableState
。
我们在每个组件中使用 useBetween
调用 useShareableState
。
useBetween
是一种调用任何钩子的方法。但是这样状态就不会存储在 React 组件中。
对于同一个钩子,调用的结果会是一样的。所以我们可以在不同的组件中调用一个钩子并在一种状态下协同工作。更新共享状态时,使用它的每个组件也会更新。
免责声明:我是 use-between
软件包的作者。