alibaba / hooks

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/
https://ahooks.js.org/
MIT License
13.9k stars 2.69k forks source link

[RFC] useGlobalState 全局状态 #1832

Closed mushan0x0 closed 2 years ago

mushan0x0 commented 2 years ago

useGlobalState

React Hook 实现一个全局状态的功能,不同页面不同组件使用同一个状态。Preview →

https://user-images.githubusercontent.com/29084441/186406656-d0f1a7fd-0402-4bf6-ac8c-d16122b5eb24.mp4

Usage

1.先为全局状态设置一个唯一key,或者还可以设置一个初始值

const initStep = 1;
const key = 'global-step';
const [step, setStep] = useGlobalState(key, initStep);
或者二次封装一个useGlobalStep
const useGlobalStep = (initStep: number = 0) => {
  const key = 'global-step';
  useGlobalState(key, initStep);
}

2.在其他组件或者页面引入useGlobalState并使用相同的key

const PageA = () => {
  const key = 'global-step';
  const [step, setStep] = useGlobalState(key, 1);
  return <div>
    {step}
    <Button onClick={() => setStep(step + 1)}>step + 1</Button>
  </div>
}

const PageB = () => {
  const key = 'global-step';
  const [step, setStep] = useGlobalState(key, 1);
  return <div>
    {step}
    <Button onClick={() => setStep(step - 1)}>step - 1</Button>
  </div>
}
或者使用二次封装的useGlobalStep
const PageA = () => {
  const [step, setStep] = useGlobalStep();
  return <div>
    {step}
    <Button onClick={() => setStep(step + 1)}>step + 1</Button>
  </div>
}

const PageB = () => {
  const [step, setStep] = useGlobalStep();
  return <div>
    {step}
    <Button onClick={() => setStep(step - 1)}>step - 1</Button>
  </div>
}

API

useGlobalState\<T>(key: string, initState?: T): [T, (state: T) => void]

参数 类型 必须? 描述
key string Yes 全局状态的唯一key
initState any No 状态的初始值

Demo

Edit dumi-demo (forked)

mushan0x0 commented 2 years ago

@brickspert 有什么意见不

mushan0x0 commented 2 years ago

@awmleer

brickspert commented 2 years ago

关于全局状态管理,我们建议使用 hox,ahooks 里不建议加这个 Hooks

brickspert commented 2 years ago

感谢 RFC 哈!

mushan0x0 commented 2 years ago

@brickspert 这个不一样,你看下里面的源码呢 https://codesandbox.io/s/dumi-demo-forked-73u0pv?file=/useGlobalState.tsx

mushan0x0 commented 2 years ago

这个是用批量更新实现的全局状态,不是用的context

hchlq commented 2 years ago

实现是用全局的一个 globalStates 去管理,相当于脱离了 React 的范围之外了,这样好么?

hchlq commented 2 years ago

那我用 Context 去管理岂不是更好么,感觉不到优势在哪呢

mushan0x0 commented 2 years ago

可以实现组件卸载可以清除globalStates里面存的内容,优势在于不需要provider,useContext,createContext @hchlq

hchlq commented 2 years ago

我的意思不是 ”组件卸载可以清除里面存的内容“。如果是 React 18 之后的,该如何考虑,状态管理是不建议脱离 React 的范围的

而且 createContext 我也可以封装,使用也很方便呀

mushan0x0 commented 2 years ago

我以为你考虑的是内存泄漏的问题,你可以举例说明可能会有什么问题吗 @hchlq

mushan0x0 commented 2 years ago

createContext 容易被滥用,开发者可能会往里面放很多东西,useGlobalState设计跟useState是一样的,可以防止这一点

hchlq commented 2 years ago

没有特别好的举例,但是状态尽量不要脱离 React 的范围~

hchlq commented 2 years ago

滥用是相对而言的,团队中通过一些开发原则就可以避免了

mushan0x0 commented 2 years ago

但是这样也没有脱离react范围,实际设置状态和读取状态都是react的useState

hchlq commented 2 years ago

但是你会间接额外的维护状态和更新函数呀,我觉得不是特别有必要,看下 @brickspert 怎么说~

miracles1919 commented 2 years ago

你的 demo 看起来比较简单,怎么处理多个数据呢

比如有 Global-A、 Global-B、Global-C,看起来得这样..?

const useGlobalA = () => useGlobalState('A');
const useGlobalB = () => useGlobalState('B');
const useGlobalC = () => useGlobalState('C');

const A = () => {
  const [globalA] = useGlobalA();
  const [globalB] = useGlobalB();
  const [globalC] = useGlobalC();
  return <div>组件A:{globalA}{globalB}{globalC}</div>;
};
mushan0x0 commented 2 years ago

@miracles1919 是的,它跟useState一样,只不过多了个key

miracles1919 commented 2 years ago

那感觉实用性不是很大啊... 真实的场景下往往是需要聚合多组数据的,个人觉得没有必要加在 ahooks 里

关于全局状态管理,我们推荐使用 hox

mushan0x0 commented 2 years ago

多个数据组合也可以这样用,useState可以怎么用,它就可以怎么用

const useGlobalABC = () => {
  const [globalA] = useGlobalState('A');
  const [globalB] = useGlobalState('B');
  const [globalC] = useGlobalState('C');
  return { globalA, globalB, globalC };
};
const A = () => {
  const {globalA, globalB, globalC} = useGlobalABC();
  return <div>组件A:{globalA}{globalB}{globalC}</div>;
};
mushan0x0 commented 2 years ago

@miracles1919 组合使用的例子我加了,你看看

awmleer commented 2 years ago

我有个小问题,如果在多个组件中都调用了 useGlobalStatekey 是同一个,但是 initialValue 值不一样,这种情况感觉还挺有问题的?

const Foo = () => {
  const [counter, setCounter] = useGlobalState('counter', 1)
  // ...
}

const Bar = () => {
  const [counter, setCounter] = useGlobalState('counter', 2)
  // ...
}
mushan0x0 commented 2 years ago

我有个小问题,如果在多个组件中都调用了 useGlobalStatekey 是同一个,但是 initialValue 值不一样,这种情况感觉还挺有问题的?


const Foo = () => {

  const [counter, setCounter] = useGlobalState('counter', 1)

  // ...

}

const Bar = () => {

  const [counter, setCounter] = useGlobalState('counter', 2)

  // ...

}

key重复是不好避免的问题,但是像useLocalStorage这些都是用的key,大家普遍在用一般不会遇到什么问题。初始值只会在第一个使用到的地方才会生效,这个容易让用户困惑,但是可以加一些警告说初始值没有生效来避免

miracles1919 commented 2 years ago

这么说的话... useLocalStorage 完全能代替啊

mushan0x0 commented 2 years ago

这么说的话... useLocalStorage 完全能代替啊

useLocalStorage不会更新所有用到的地方啊....你看看例子呢。而且有全局状态需求的地方并不一定有存储local storage的需求啊

brickspert commented 2 years ago

我的结论是,这个 Hook 只适合简单的项目,稍微复杂的项目还是要引入正儿八经的状态库的。

mushan0x0 commented 2 years ago

我的结论是,这个 Hook 只适合简单的项目,稍微复杂的项目还是要引入正儿八经的状态库的。

很早期我就有在大大小小的项目使用了,目前没什么问题,甚至有很多好处。这个就是使用很简单,如果项目是需要靠使用门槛来限制全局状态的使用,用别的状态库也是可以的。

miracles1919 commented 2 years ago

用 key 这个方案,设计上就是有缺陷的,例如 https://github.com/alibaba/hooks/issues/1832#issuecomment-1229097767 写的 demo,没有办法解决重复 key 的问题

设计上的问题,需要人为的方式避免(比如警告?),肯定是不合理的,一旦项目规模或者团队规模起来了,就是在给他们埋坑

这个就在你的项目中自己维护吧

mushan0x0 commented 2 years ago

useLocalStorage难道大型项目里面不能用吗,用了useLocalStorage就是在埋坑吗

miracles1919 commented 2 years ago

如果 useLocalStorage 用于全局状态,那确实是在埋坑...

mushan0x0 commented 2 years ago

你的全局状态概念还是传统的里面乱七八糟放很多东西,这种情况全局状态不好管理,但这个全局状态就只是一个状态了,副作用没那么大。

比如一个暗色主题切换,主题色切换,第一直觉就会让人用这个工具,他们本身就是一个状态而已。

mushan0x0 commented 2 years ago

而且这样状态颗粒度低,性能也好优化,避免了传统全局状态里面放的东西太多,带来的不必要的渲染

miracles1919 commented 2 years ago

1、我上面说的是这个 Hook 作为全局状态,设计上的弊端

结合起来就是 https://github.com/alibaba/hooks/issues/1832#issuecomment-1229097767 这个例子,使用 Foo 组件的时候 Count 是 1,使用 Bar 组件的时候 Count 变成了 2,反而会增加复杂性

2、对于全局状态的数据规模,如果复杂度比较低,比如一个主题色切换,那使用传统的状态管理一样有这些优点 "状态颗粒度低、性能也好优化、避免带来的不必要的渲染"

但是反过来,如果全局状态有一定的复杂度,用 useGlobalState 反而不好管理了

所以,这个 RFC 个人觉得没有必要,先关了。有问题可以继续讨论哈

mushan0x0 commented 2 years ago

一般使用都是会二次封装比如例子useGlobalStep,因为没人想重复去写key和初始值还有类型,复杂例子我也写了的useGlobalABC,我个人觉得很多人被redux、dva给教化了,没有需求制造需求,简单的反而是异类了

brickspert commented 2 years ago

一般使用都是会二次封装比如例子useGlobalStep,因为没人想重复去写key和初始值还有类型,复杂例子我也写了的useGlobalABC,我个人觉得很多人被redux、dva给教化了,没有需求制造需求,简单的反而是异类了

我现在一直用 hox,很简单的一个状态管理库。

SignDawn commented 11 months ago

我认为这个 hook 还是挺有意思的,有点类似于 vue 中将 ref 变量放到 composition api 外。 所谓的 key 和 初始值问题,可以稍微改造一下。 让 开发者 不传这个,传一个对象,如果是同一个对象,那么就是同一个全局状态;这样初始值就没有歧义了。state 和 sets 都存储到这个对象里。 image

image

改造了一下 demo ,可以参考。https://codesandbox.io/s/dumi-demo-forked-z6j9zh?file=/combination-use.tsx

mushan0x0 commented 11 months ago

https://github.com/reactjs/rfcs/pull/224#issuecomment-1678265041 react-use 已经有了。要解决 key 重复,用 Symbo 代替字符串就行了。最大的问题是初始化值,其实加个警告说已经初始化过了也可以。