高阶函数一点通


理解快速变化的 React 最佳实践

image.png

如果你刚开始接触 React,你可能已经听说过 “高阶组件” 和 “容器” 组件。你也许会奇怪这都什么鬼东西。或者你已经开始使用库提供的 API 了,但对于这些个术语还有些疑惑。

作为 Apollo 的 React 集成 - 一个重度使用高阶组件的热门开源库 - 的维护者和文档作者,我花了些时间来理清这些概念。

我希望这篇文章能够帮你对这一主题有更进一步的了解。

重识 React

本文假定你已对 React 有一定的了解 - 如果没有的话有很多资料可供查阅。例如 Sacha Greif 的 React 5 大概念 就是很好的入门文章。但是,让我们再回顾一下然后继续我们的文章。

一个 React 应用包含一系列 组件。组件中会传递一组输入属性(props),并且输出屏幕渲染的 HTML 片段。当一个组件的 props 更新时,会触发组件重绘,HTML 也会相应变化。

当用户通过一种事件(例如鼠标点击)与 HTML 进行交互时,组件处理事件要么通过触发 回调 prop,要么通过更新内部 state。更新内部 state 也会造成组件自身及其子组件的重绘。

这里就不得不提组件 生命周期,即组件首次渲染,绑定 DOM,传递新 props 等。

组件的渲染函数返回一个或多个其他组件的实例。合成 视图树 是一个好的思维模型,能够表明应用内的组件是如何交互的。通常,组件交互是通过传递 props 给子组件实现的,或者通过触发父组件传递来的回调函数实现。

image.png

React 视图树中的数据流

React UI vs 无状态

似乎现在已经过时,但曾经一切都区分为 Model,View 和 Controller(或者 View Model,或者 Presenter)来描述。在这种分类方式,View 的任务就是 渲染 并且处理用户交互,Controller 的任务则是 准备数据

React 最近的趋势是实现 无状态函数组件。这些简单的“纯”组件只根据自身的 props 转换成 HTML 和调用回调 props 来响应用户交互:

image.png

他们是函数式的,你甚至可以就把他们当做函数。如果你的视图树包含“纯”组件,你可以把整棵树看成一个由许多小函数组成的输出 HTML 的大型函数。

无状态函数式组件有个很好的特点是极容易测试,并且易于理解。即易于开发和快速 debug。

但是你不能一直逃避的是,UI 需要状态。比如,当用户滑过菜单时,要自动打开(我希望是不要啦!)- 在 React 是利用 state 来实现的。要用 state,你就要用基于 class 的组件。

把 UI 的 “全局 state” 引入视图树就是事情复杂的开始。

全局 State

UI 的 全局 state 不能直接独立和某个独立组件相联系。典型地,这一般包含了两类事情:

  1. 应用的 数据 从 server 来。通常,数据用于多处,所以并不唯一关联某个组件。

  2. 全局 UI state,(像 URL,决定了用户浏览的页面路径)。

安置全局 state 的一个方法是应用内绑定最高层的 “根” 组件,并且下发到各个需要它的子组件中去。然后 state 的改变再通过一连串的回调反馈到顶层。

image.png

单容器从 store 到视图树的数据流。

这一方法即使快但很笨拙。根组件需要理解全树的需求,每个子树的父组件同样需要理解每个子树的需求。此时引入另一个概念。

容器和展示类组件

这个问题通常通过允许任何层级组件都能获取全局 state 的方式来解决(要求有一些限制)。

在 React 的世界里,组件可以分为能拿到全局 state 的和不能拿到的。

“纯”组件易于测试和理解(尤其是无状态函数式组件)。一旦一个组件是“不纯”的,它就被污染了,并且很难处理。

因此,出现了一个 pattern 把“不纯”的组件拆分成 两个 组件:

  • 容器 组件操作“脏”全局 state
  • 展示 组件相反

我们只要像对待上面的一般组件一样对待展示类组件,但把脏的和复杂数据操作类的工作独立到容器组件里。

image.png

多容器的数据流

容器

一旦你开始区分展示类/容器类组件,编写容器组件会变得有趣。

有件事要注意的是容器类组件有时候不像个组件。它们可能:

  • 获取并传递一个全局 state(可以是 Redux)片段到子组件。
  • 运行一个数据访问(可以是 GraphQL)请求,然后把结果传给子组件。

当然,如果我们遵循好的拆分原则,容器 只挂载单个子组件。容器和子组件强绑定,因为子组件天生在 render 方法里。不是么?

容器归纳

对于容器组件的 众多类型 来说(例如,某个容器组件访问的是 Redux store),实现基本相同,不同在于细节:渲染的子组件的不同,获取数据的不同。

举个栗子,在 Redux 的世界里,容器可能是这样的:

image.png

虽然这个容器很多功能不像真的 Redux 容器,你可以看到除了 mapStateToProps 的实现和我们包装的特定 MyComponent每次写访问 Redux 的容器,我们还要写很多模板代码。

生成容器

事实上,写一个自动 生成 容器组件的方法会更容易,这个方法基于相关信息(此例中是子组件和 mapStateToProps 函数)。

image.png

这是一个 高阶组件(HOC),是以子组件和其他选项作为参数,为该子组件构造容器的函数。

“高阶”即“高阶函数” - 构造函数的函数,事实上,可以认为 React 组件是产出 UI 的组件。尤其在无状态函数式组件中,这一方法尤其实用,但是仔细想想,它在纯状态展示组件中也同样实用。HOC 其实就是高阶函数。

HOC 例子

这里有些值得一看的例子:

  • 最普遍的可能是 Reduxconnect 函数了,上述的 buildReduxContainer 函数就是一个简陋版 connect 函数。
  • React RouterwithRouter 函数,它从上下文中抓取路由并作为 props 传入子组件。
  • [react-apollo](http://dev.apollodata.com/react/) 主要的接口就是 graphql HOC,给定一个组件和一个 GraphQL 请求,即为子组件提供请求的返回结果。
  • Recompose 是一个全是 HOC 的库,它能执行一系列任何你想从组件中抽取出来的不同的子任务。

自定义 HOC

应该为你的应用编写新的 HOC 吗?当然了,如果你有组件的模板要生成的话更应该这么做。

以上简单分享了有用的库和简单的组成方式,HOC 是 React 组件中共享行为的最佳方式。

编写 HOC 是一个函数返回类的简单方法,像我们在上面看到的 buildReduxContainer 方法。如果你想了解通过构建 HOC 你能做些什么,我建议你阅读 Fran Guijarro 关于这一主题的 极度全面的博客

结论

高阶组件在本质上是一种以 函数式 的方式分离组件中的关注点的编码方式。React 早期版本用 class 和 mixin 来重用代码,但所有迹象表明更函数式的方法才是 React 的未来。

如果当你听说函数式编程技术时呆住了,不要紧!React 团队致力于简化这些方法,让我们所有人都能写出模块化,组件化的 UI。

如果你想获取更多关于构建现代、组件化应用的信息,查阅我在 Chroma 上的 系列博客。如果你喜欢这篇文章,请点赞💚 并分享出去哦~


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容