[译]Thinking in React
编者按
使用React的思想来构建应用对我在实际项目中以及帮助他人解决实际问题时起到了很大作用,所以我翻译此文来向那些正在或即将陷入React或React-Native深坑的同胞们表示慰问。网上已经有人翻译过,我想用更易读的语言翻译一次,这也是我首次如此一本正经的翻译技术文章给大众阅读,权当练习吧。
原文地址:https://facebook.github.io/react/docs/thinking-in-react.html
转载还请注明出处以及原文地址,出于对作者和译者劳动成果的尊重吧,谢谢了我的哥。
Thinking in React
作者:Pete Hunt 译者:Rex Rao (sohobloo)
我认为React是使用JavaScript构建高性能大型Web应用的首选方案,我们已经在Facebook和Instagram中广泛使用,哎哟,效果不错哟。
React的众多优势之一是——且看它如何让你能顺着思路构建应用。在此,我将引领你用React逐步构建出一个可搜索的商品列表应用。
从模型图开始
假设设计师已经为我们提供了API并可以返回模拟的JSON数据。容我小小鄙视一下这位美工,因为原型图长成这个挫样:

我们的API返回的模拟JSON数据长这样:
[
{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"}
];
第一步:拆分并构建界面的组件层次结构树
你应该做的第一件事是为你原型图所有组件和子组件画个边框、起个名。要是你跟设计师坐一起,找他们喝喝茶,说不定他们的Photoshop图层名恰巧可以用作你React组件的名字!(译者:我只能说,Too young too simple, sometimes naive!)
但你怎么知道如何拆分一个组件呢?这和你平时决定是不是要新建一个函数或者类的道理一样一样的。其中有个叫做单一职责原则的原理,也就是说理想状态下一个组件只做一件事,当他需要做更多,那就应该继续拆拆拆。
如果你经常向用户展示JSON数据,你会发现只要你的数据模型建得好,你的界面乃至你的组件架构也会完美的与之映射。因为界面和数据模型倾向于支持相同的信息架构,这让界面拆分工作变简单了,拆分出的一个组件只对应展示数据模型中的一种数据就行。

你看,咱这简单的应用有5种组件。我用斜体标示出了每个组件要展示的数据。
FilterableProductTable(橙色): 包含整个示例SearchBar(蓝色): 接收用户输入(user input)ProductTable(绿色): 显示基于用户输入(user input)过滤的数据集 (data collection)ProductCategoryRow(青色): 显示分类( category)头ProductRow(红色): 显示每一行商品(product)
看ProductTable你会发现表头(含"Name"和"Price"标签)并没有拆分成组件,这是出于一种存在争议的个人喜好而已啦。这个例子中,既然渲染数据集 (data collection)是ProductTable的职责,那就让它作为此组件的一部分好了。要是它再复杂一点的话(比如排序功能),那就另当别论独立成ProductTableHeader组件咯。
让我们把从原型图中定义的组件组合成层次结构树。如果一个组件出现在另一个组件中,那么这个组件就是它的子组件,so easy:
FilterableProductTableSearchBarProductTableProductCategoryRowProductRow
第二步:用React做个静态版
var ProductCategoryRow = React.createClass({
render: function() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
});
var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ?
this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
});
var ProductTable = React.createClass({
render: function() {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
});
var SearchBar = React.createClass({
render: function() {
return (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" />
{' '}
Only show products in stock
</p>
</form>
);
}
});
var FilterableProductTable = React.createClass({
render: function() {
return (
<div>
<SearchBar />
<ProductTable products={this.props.products} />
</div>
);
}
});
var PRODUCTS = [
{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'}
];
ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
有了组件层次结构,是时候表演真正的技术了实现你的应用了。最简单的方式是把数据渲染到界面上,但是不带交互功能。最好是分离这些步骤,因为构建一个静态版本更多是需要你敲键盘而增加交互功能就需要你敲脑袋了。
将你的应用构建出一个静态版本来展示数据模型,你也许会需要构建组件来复用其他组件,用属性(props)传入数据。 属性(props)是一种将数据从父组件传入子组件的途径。 即便你对状态(state)模式非常熟悉,在静态版本中也不要使用状态哦。状态是留给交互来处理那些会变化的数据使用的。作为一个静态版请无视之。
你可以用自上而下或自下而上的方式构建应用。既可以从最顶层组件开始(比如从FilterableProductTable开始)也可以从最底层组件开始(如ProductRow)。在简单的示例中,自上而下往往更容易;而在大型项目中,使用自下而上更好,你还能方便的写单元测试呢!
这一步完成之后,你就有了一个可以展示你的数据模型的组件库。作为一个静态版本,每个组件都只有一个 render()方法。顶层组件(FilterableProductTable)通过属性(prop)获得你的数据模型。如果此时你改变你的基础数据模型并再次调用ReactDOM.render(),界面会刷新。界面的刷新和变化了一目了然直接了当。React的单向数据流(又名单向绑定)让所有事情有序且快速。
如果在这一步中遇到问题,你可以参考React文档。
小插曲儿: 属性(props)与状态(state)
React中有两类数据模型:属性和状态,了解他们的区别是很有必要的!还不太清楚?来来来看这里React官方文档咋说的。
第三步:定义最小(完整)的界面状态值
界面想要动起来?数据必须变起来!React使用状态(state)来实现。
若想正确构建你的应用,首先你得考虑你的应用至少需要一组什么样的可变状态值。来跟我念口诀:取其精华,去其糟粕。找出你的应用的那组干货——绝对最小化的界面状态值组,并且其他任何需要都可以通这组值计算得出。比如你要构建一个待办列表,只需要维护一组待办项即可;你不需要再维护这组列表的个数的值,因为在你需要展示待办数时可以直接获取列表长度得到结果。
来看看我们例子里有哪些数据:
- 原始的商品列表
- 用户输入的搜索文本
- 勾选框的值
- 筛选后的商品列表
让我们逐条看看哪些是状态,对每一条数据三省吾身:
- 是否是父组件传入的属性?如果是的,估计不是状态。
- 是否会随时间变化改变?如果不会变,估计不是状态。
- 能否从其他状态或属性计算得到?如果可以,肯定不是状态。
原始商品列表通过属性传进来,因此它不是状态。搜索框的值和勾选框的值可以改变而且其他东西也计算不出来这些值,看上去应该是状态。最后,筛选后的商品列表也不是状态,因为它可以通过原始商品列表、搜索框的值和勾选框的值计算得出。
最后得出我们需要的状态:
- 用户输入的输入框的值
- 勾选框的值
第四步:给状态找个家
最小状态集新鲜出炉,接下来我们需要定义哪些组件会变化,或者说拥有这些状态。
记住了: React数据总是单向且「下流」的——流向组件层次中的底层。可能并不是一开始就看得出哪个组件拥有什么状态。这常常是萌新最难理解的部分,所以就让老司机带带你吧:
对于你应用的每一条状态:
- 找出每一个需要基于此状态来渲染界面的组件。
- 找到它们共同的爹(一个在组件层次中需要此状态的所有控件的顶层父组件)。
- 它们共同的父组件或更高层级的组件都可以作为状态的持有者。
- 如果你觉得哪个组件持有这个状态都很别扭,可以为了这个状态创造一个新的组件来持有,并把这个新组件加到它们共同父组件的上层结构中的任何合适位置。
针对我们的应用,让我们根据以上策略捋一捋:
ProductTable需要根据状态值来过滤商品列表,SearchBar需要显示搜索文本和勾选框状态值。FilterableProductTable是它们的共同父组件。- 看起来搜索文本和勾选框值放在
FilterableProductTable挺合适。
就这么愉快的决定了,把这些状态放FilterableProductTable里吧。首先在FilterableProductTable中增加getInitialState()(译者:ES6中如果用class构建组件,初始化状态的方法将发生改变)方法并返回{filterText: '', inStockOnly: false}来对应应用的初始状态。然后将filterText和inStockOnly作为属性传给ProductTable和SearchBar。最后就用属性来过滤ProductTable中的商品列表并把搜索文本设置到SearchBar的输入框中。
来看看你应用的表现如何:把filterText设置成"ball"然后刷新。厉害了我的哥,列表正确的更新了!
第五步:增加反向数据流
至此,我们已经构建了一个能正确渲染属性和状态从组件层次自上而下传递的应用了。是时候表演真正的技术了支持数据反向传递了:底层组件需要更新FilterableProductTable里的状态。
React明确的数据传递能让你更容易搞清楚你的程序是怎么运作的,但比起传统的双向数据绑定你就需要敲稍微多一点的代码了。 React提供了一个叫ReactLink的插件来让这种模式变得和双向绑定一样方便,但本文的目的在于让一切明晰,暂不使用。
如果你尝试在当前版本的示例中输入或勾选,你会发现React完全无视你的输入。 怎么回事难道有Bug?乖乖我们故意的!因为我们刚才把input的value属性设置成总是等于FilterableProductTable传进来的状态了。
然并卵,我们需要用户的输入立刻更新状态。既然控件只允许更新自己的状态,FilterableProductTable可以传一个每次状态需要发生变化时都会触发的回调函数回传到SearchBar。我们可以用输入框的onChange事件来触发并在FilterableProductTable传入的回调函数中调用setState()来更新状态。
看上去好像很复杂的样子,其实只是多了几行代码而已,但这真真真的让你能看清数据是如何在你应用的身体里流来流去的。
没错就是这样
希望这篇文章能在你用React构建组件或应用时给你点亮一盏明灯。虽然可能比以前要搬更多砖,但请你记住代码写出来是要可以给人阅读的,特别是那些标准统一、逻辑清晰的代码更赏心悦目。当你开始构建大型的控件库的时候,你会感激这种规则化、清晰化的风格,再加上代码的复用,你的代码行数会得到缩减。☺
[译]Thinking in React的更多相关文章
- 【译】在React中实现条件渲染的7种方法
原文地址:https://scotch.io/tutorials/7-ways-to-implement-conditional-rendering-in-react-applications 借助R ...
- 【译】用 React 和 D3 创建图表
本文翻译自:https://dzone.com/articles/charts-with-modern-react-and-d3 本文将介绍如何利用 D3JS 和 ReactJS 来创建基础图表. R ...
- 没有用到React,为什么我需要import引入React?
没有用到React,为什么我需要import引入React? 本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖. 所以 ...
- React 引入import React 原理
本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖. 所以我们如果使用了JSX,我们其实就是在使用React,所以我们就 ...
- react实战系列 —— react 的第一个组件
react 的第一个组件 写了 react 有一个半月,现在又有半个月没写了,感觉对其仍旧比较陌生. 本文分两部分,首先聊一下 react 的相关概念,然后不使用任何语法糖(包括 jsx)或可能隐藏底 ...
- babel如此简单
凡是看到这个标题点进来的同学,相信对babel都有了一定的了解.babel使用起来很简单,简单到都没有必要写一篇文章去介绍,直接看看官方文档就可以.所以我也在怀疑到底该不该写这篇文章.想来想去还是决定 ...
- babel从入门到入门
babel从入门到入门 来源 http://www.cnblogs.com/gg1234/p/7168750.html 博客讲解内容如下: 1.babel是什么 2.javascript制作规范 3. ...
- babel简介
1.babel是什么 babel官网正中间一行黄色大字写着“babel is a javascript compiler”,翻译一下就是babel是一个javascript转译器.为什么会有babel ...
- babel简介——简单介绍与实用(转)
博客讲解内容如下: 1.babel是什么 2.javascript制作规范 3.babel转译器 4.babel的使用 5.常见的几种babel转译器和插件 6.babel最常见配置选项 7.babe ...
随机推荐
- 三、Redis基本操作——List
小喵的唠叨话:前面我们介绍了Redis的string的数据结构的原理和操作.当时我们提到Redis的键值对不仅仅是字符串.而这次我们就要介绍Redis的第二个数据结构了,List(链表).由于List ...
- System.arraycopy()和Arrays.copyOf()的区别
先看看System.arraycopy()的声明: public static native void arraycopy(Object src,int srcPos, Object dest, in ...
- 用SignalR 2.0开发客服系统[系列2:实现聊天室]
前言 交流群:195866844 上周发表了 用SignalR 2.0开发客服系统[系列1:实现群发通讯] 这篇文章,得到了很多帮助和鼓励,小弟在此真心的感谢大家的支持.. 这周继续系列2,实现聊天室 ...
- JavaScript实现图片轮播组件
效果: 自动循环播放图片,下方有按钮可以切换到对应图片. 添加一个动画来实现图片切换. 鼠标停在图片上时,轮播停止,出现左右两个箭头,点击可以切换图片. 鼠标移开图片区域时,从当前位置继续轮播. 提供 ...
- 数据库进阶之路(五) - MySQL行锁深入研究
由于业务逻辑的需要,必须对数据表的一行或多行加入行锁,举个最简单的例子,图书借阅系统:假设id=1的这本书库存为1,但是有2个人同时来借这本书,此处的逻辑为: ; --如果restnum大于0,执行u ...
- Linux特殊符号浅谈
Linux特殊字符浅谈 我们经常跟键盘上面那些特殊符号比如(?.!.~...)打交道,其实在Linux有其独特的含义,大致可以分为三类:Linux特殊符号.通配符.正则表达式. Linux特殊符号又可 ...
- Redis简单案例(一) 网站搜索的热搜词
对于一个网站来说,无论是商城网站还是门户网站,搜索框都是有一个比较重要的地位,它的存在可以说是 为了让用户更快.更方便的去找到自己想要的东西.对于经常逛这个网站的用户,当然也会想知道在这里比较“火” ...
- MFC背景透明
# 一: # typedef BOOL (WINAPI *lpfnSetLayeredWindowAttributes)(HWND hWnd, COLORREF crKey, BYTE bAlpha, ...
- Devexress XPO xpPageSelector 使用
在官网找到的.在这里做个备注. private void simpleButton1_Click(object sender, EventArgs e) { ) return; xpPageSelec ...
- ASP.NET MVC——CodeFirst开发模式
Entity Framework框架提供了几种开发模式,比如Database First,Model First,Code First.Database First是最老也是应用得最广泛的一种设计方式 ...