结合 React useContext 和一次性数据加载(React hooks)的最佳实践?

Posted

技术标签:

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

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

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

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

表单上下文

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

Reference 为useLocalStorage

【问题讨论】:

如果您使用 React Router,您可以通过 match 属性获取查询参数。这是demo。然后,您可以使用参数来获取您需要的任何数据,将其存储在您的状态中,并有条件地将其传递给您的 Provider 的 value 属性。为了防止重新获取,您可以使用某种形式的 usePrevious 功能,仅在数据发生更改时才获取(希望他们使用钩子使这更容易)。 【参考方案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 级别的意图,我目前正在考虑这个。 这里是我想出的概述:gist.github.com/kwhitejr/df3082d2a56a00b7b75365110216b395【参考方案2】:

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

很高兴收到反馈!

【讨论】:

以上是关于结合 React useContext 和一次性数据加载(React hooks)的最佳实践?的主要内容,如果未能解决你的问题,请参考以下文章

react中 useContext 和useReducer的使用

createContext 和 useContext 结合使用实现方法共享

React.useContext 没有更新

React.useContext 显示为未定义

React.useContext() 不断返回未定义?

在一个 React 组件中使用多个“useContext”