关于减少 React useState 钩子数量的建议

Advise about the reduction of the amount of React useState hooks

我正在尝试构建我自己的第一个相对较大的项目,至少就我的经验水平而言是这样。 我严重依赖 useContext 结合 useStates 挂钩来处理我的不同组件之间的逻辑,随着时间的推移,跟踪所有这些不同的状态变化和简单的 onClick 事件真的开始变得困难我必须改变逻辑大量的州。

希望得到一些可以引导我朝着正确方向前进的个人建议。 不知何故我所做的感觉不正常,或者这就是 React 的现实? 肯定有更聪明的方法来减少状态逻辑管理的数量?

这是我正在使用的一些代码片段

  const onClick = (note: INote) => {
    SetAddNote(false);
    SetNote(note);
    onSelected(note)
    SetReadOnly(true);
    SetEditor(note.data.value);
    SetInputValue(note.data.name);
    SetCategory(note.data.category);
  };
  const { note, noteDispatch, SetNoteDispatch } = useContext(NoteContext);
  const { categories } = useContext(CategoriesContext);
  const [ editMode, setEditMode ] = useState(false);
  const [ module, setModule ] = useState<{}>(modulesReadOnly)
  const [inputValue, setInputValue] = useState<string>('');
  const [category, setCategory] = useState('');
  const [color, setColor] = useState('');
import React, { createContext, useState } from 'react';


type EditorContextType = {
  editor: string;
  SetEditor: React.Dispatch<React.SetStateAction<string>>;
  readOnly: boolean;
  SetReadOnly: React.Dispatch<React.SetStateAction<boolean>>;
  inputValue: string;
  SetInputValue: React.Dispatch<React.SetStateAction<string>>;
  category: string;
  SetCategory: React.Dispatch<React.SetStateAction<string>>;
};

type EditorContextProviderProps = {
  children: React.ReactNode;
};

export const EditorContext = createContext({} as EditorContextType);

export const EditorContextProvider = ({
  children,
}: EditorContextProviderProps) => {
  const [editor, SetEditor] = useState('');
  const [readOnly, SetReadOnly] = useState(false)
  const [inputValue, SetInputValue] = useState('');
  const [category, SetCategory] = useState('');
  return (
    <EditorContext.Provider value={{ editor, SetEditor, readOnly, SetReadOnly, inputValue, SetInputValue, category, SetCategory  }}>
      {children}
    </EditorContext.Provider>
  );
};

当然,我可以削减几个状态并将它们合并为一个,但看起来这会变得比现在更复杂。

我正在阅读有关 useReducer 钩子的信息,但是很难掌握它背后的整个想法,并且不太确定在这种情况下它是否真的对我有帮助。 鉴于我继续以这种方式工作,我觉得我正在为失败做好准备,但我没有看到任何更好的实施方案

我也在做大项目,正如你在问题中所说,Reducer 会帮助你解决问题,但是你需要谨慎地构建和管理你的状态,所以这个想法如何你管理一个状态,所以在我回答之前,我会写一些重要的注释:

  1. 确保尽可能减少嵌套上下文,仅在需要时构建上下文并使用上下文,这将优化您的工作
  2. 对于处理或合并状态,您可以使用对象、数组和普通变量,但请记住,尽量防止对象的嵌套级别,以在状态更改时保持状态更新。
  3. 使用 reducer 来处理状态更新会给你一个很好的能力
  4. 我们可以做一些技巧来提高性能,比如在 reducer 中设置条件,它会检查旧状态和新状态。

记住,真的很容易上手,但是第一次上手很难...

现在让我们从一个真实的项目示例开始:

// create VirtualClass context
export const JitsiContext = React.createContext();

// General Action
const SET_IS_SHARED_SCREEN = 'SET_IS_SHARED_SCREEN';
const SET_ERROR_TRACK_FOR_DEVICE = 'SET_ERROR_TRACK_FOR_DEVICE';
const UPDATE_PARTICIPANTS_INFO = 'UPDATE_PARTICIPANTS_INFO';
const UPDATE_LOCAL_PARTICIPANTS_INFO = 'UPDATE_LOCAL_PARTICIPANTS_INFO';

// Initial VirtualClass Data
const initialState = {
  errorTrackForDevice: 0,
  participantsInfo: [],
  remoteParticipantsInfo: [],
  localParticipantInfo: {}
};

// Global Reducer for handling state
const Reducer = (jitsiState = initialState, action) => {
  switch (action.type) {
    case UPDATE_PARTICIPANTS_INFO:// Update particpants info and remote list
      if (arraysAreEqual(action.payload, jitsiState.remoteParticipantsInfo)) {
        return jitsiState;
      }

      return {
        ...jitsiState,
        participantsInfo: [jitsiState.localParticipantInfo, ...action.payload],
        remoteParticipantsInfo: action.payload,
      };
    case UPDATE_LOCAL_PARTICIPANTS_INFO:// Update particpants info and local one
      if (JSON.stringify(action.payload) === JSON.stringify(jitsiState.localParticipantInfo)) {
        return jitsiState;
      }

      return {
        ...jitsiState,
        localParticipantInfo: action.payload,
        participantsInfo: [action.payload, ...jitsiState.remoteParticipantsInfo],
      };
    case SET_IS_SHARED_SCREEN:
      if (action.payload === jitsiState.isSharedScreen) {
        return jitsiState;
      }

      return {
        ...jitsiState,
        isSharedScreen: action.payload,
      };
    default:
      throw new Error(`action: ${action.type} not supported in VirtualClass Context`);
  }
};


const JitsiProvider = ({children}) => {
  const [jitsiState, dispatch] = useReducer(Reducer, initialState);
  
  // Update shared screen flag
  const setIsSharedScreen = useCallback((flag) => {
    dispatch({type: SET_IS_SHARED_SCREEN, payload: flag})
  }, []);

  // Update list of erros
  const setErrorTrackForDevice = useCallback((value) => {
    dispatch({type: SET_ERROR_TRACK_FOR_DEVICE, payload: value})
  }, []);
  
  // Local Participant info
  const updateLocalParticipantsInfo = useCallback((value) => {
    dispatch({type: UPDATE_LOCAL_PARTICIPANTS_INFO, payload: value})
  }, []);
  
  const updateParticipantsInfo = useCallback(async (room, currentUserId = null) => {
    if (!room.current) {
      return;
    }

    // get current paricipants in room
    let payloads = await room.current.getParticipants();
    // ... some logic
    let finalResult = payloads.filter(n => n).sort((a, b) => (b.startedAt - a.startedAt));
    dispatch({type: UPDATE_PARTICIPANTS_INFO, payload: finalResult})
  }, []);

  const contextValue = useMemo(() => {
    return {
      jitsiState,
      setIsSharedScreen,
      setErrorTrackForDevice,
      updateParticipantsInfo,
      updateLocalParticipantsInfo,
    };
  }, [jitsiState]);

  return (
    <JitsiContext.Provider
      value={contextValue}
    >
      {children}
    </JitsiContext.Provider>
  );
};

export default JitsiProvider;

这个例子允许你更新状态,而且你有不止一种情况,所有的状态值都由jitsiState共享,所以你可以得到任何你想要的数据,关于功能,你可以直接使用dispatch!但根据我们的经验,我们构建了一个回调方法并通过提供者发送它,这让我们很难在一个地方控制代码和逻辑,并使过程非常简单,所以当点击每个地方时,我们只调用所需的方法...

您还将看到条件和 useMemo...这些是为了防止渲染不需要的触发器,例如更改内存中的键而不是实际值等等...

终于在我们使用它之后,我们现在控制所有组件之间的所有状态太容易了,除了包装器上下文我们没有嵌套上下文。

注意:当然,您可以根据您的逻辑或需要的概念跳过或更改此代码库。

注意 2:此代码已整理并做了一些更改以使其易于阅读或理解...

注3:可以忽略所有传入provider的函数,直接使用dispatch,但是在我的项目中,我发送了一个类似这个例子的函数。