使用异步代码将 React 代码重构为普通 JS 减速器

问题描述 投票:0回答:1

我有一个 React 库,我需要重构它以允许与其他框架一起使用。我需要在 React-less vanilla js 模块中放入尽可能多的逻辑,然后使用精简的 React 包装器以便在 React 项目中方便使用(对于 vue.js 和 svelte 也是如此)。

我想出的最好的解决方案是定义减速器和与此类减速器一起使用的函数,然后反应组件只是委托。

例如,假设我使用 id、firstName、lastName 属性和两个函数 getFirstName 和 getLastName 管理一个状态。假设这两个操作都需要读取当前状态,并且其中一个可以同步执行,而另一个则不能。

这是普通的 js 模块代码:

const initialUserState = {
  id: 1,
  firstName: undefined,
  lastName: undefined
};

const getFirstNameSync = (id) => {
  const userIdFirstNameMap = { 1: "Terry" };
  return userIdFirstNameMap[id];
};

const getLastNameAsync = (id) =>
  new Promise((resolve, reject) => {
    fetch(`https://dummyjson.com/users/${id}`)
      .then((res) => res.json())
      .then((json) => {
        const { lastName } = json;
        resolve(lastName);
      })
      .catch((err) => {
        reject(err);
      });
  });

const userReducer = (state, action) => {
  if (action.type === "getFirstName") {
    const { id } = state;
    const firstName = getFirstNameSync(id);
    return { ...state, firstName };
  }

  if (action.type === "getLastName") {
    const { lastName } = action.payload;
    return { ...state, lastName };
  }

  throw Error("Unknown action");
};

const getFirstName = (userDispatch) => {
  userDispatch({ type: "getFirstName" });
};

const getLastName = (userDispatch, userState) => {
  const { id } = userState;
  getLastNameAsync(id).then((lastName) => {
    userDispatch({ type: "getLastName", payload: { lastName } });
  });
};

export { initialUserState, userReducer, getFirstName, getLastName };

这是反应包装器代码:

import { createContext, useCallback, useReducer } from "react";
import {
  initialUserState,
  userReducer,
  getFirstName as _getFirstName,
  getLastName as _getLastName
} from "./core/user";

const UserContext = createContext();

const UserProvider = ({ children }) => {
  const [userState, userDispatch] = useReducer(userReducer, initialUserState);

  const getFirstName = useCallback(() => {
    _getFirstName(userDispatch);
  }, []);

  const getLastName = useCallback(() => {
    _getLastName(userDispatch, userState);
  }, [userState]);

  return (
    <UserContext.Provider
      value={{
        firstName: userState.firstName,
        lastName: userState.lastName,
        getFirstName,
        getLastName
      }}
    >
      {children}
    </UserContext.Provider>
  );
};

export { UserContext, UserProvider };

最后在使用上下文状态和函数的 React 项目中进行代码:

import "./styles.css";
import { useContext } from "react";
import { UserContext, UserProvider } from "./UserContext";

const UserArea = () => {
  const { firstName, lastName, getFirstName, getLastName } = useContext(
    UserContext
  );
  return (
    <div>
      <h1>
        Hello {firstName || "Anonymous"} {lastName}
      </h1>
      <div>
        <button onClick={getFirstName}>Get First Name</button>
      </div>
      {firstName ? (
        <div>
          <button onClick={getLastName}>Get Last Name</button>
        </div>
      ) : null}
    </div>
  );
};

export default function App() {
  return (
    <UserProvider>
      <div className="App">
        <UserArea />
      </div>
    </UserProvider>
  );
}

对于 getFirstName,我可以委托仅传递调度函数。在reducer函数中,我可以从状态读取id,恢复名字并立即返回。这很好,因为包装器 useCallback 不需要引用状态,因此依赖项数组为空并且函数是稳定的(不会在重新渲染时重新定义)。

第二种情况比较棘手。理想情况下,我会以相同的方式委托并访问减速器函数内的状态:

  if (action.type === "getLastName") {
    const { id } = state;
    getLastNameAsync(id).then((lastName) => {
      return { ...state, lastName };
    });
  }

然而,reducer 的返回无法等待异步调用的返回,因此我必须在调度之前进行调用(包括作为操作负载的响应),并且由于调用取决于状态的 id 属性,包装器函数还需要传递状态,因此状态需要位于 useCallback 依赖项数组中(以便它不会使用陈旧状态进行调用)。这样,useCallback 函数就不稳定,并且每次重新定义该函数时都会更新依赖组件。

您有什么建议来改善这种情况?

请注意,我正在寻找有关隔离逻辑(包括异步代码)的一般建议,以使反应层尽可能简单(不会出现明显的性能问题,例如在每次状态更改时重新定义回调),而不是逐案优化(在这个特定的示例中,我可以将 id 属性放在依赖项数组上,而不是整个状态上)。

请随意提出与我想要实现的目标完全不同的策略(可重用的普通 js 代码,可以包装在薄的无逻辑反应层中)。

javascript reactjs react-hooks architecture code-reuse
1个回答
0
投票

在普通 JavaScript 模块中隔离逻辑并提供精简的 React 包装器的方法是使代码更可重用且与框架无关的好方法。

  1. 您可以创建一个中间件或单独的函数来处理 普通 JavaScript 模块中的异步操作。

在 core/user.js 中

const asyncMiddleware = (dispatch) => (action) => {


if (action.type === "getLastName") {
    const { id } = action.payload;
    getLastNameAsync(id).then((lastName) => {
      dispatch({ type: "setLastName", payload: { lastName } });
    });
  } else {
    dispatch(action);
  }
};

// ...

export { asyncMiddleware };
  1. 创建一个单独的减速器来处理异步操作。

在 core/user.js 中

const asyncReducer = (state, action) => {
  if (action.type === "setLastName") {
    const { lastName } = action.payload;
    return { ...state, lastName };
  }
  return state;
};

// ...

export { asyncReducer };
  1. 您可以使用实用函数将您的减速器组合成一个。

    导出函数combineReducers(reducers) { 返回(状态,动作)=> { 返回reducers.reduce((accState,reducer)=> { 返回减速器(accState,操作); }, 状态); }; }

    从“./utility”导入{combineReducers}; const rootReducer = mergeReducers([userReducer, asyncReducer]); // ... 导出 { rootReducer };

© www.soinside.com 2019 - 2024. All rights reserved.