使用异步函数在 useImmerReducer 的减速器中获取草稿

use async function to get draft inside reducer of useImmerReducer

我有这个 reducer 函数用于我的应用程序的状态管理。

const initialState = {roles: null};

const reducer = (draft, action) => {
    switch (action.type) {
      case 'initialize':
        //what should i do here????
        return;
      case 'add':
        draft.roles = {...draft.roles, action.role};
        return;
      case 'remove':
        draft.roles = Object.filter(draft.roles, role => role.name != action.role.name);
    }
  };

 const [state, dispatch] = useImmerReducer(reducer, initialState);

为了初始化我的状态,我必须使用异步函数从 asyncStorage 中读取内容(如果存在),必须设置 draft.roles,如果不存在,则应将其设置为默认值。

  const initialize = async () => {
    try {
      let temp = await cache.get();
      if (temp == null) {
        return defaultRoles;
      } else {
        return temp;
      }
    } catch (error) {
      console.log('initialization Error: ', error);
      return defaultRoles;
    }
  };

如何在 'initialize' 案例中获取初始化函数的返回值?如果我使用 initilize().then(value=>draft.roles=value) 我会得到这个错误:

TypeError: Proxy has already been revoked. No more operations are allowed to be performed on it

您不能在 reducer 中使用异步代码。您需要将该逻辑移到减速器本身之外。我正在使用 useEffect 挂钩来触发 initialize,然后将结果发送到状态。

这里有很多语法错误 -- state.roles 应该是 array 还是 object

这是我尝试演示如何执行此操作的尝试。可能您希望将其作为上下文提供程序组件而不是挂钩,但逻辑是相同的。

Javascript:

import { useEffect } from "react";
import { useImmerReducer } from "use-immer";

export const usePersistedReducer = () => {
  const initialState = { roles: [], didInitialize: false };

  const reducer = (draft, action) => {
    switch (action.type) {
      case "initialize":
        // store all roles & flag as initialized
        draft.roles = action.roles;
        draft.didInitialize = true;
        return;
      case "add":
        // add one role to the array
        draft.roles.push(action.role);
        return;
      case "remove":
        // remove role from the array based on name
        draft.roles = draft.roles.filter(
          (role) => role.name !== action.role.name
        );
        return;
    }
  };

  const [state, dispatch] = useImmerReducer(reducer, initialState);

  useEffect(() => {
    const defaultRoles = []; // ?? where does this come from?

   // always returns an array of roles
    const retrieveRoles = async () => {
      try {
        // does this need to be deserialized?
        let temp = await cache.get();
        // do you want to throw an error if null?
        return temp === null ? defaultRoles : temp;
      } catch (error) {
        console.log("initialization Error: ", error);
        return defaultRoles;
      }
    };

    // define the function
    const initialize = async() => {
      // wait for the roles
      const roles = await retrieveRoles();
      // then dispatch
      dispatch({type: 'initialize', roles});
    }

    // execute the function
    initialize();
  }, [dispatch]); // run once on mount - dispatch should not change

  // should use another useEffect to push changes
  useEffect(() => {
    cache.set(state.roles);
  }, [state.roles]); // run whenever roles changes

  // maybe this should be a context provider instead of a hook
  // but this is just an example
  return [state, dispatch];
};

打字稿:

import { Draft } from "immer";
import { useEffect } from "react";
import { useImmerReducer } from "use-immer";

interface Role {
  name: string;
}

interface State {
  roles: Role[];
  didInitialize: boolean;
}

type Action =
  | {
      type: "initialize";
      roles: Role[];
    }
  | {
      type: "add" | "remove";
      role: Role;
    };

// placeholder for the actual
declare const cache: { get(): Role[] | null; set(v: Role[]): void };

export const usePersistedReducer = () => {
  const initialState: State = { roles: [], didInitialize: false };

  const reducer = (draft: Draft<State>, action: Action) => {
    switch (action.type) {
      case "initialize":
        // store all roles & flag as initialized
        draft.roles = action.roles;
        draft.didInitialize = true;
        return;
      case "add":
        // add one role to the array
        draft.roles.push(action.role);
        return;
      case "remove":
        // remove role from the array based on name
        draft.roles = draft.roles.filter(
          (role) => role.name !== action.role.name
        );
        return;
    }
  };

  const [state, dispatch] = useImmerReducer(reducer, initialState);

  useEffect(() => {
    const defaultRoles: Role[] = []; // ?? where does this come from?

   // always returns an array of roles
    const retrieveRoles = async () => {
      try {
        // does this need to be deserialized?
        let temp = await cache.get();
        // do you want to throw an error if null?
        return temp === null ? defaultRoles : temp;
      } catch (error) {
        console.log("initialization Error: ", error);
        return defaultRoles;
      }
    };

    // define the function
    const initialize = async() => {
      // wait for the roles
      const roles = await retrieveRoles();
      // then dispatch
      dispatch({type: 'initialize', roles});
    }

    // execute the function
    initialize();
  }, [dispatch]); // run once on mount - dispatch should not change

  // should use another useEffect to push changes
  useEffect(() => {
    cache.set(state.roles);
  }, [state.roles]); // run whenever roles changes

  // maybe this should be a context provider instead of a hook
  // but this is just an example
  return [state, dispatch];
};