redux React全局状态没有上下文或还原?

rsaldnfx  于 2023-03-12  发布在  React
关注(0)|答案(2)|浏览(176)

我最近看到了下面的文章State Management with React Hooks — No Redux or Context API。自从开始React以来,谈论最多的问题总是状态管理和全局状态。Redux一直是流行的选择,最近是上下文API。但这种方法似乎更容易,代码更少,可伸缩性更强。
我的问题是,是否有人能看到使用这种我可能忽略了的状态管理方法的缺点,我对代码做了一些修改以支持SSR,它可以在Nextjs中工作,并且使其在使用操作和状态变量设置方面更加友好。
useGlobalState.js

import React, { useState, useEffect, useLayoutEffect } from 'react';

const effect = typeof window === 'undefined' ? useEffect : useLayoutEffect;

function setState(newState) {
    if (newState === this.state) return;
    this.state = newState;
    this.listeners.forEach((listener) => {
        listener(this.state);
    });
}

function useCustom() {
    const newListener = useState()[1];
    effect(() => {
        this.listeners.push(newListener);
        return () => {
            this.listeners = this.listeners.filter((listener) => listener !== newListener);
        };
    }, []);
    return [this.state, this.setState, this.actions];
}

function associateActions(store, actions) {
    const associatedActions = {};
    if (actions) {
        Object.keys(actions).forEach((key) => {
            if (typeof actions[key] === 'function') {
                associatedActions[key] = actions[key].bind(null, store);
            }
            if (typeof actions[key] === 'object') {
                associatedActions[key] = associateActions(store, actions[key]);
            }
        });
    }
    return associatedActions;
}

const useGlobalHook = (initialState, actions) => {
    const store = { state: initialState, listeners: [] };
    store.setState = setState.bind(store);
    store.actions = associateActions(store, actions);
    return useCustom.bind(store, React);
};

export default useGlobalHook;

然后为一个状态变量设置一个自定义钩子,可以是一个简单的字符串或者是一个对象下面是一个简单的例子:

import useGlobalState from './useGlobalState';

const initialState = 'Hi';

// Example action for complex processes setState will be passed to component for use as well
const someAction = (store, val) => store.setState(val); 

const useValue = useGlobalState(initialState, { someAction });

export default useValue;

以及在组件中的使用:

import React from 'react'
import useVal from './useVal'

export default () => {
  const [val, setVal, actions] = useVal();

  const handleClick = () => {
    setVal('New Val');
    // or use some actions
    actions.someAction('New Val');
  }

  return(
    <div>{val}</div>
    <button onClick={handleClick}>Click Me</button>
  )
}

这看起来是一种更干净、更简单的方法,我想知道为什么这不是状态管理的最佳方法。首先,你不必将所有东西都 Package 在一个提供程序中。其次,它非常容易实现,实际应用中涉及的代码也少得多。有人能看到使用这种方法的缺点吗?我唯一能想到的是上下文API的重新呈现问题,但在小块中,这不应该是一个问题。

t8e9dugd

t8e9dugd1#

我一直在使用一种类似的方法,我真的很喜欢它。我真的不能相信更多的人不谈论这种方法。我在这里写了一个自定义钩子React Global Store Hook。它让你自由地从应用程序的任何地方调度和浅比较,以避免不必要的重新渲染。我不认为任何性能问题,只要你可以避免不必要的重新渲染。
总的来说,这是一个简单的概念。你基本上创建一个函数来存储你的状态,并返回2个函数。一个是设置存储状态的函数,另一个是react组件中使用的钩子。在钩子中,你使用createEffect获取react on initialrender的setState函数,并将其存储在一个数组中。然后,你可以使用这个setState函数来重新呈现你的组件。所以当你调用dispatch函数时你可以循环访问这些setState函数并调用它们。
简单举例:

import { useState, useEffect } from 'react'

const createStore = (initialStore) => {
  let store = initialStore
  const listeners = new Set()

  const dispatch = (newStore) => {
    // Make it like reacts setState so if you pass in a function you can get the store value first
    store = typeof newStore === 'function' ? newStore(store) : newStore
    listeners.forEach(listener => listener(() => store))
  }

  const useStore = () => {
    const [, listener] = useState()
    useEffect(() => {
      listeners.add(listener)
      return () => listeners.delete(listener)
    }, [])
    return store
  }

  return [useStore, dispatch]
}

然后创建一个存储并在组件中使用

const [useStore, dispatch] = createStore(0)

const Display = () => {
  const count = useStore()
  return <div>{count}</div>
}

const addToCount = () => 
  <button onClick={ () => dispatch(count => count + 1}>+</button>

然后,如果你想要一个复杂的商店对象,并且你想避免重新呈现,你可以在dispatch函数中做一个浅层比较来比较商店和新商店,类似于redux所做的。

const shouldUpdate = (a, b) => {
  // (b) needs to be an object or you will have to do checks before looping make sure your store is an object or check it in this function
  for( let key in b ) {
    if(b[key] !== a[key]) return true
  }
  
  return false
}

然后在调度中,你可以在forEach循环中触发监听器之前检查它。

const dispatch = (newStore) => {
  if(!shouldUpdate(
    store, 
    store = typeof newStore === 'function' ? newStore(store) : newstore
  ) return

  listeners.forEach(listener => listener(() => store))
}

它比redux的样板文件少很多,而且看起来更简洁。最棒的是,它允许你将操作与函数分离,而无需将操作附加到任何东西。你可以简单地在应用中的任何地方创建一个商店,并导出useStoredispatch函数。然后,你可以从应用中的任何地方进行调度。

nhhxz33t

nhhxz33t2#

很好的方法,但我仍然认为redux更适合大型应用程序,尤其是在性能方面。使用您的方法的一个示例是,将按钮作为单独的组件添加,同时用React.memo Package 它,并从按钮组件触发actions.toggle(),但按钮重新呈现2次,它不依赖于更改后的状态。
所以在构建大型应用程序时,你总是在通过删除不必要的重新渲染来寻找性能提升,但这里不是这样的。
这是我的分析,谢谢你的工作。
这里是代码showcase

相关问题