结合 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<IContextWithDispatch<IUserContext>>(UserContext);
其中UserContext
是从上面定义的export
导入的。
在您的情况下,如果您的路线 example.com/form/:username/:form-id
没有它需要的数据,它可以 dispatch
和 action
并监听该操作的结果的上下文。你的 reducer 可以进行任何必要的 api 调用,你的组件不需要知道任何关于它的信息。
【讨论】:
所以reducer独立于上下文而存在,但是reducerstate
和dispatch
在初始化时被添加到上下文中?你能否再解释一下为什么这里需要记忆化?如果顶层组件封装在 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的使用