反应全局状态没有上下文或 redux?

React global state no context or redux?

我最近玩了以下文章State Management with React Hooks — No Redux or Context API。自 Reacts 成立以来,最受关注的问题始终是状态管理和全局状态。 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>
  )
}

这似乎是一种更简洁、更简单的方法,我想知道为什么这不是 React 状态管理的首选方法。首先,您不必将所有内容都包装在提供程序中。接下来它非常容易实现,并且实际应用程序中涉及的代码要少得多。任何人都可以看到使用这种方法的缺点吗?我唯一能想到的是上下文 api 存在的重新渲染问题,但在小块中这不应该成为问题。

很好的方法,但我仍然认为 redux 对于大型应用程序更好,尤其是在性能方面。使用您的方法的一个示例是将按钮添加为单独的组件,同时用 React.memo 包装它并从按钮组件中触发 actions.toggle() ,但是按钮重新渲染 2 次,它不会在更改后进行中继状态。

因此,在构建大型应用程序时,您总是希望通过删除不必要的重新渲染来提高性能,但这里并非如此。

这是我的分析,感谢您的工作。

这里是代码showcase

我一直在使用类似的方法,我非常喜欢它。我真的不敢相信更多的人不谈论这种方法。我在这里写了一个自定义钩子 React Global Store Hook。它使您可以自由地从应用程序中的任何地方进行调度,并进行浅层比较以避免不必要的重新渲染。只要您可以避免不必要的重新渲染,我就看不到任何性能问题。

总而言之,这是一个简单的概念。您基本上创建了一个函数来存储您的状态和 return 2 个函数。一个是设置存储状态的函数,一个是要在 React 组件中使用的钩子。在钩子中,您获取了 setState 函数,该函数使用 createEffect 对初始渲染做出反应,并将其存储在数组中。然后您可以使用此 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>

然后如果你想避免重新渲染,你可以在调度函数中进行浅比较,将商店与新商店进行比较,类似于 redux 所做的。类似于以下内容:

const shouldUpdate = (a, b) => {
  for( let key in a ) {
    if(a[key] !== b[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 函数。然后您可以从您应用中的任何地方发送。