繁体   English   中英

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

[英]React global state no context or redux?

我最近玩过以下文章State Management with React Hooks — No Redux 或上下文 API 自 reacts 开始以来,最受关注的问题始终是 state 管理和全局 state。 Redux 一直是流行的选择,最近则是 API。 但这种方法似乎更容易,代码更少,可扩展性更强。

我的问题是任何人都可以看到使用我可能忽略的这种类型的 state 管理方法的缺点。 我对代码进行了一些调整以支持 SSR,它可以在 Nextjs 中运行,并且使用操作和 state 变量的设置更加友好。

使用GlobalState.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;

然后为 state 变量设置一个自定义挂钩,该变量可以是一个简单的字符串,也可以是一个 object,这是一个简单的:

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>
  )
}

这一切似乎是一种更清洁、更容易的方法,我想知道为什么这不是 go 用于 state 管理的方法。 首先,您不必将所有内容都包装在提供程序中。 其次,它非常容易实现,实际应用程序中涉及的代码要少得多。 任何人都可以看到使用这种方法的缺点。 我唯一能想到的是上下文 api 具有的重新渲染问题,但在小块中这应该不是问题。

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

总而言之,这是一个简单的概念。 您基本上创建了一个 function 来存储您的 state 并返回 2 个函数。 一个是 function 来设置存储的 state 另一个是在反应组件中使用的钩子。 在钩子中,您使用 createEffect 获取对初始渲染做出反应的 setState function 并将其存储在数组中。 然后,您可以使用此 setState function 重新渲染您的组件。 因此,当您调用调度 function 时,您可以遍历这些 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>

然后,如果您想避免重新渲染,您可以在调度 function 中进行浅比较,以将商店与新商店进行比较,类似于 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函数。 然后,您可以从应用程序中的任何位置进行调度。

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

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

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

这里是代码展示

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM