React.js的编程思想(九)

在我们看来,React 是 JavaScript 构建大型,高性能 Web 应用的首选。在 Facebook 和 Instagram 中都能很好的应用。

React 中许多重要部分之一是思考如何构建应用程序。 在本文档中,我们将引导你完成用 React 构建一个可搜索的产品数据表的思考过程。

从一个线框图开始

试想我们已经有一个 JSON API,和从设计者那里得来的一个线框图 。这个线框图如图所示:


image

我们的 JSON API 返回像这样的一些数据

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

步骤1:将 UI 拆解到组件层次结构中

你想要做的第一件事是在线框图中为每个组件(和子组件)绘制框,并给它们命名。如果你和设计师一起工作, 他们可能已经做了这些工作,所以去和他们交流一下!他们的 Photoshop 图层名称可能最终成为你的 React 组件名称(愚人码头注:意思是可以和设计师交流一下,约定图层命名规范)!

但是你该如何拆分组件呢?其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。

由于你经常向用户显示 JSON 数据模型,你应该发现如果模型构建正确,则对应的 UI(以及组件结构)将很好地映射。这是因为 UI 和数据模型往往遵循相同的信息结构,这意味将UI拆分成组件的工作通常比较琐碎,只需要将其拆分为精确对应数据模型的片段即可。


image

你可以看到,在这个简单的小应用中,我们有 5 个组件。 我们已经对每个组件代表的数据进行了斜体标注。

1.FilterableProductTable (orange): 包含整个示例
2.SearchBar(蓝色): 接收所有的 用户输入
3.ProductTable(绿色): 根据 用户输入 显示和过滤 数据集合
4.ProductCategoryRow(宝石绿): 显示每个 类别 的标题
5.ProductRow(红色): 显示每个 产品 的行数据

仔细观察 ProductTable ,你将会发现表头(包含 “Name” 和 “Price” 标签)不是独立的组件。这是个人偏好问题,当然也存在用其他方式实现的争论。在这个例子中,将其作为 ProductTable 的一部分来处理,因为它是 数据集合 渲染的一部分,是 ProductTable 的职责。当然,如果这个表头变得复杂的时候(比如,如果需要添加排序功能),这时候创建一个独立的 ProductTableHeader 组件更合适。

现在,我们已经在线框图中做了标识,让我们对它们进行层级结构排列。这很简单。在线框图中,出现在其它组件内的组件,应该在层次结构中显示为子组件即可:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

步骤2: 用 React 构建一个静态版本

目前为止你已经有了组件层次结构,现在是时候实现你的 app 了。最简单的方法是构建一个采用数据模型并渲染 UI 但没有交互性的版本。最好解耦这些处理,因为构建静态版本需要 大量的代码 和 少量的思考,而添加交互需要 大量思考 和 少量的代码。我们将看到原因。

要构建你 app 的一个静态版本,用于渲染数据模型, 您将需要构建复用其他组件并使用 props传递数据的组件。props 是将数据从 父级组件 传递到 子级 的一种方式。如果你熟悉 state 的概念,在构建静态版本时不要使用statestate只用于交互,也就是说,数据可以随时被改变。由于这是一个静态版本 app,所以你并不需要使用 state

您可以 自上而下 或 自下而上 构建。也就是说,您可以从构建层次结构中顶端的组件开始(即从 FilterableProductTable 开始),也可以从构建层次结构中底层的组件开始(即 ProductRow )。在更简单的例子中,通常 自上而下 更容易,而在较大的项目中,自下而上,更有利于编写测试。

在这一步结束时,你已经有了一个可重用的组件库,用于渲染你的数据模型。组件将只有 render() 方法,因为这是你应用程序的静态版本。层次结构顶部的组件( FilterableProductTable )应该接收你的数据模型作为 prop 。如果您对基础数据模型进行更改,并再次调用 ReactDOM.render(),UI 将同步更新。这有利于观测UI的更新以及相关的数据变化,因为这中间没有做什么复杂的事情。React 的 单向数据流(也称为 单向绑定 )使所有模块化和高性能。

如果执行这一步,需要帮助,请参阅 React 文档

小插曲: Props(属性) vs State(状态)

在 React 中有两种类型的“模型”数据:props(属性) 和 state(状态)。理解这两者的差异非常重要;如果不确定它们之间的区别,请参阅官方 React 文档

步骤3: 确定 UI state(状态) 的最小(但完整)表示

为了你的 UI 可以交互,你需要能够触发更改底层的数据模型。React 通过 state 使其变得容易。

要正确的构建应用程序,你首先需要考虑你的应用程序需要的可变 state(状态) 的最小集合。这里的关键是:不要重复你自己 (DRY,don’t repeat yourself)。找出你的应用程序所需 state(状态) 的绝对最小表示,并且可以以此计算出你所需的所有其他数据内容。例如,如果你正在构建一个 TODO 列表,只保留一个 TODO 元素数组即可;不需要为元素数量保留一个单独的 state(状态) 变量。相反,当你要渲染 TODO 计数时,只需要获取 TODO 数组的长度即可。

想想我们的示例应用中的所有数据。 我们有:

  • 原始产品列表
  • 用户输入的搜索文本
  • 复选框的值
  • 过滤后的产品列表

让我们仔细分析每一个数据,弄清楚哪一个是 state(状态) 。请简单地提出有关每个数据的 3 个问题:

1.是否通过 props(属性) 从父级传入? 如果是这样,它可能不是 state(状态) 。
2.是否永远不会发生变化? 如果是这样,它可能不是 state(状态)。
3.是否可以由组件中其他的 state(状态) 或 props(属性) 计算得出?如果是这样,则它不是 state(状态)。

原始的产品列表作为 props(属性) 传递,所以它不是 state(状态) 。
搜索文本和复选框似乎是 state(状态) ,因为它们会根据用户的输入发生变化,并且不能从其他数据计算得出。
最后,过滤后的产品列表不是 state(状态) ,因为它可以通过结合 原始产品列表 与 搜索文本 和 复选框的值 计算得出。

所以最终,我们的 state(状态) 是:

  • 用户输入的搜索文本
  • 复选框的值

步骤4:确定 state(状态) 的位置

现在,已经确定了应用所需 state(状态) 的最小集合。接下来,需要确定是哪个组件可变,或者说哪个组件拥有这些 state(状态) 。

记住:React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:

对于你应用中的每一个 state(状态) :

  • 确定每个基于这个 state(状态) 渲染的组件。
  • 找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。即父级组件)。
  • 公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。
  • 如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。

我们在我们的应用中贯穿这个策略:

  • ProductTable 需要基于 state(状态) 过滤产品列表,SearchBar 需要显示 搜索文本和选中状态 state(状态) 。
  • 公共的父级组件是 FilterableProductTable
  • 它从概念上讲适用于过滤文本和复选框选中值应该存在于 FilterableProductTable。

那么我们已经决定 state(状态) 保存在 FilterableProductTable 中。首先,添加一个实例属性 this.state = {filterText: '', inStockOnly: false} 到 FilterableProductTable 的constructor 来反映你应用的初始 state(状态) 。然后,传递 filterText 和 inStockOnly 到 ProductTable 和 SearchBar 作为一个 prop(属性) 。最后,使用这些 props(属性) 来过滤 ProductTable 中的行,并设置 SearchBar 中的表单字段的值。

你可以看一下你应用的行为:设置 filterText 为 "ball" 并刷新你的应用。你将发现数据表被正确的更新。

步骤5:添加反向数据流

目前,构建的应用已经具备了正确渲染 props(属性) 和 state(状态) 沿着层次结构向下传播的功能。现在是时候实现另一种数据流方式:层次结构中深层的 form(表单) 组件需要更新 FilterableProductTable 中的 state(状态) 。

React 中明确的数据流向,使你容易理解程序如何运行。但是相比传统的数据双向绑定来说,的确需要多敲一些代码。

如果你尝试在当前版本中的例子中输入或勾选复选框,你发现 React 会忽略了你的输入。这是有意为之的,因为我们已经设置了 input 的 value prop(属性) 总是等于从 FilterableProductTable 中传递的 state 。

想想我们希望发生什么。我们期望当用户改变表单输入的时候,我们更新 state(状态) 来反映用户的输入。由于组件只能更新它们自己的 state(状态) ,FilterableProductTable 将传递回调到 SearchBar,然后在 state(状态) 被更新的时候触发。我们可以使用 input 的 onChange 事件来接收通知。而且通过 FilterableProductTable 传递的回调调用 setState(),然后应用被更新。

尽管这听起来很复杂,但真的只需要简单的几行代码即可实现。同时清晰的表达数据在应用中的流动。

这部分内容在状态提升那一节有具体的示例。

就这么简单

非常希望,这篇文章能给你一些使用 React 构建组件和应用的想法。有可能这种写法会比你通常的写法多几行代码,但切记阅读代码的重要性远远高于写代码,模块化、结构清晰的代码最利于阅读。当创建一个大组件库的时候,你将感激模块化、结构清晰和可以重用的代码,同时你的代码行数会慢慢减少。

参考:

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

推荐阅读更多精彩内容

  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,705评论 14 128
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,824评论 0 24
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,834评论 1 18
  • 漫步白浪街是我两年前就有了的冲动,早就想尝试一下一脚踏三省的神奇。然而计算一下路程约有500公里之遥,鉴于心...
    客舟听雨2011阅读 233评论 0 2
  • 导读:小海与女朋友恋爱一年多,于是女朋友带小海去见家长,通过聊天,爷爷得知小海不是名牌大学毕业,工资不高,并且社会...
    蓝智论职场阅读 538评论 0 0