ReasonReact 上下文在实际应用中得到解释
Contextinreact旨在用于在组件树不同层级的组件之间共享一些全局数据。它允许避免将数据props逐级传递到各个组件(“属性钻取”),同时还能在 in 的值context发生变化时更新这些组件。
值得注意的是,由于查找上下文值订阅者的方式可能会对性能产生影响,因此建议将其用于context低频更新(引用 Sebastian Markbåge 的话react) 。这个话题需要单独撰写一篇文章(甚至一本书?),因此我在这里不做赘述,而是重点介绍一个context在react应用程序中使用ReasonML.
我们正在建造什么?
我们将构建一个登录/登出功能,其中会存储用户信息context,以便我们可以从应用程序的任何位置访问这些信息,并根据用户是否匿名浏览进行自定义设置。本文中的源代码位于此仓库中,包含此功能的迷你应用程序链接在此。
要获得 React 上下文带来的好处和所有便利,尤其是在强类型环境中ReasonML,需要将许多零碎的部分连接在一起,但这绝对是值得的。
我将逐步讲解如何将所有内容连接起来,最终我们会得到一个简单的钩子,它允许从上下文中读取用户数据,并分发操作以从任何组件更新它,如下所示:
let (user, dispatch) = UserContext.useUser();
let handleLogIn = () => dispatch(UserLoggedIn(userName));
switch (user) {
| Anonymous => /** display login form */
| LoggedIn(userName) => /** say hi to the user! */
};
向下滚动查看详情👇
创建提供者和上下文
我们将从以下步骤开始:
- 创建上下文,
- 创建提供程序组件,
- 创建可重用的钩子以访问上下文值。
我们需要知道使用我们应用的用户是匿名用户还是已登录用户,以及哪些操作会改变这一点,所以让我们先从几种类型开始:
/** Types.re */
type user =
| Anonymous
| LoggedIn(string);
type userAction =
| UserLoggedIn(string)
| UserLoggedOut;
LoggedIn它将存储用户名,但也可以是包含更多用户数据的任何其他类型。我们将userAction在为用户状态实现 reducer 时使用它。
现在让我们在文件中创建上下文和可重用的钩子来访问上下文值UserContext.re:
/** initial value is Anonymous */
let context = React.createContext(Anonymous);
/** hook to easily access context value */
let useUser = () => React.useContext(context);
这与你在 JS 中实现的方式非常相似。现在让我们在一个文件中创建上下文提供程序。UserProvider.re
/** UserProvider.re */
let make = React.Context.provider(UserContext.context);
/** Tell bucklescript how to translate props into JS */
let makeProps = (~value, ~children, ()) => {
"value": value,
"children": children,
};
这是makeProps做什么用的?为什么我们不能直接用[@react.component]`and`创建一个普通的组件make?这个问题我反复问自己很多次,直到我感到厌烦,才深入研究并找到了答案🤦♀️🙃
props还记得我们在组件中总是使用命名参数吗reason,比如 ` ~ida` 或 `b` ~className?JavaScript 没有这样的特性,所有常规的 JavaScript 组件都只能将 `a` 视为props一个对象。那么,它是如何编译成有效的reactJavaScript 组件的呢?
这就是该属性[@react.component]的作用。它会生成一个名为 ` makePropsget_ props...
React.Context.provider它已经生成了一个 React 组件,并将其用作propsJS 对象,但我们希望将其用作reason带有命名参数的组件。因此,我们需要makeProps手动创建它,它会告诉 BuckleScript 如何将我们的命名参数转换为 JS 对象,供propsJS 组件使用。为了创建一个可以编译成 JS 对象的对象,我们使用了 BuckleScriptObject 2绑定,如下所示:
{
"value": value,
"children": children,
}
所以我们基本上是在做这项工作[@react.component],但幸运的是工作量并不大,因为提供者只需要一个值和子项😅。
现在我们可以像这样使用我们的提供者组件,<UserProvider...>因为我们遵循了在文件中设置convention两个函数的做法。makemakePropsUserProvider
更新上下文中的值
现在,我们想使用我们的Provider组件并向其提供用户信息,以便在用户登录或注销时更新该信息。
这里需要理解的关键是,如果我们想要更新某个值context并将更新传递给订阅组件,那么该值必须存在于某个组件的状态中。该组件需要使用自身状态中的值来渲染提供组件。
我们称之为Root组件:
/** Root.re */
type state = {user};
/** user and userAction defined in Types.re */
let reducer = (_, action) =>
switch (action) {
| UserLoggedIn(userName) => {user: LoggedIn(userName)}
| UserLoggedOut => {user: Anonymous}
};
[@react.component]
let make = () => {
let (state, dispatch) = React.useReducer(reducer, {user: Anonymous});
<UserProvider value=state.user>
<Page />
</UserProvider>;
};
太好了,现在只要上下文中的值发生变化,使用该值的组件useUser就会更新为新值!等等,这个值实际上从未改变过……哦不!😯
让我们赋予组件通过上下文更新用户数据的能力。我们可以将更新函数作为参数传递下去props,但这又回到了属性传递的方式,不过更有趣的方法是将dispatch其直接包含在上下文值中。
在上下文中传递调度
让我们将我们的dispatch值user作为上下文值传递。已知它dispatch接受userAction并返回unit,我们可以修改上下文值的类型UserContext.re:
/** UserContext.re */
type dispatch = userAction => unit;
type contextValue = (user, dispatch);
let initValue: contextValue = (Anonymous, _ => ignore());
/** no changes when creating context */
以及根组件:
/** Root.re */
let make = () => {
let (state, dispatch) = React.useReducer(reducer, {user: Anonymous});
<UserProvider value=(state.user, dispatch)>
<Page />
</UserProvider>;
}
通过钩子使用上下文值
现在,我要兑现一开始承诺的奖励——一个简单易用且方便的钩子。我在这里再重复一遍,因为它真的很棒:
let (user, dispatch) = UserContext.useUser();
let handleLogIn = () => dispatch(UserLoggedIn(userName));
switch (user) {
| Anonymous => /** display login form */
| LoggedIn(userName) => /** say hi to the user! */
};
额外内容:优化技巧
更新上下文值会导致已订阅的组件重新渲染。在某些情况下,如果我们知道额外的重新渲染不会对 UI 带来任何更新,则可能希望避免这些重新渲染。例如,如果一个组件只需要通过 `getUser()` 更新用户dispatch,它就不会关心实际用户数据的任何更新,但如果用户数据更新,它仍然会重新渲染。
这可以通过将该函数放在一个独立的上下文中来解决dispatch,该上下文不会更新,因为dispatch它保证了稳定性。另一个上下文将存储用户数据,并更新依赖于这些数据的组件。
当Root组件自身更新时(props例如,如果它被更新),它会重新创建(user, dispatch)传入的上下文元组,并导致订阅的组件也随之更新。可以通过useMemo在上下文值周围添加一些机制来解决这个问题,使其更加稳定。
我们现在已经完成了在应用程序中使用上下文来存储和更新少量全局数据所需的一切设置reason-react。我们也研究了一些底层机制,例如组件的编译方式。contextreactreason-react
我是否遗漏了什么或者犯了错误?请在评论区告诉我。或者简单地分享一下您context在应用中的使用体验!💬