【问题标题】:Best practice for combining React useContext and one-time data load (React hooks)?结合 React useContext 和一次性数据加载(React hooks)的最佳实践?
【发布时间】:2020-01-16 20:48:04
【问题描述】:

我有一个由几个输入组件组成的表单。表单数据通过 React 上下文和 React 钩子 useContext 在这些兄弟组件之间共享和共享。

我对如何选择性地将数据异步加载到同一上下文中感到困惑。例如,如果浏览器 URL 是 example.com/form,那么表单可以使用 FormContext 提供的默认值加载(没问题)。但是,如果用户通过导航到example.com/form/:username/:form-id 返回以完成先前编辑的表单,则应用程序应使用这两个数据点获取数据。大概这必须以某种方式在 FormContext 中发生,以便覆盖默认的空表单初始值。

  1. React.createContext 函数是否可以使用 url 参数?
  2. 如果是这样,当hooks are not to be used with in a conditional时如何处理可选的数据获取要求?
  3. 如何保证数据只取一次?
  4. 最后,表单还将当前状态保存到本地存储,以便在刷新时保持值。如果实现了数据获取,刷新应该加载异步数据还是本地存储?我倾向于本地存储,因为这更有可能代表用户上次体验的内容。我对实施的意见和想法持开放态度。

表单上下文

export const FormContext = React.createContext();
export const FormProvider = props => {
  const defaultFormValues = {
    firstName: "",
    lastName: "",
    whatever: "",
  };
  const [form, setForm] = useLocalStorage(
    "form",
    defaultFormValues
  );
  return (
    <FormContext.Provider value={{ form, setForm }}>
      {props.children}
    </FormContext.Provider>
  );
};

ReferenceuseLocalStorage

【问题讨论】:

  • 如果您使用 React Router,您可以通过 match 属性获取查询参数。这是demo。然后,您可以使用参数来获取您需要的任何数据,将其存储在您的状态中,并有条件地将其传递给您的 Provider 的 value 属性。为了防止重新获取,您可以使用某种形式的 usePrevious 功能,仅在数据发生更改时才获取(希望他们使用钩子使这更容易)。

标签: reactjs react-hooks


【解决方案1】:

我认为您正在寻找的答案是 Redux,而不是库,而是工作流程。我确实觉得很好奇 React 并没有就此提供更多指导。我不确定其他人在做什么,但这是我想出的。

首先,我确保将来自useReducer 的调度添加到上下文中。这是那个界面:

export interface IContextWithDispatch<T> {
  context: T;
  dispatch: Dispatch<IAction>;
}

然后给出这个上下文:

export interface IUserContext {
  username: string;
  email: string;
  password: string;
  isLoggedIn: boolean;
}

我可以这样做:

export const UserContext = createContext<IContextWithDispatch<IUserContext>>({
  context: initialUserContext,
  dispatch: () => {
    return initialUserContext;
  },
});

在我的顶级组件中,我记住了上下文,因为我只想要一个实例。这就是我把它们放在一起的方式

import memoize from 'lodash/memoize';
import {
  IAction,
  IContextWithDispatch,
  initialUserContext,
  IUserContext,
} from './domain';

const getCtx = memoize(
  ([context, dispatch]: [IUserContext, React.Dispatch<IAction>]) =>
    ({ context, dispatch } as IContextWithDispatch<IUserContext>),
);
const UserProvider = ({ children }) => {
  const userContext = getCtx(useReducer(userReducer, initialUserContext)) as IContextWithDispatch<
      IUserContext
      >;
  useEffect(() => {
    // api call to fetch user info
  }, []);
  return <UserContext.Provider value={userContext}>{children}</UserContext.Provider>;
};

您的 userReducer 将响应所有 dispatch 调用,并且可以进行 API 调用或调用其他服务来执行此操作等...reducer 处理对 context 的所有更改。 一个简单的 reducer 可能如下所示:

export default (user, action) => {
  switch (action.type) {
    case 'SAVE_USER':
      return {
        ...user,
        isLoggedIn: true,
        data: action.payload,
      }
    case 'LOGOUT':
      return {
        ...user,
        isLoggedIn: false,
        data: {},
      }
    default:
      return user
  }
}

在我的组件中,我现在可以这样做:

const { context, dispatch } = useContext&lt;IContextWithDispatch&lt;IUserContext&gt;&gt;(UserContext);

其中UserContext 是从上面定义的export 导入的。

在您的情况下,如果您的路线 example.com/form/:username/:form-id 没有它需要的数据,它可以 dispatchaction 并监听该操作的结果的上下文。你的 reducer 可以进行任何必要的 api 调用,你的组件不需要知道任何关于它的信息。

【讨论】:

  • 所以reducer独立于上下文而存在,但是reducerstatedispatch在初始化时被添加到上下文中?你能否再解释一下为什么这里需要记忆化?如果顶层组件封装在 中,你将如何获得多个上下文实例?
  • 这是我几个月前在学习钩子时想出的东西,我还没有在生产中使用它。关于记忆,我试图记住我是否有充分的理由,并且我打算做一些测试,看看它是否真的有必要。正如你所说,我的直觉是没有必要。我在上下文中添加了dispatch,但没有添加state,所以我最终得到了一个IContextWithDispatch 对象,因此可以轻松访问dispatch 函数。
  • 经过进一步思考,记忆化是必要的,因为userContext 必须在它的位置创建,而不是在useEffect 调用内部,因为我在Provider 中使用它。由于功能组件的性质,每个渲染周期都会调用它。我想当我创建这个时,我并没有真正理解 useReducer 处于 state 级别的意图,我目前正在考虑这个。
【解决方案2】:

设法完成了我想要的大部分工作。以下是展示最终产品基本思想的要点:https://gist.github.com/kwhitejr/df3082d2a56a00b7b75365110216b395

很高兴收到反馈!

【讨论】:

    猜你喜欢
    • 2020-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-02
    • 2019-02-19
    • 2020-08-01
    相关资源
    最近更新 更多