[译]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:
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
第二步:用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 ...
随机推荐
- 06. Web大前端时代之:HTML5+CSS3入门系列~HTML5 画布
Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 我们先看看画布的魅力: 初始画布 canvas默认是宽3 ...
- PHP 面向对象编程和设计模式 (5/5) - PHP 命名空间的使用及名称解析规则
PHP高级程序设计 学习笔记 2014.06.12 命名空间概述 PHP 在 5.3.0 以后的版本开始支持命名空间.什么是命名空间?从广义上来说,命名空间是一种封装事物的方法.在很多地方都可以见到这 ...
- HTML基础
HTML指的是超文本标记语言 (Hyper Text Markup Language), HTML不是一种编程语言,而是一种标记语言 (markup language) ,HTML使用标记标签来描述网 ...
- 【JS】javascript 正则表达式 大全 总结
javascript 正则表达式 大全 总结 参考整理了一些javascript正则表达式 目的一:自我复习归纳总结 目的二:共享方便大家搜索 微信:wixf150 验证数字:^[0-9]*$ 验证n ...
- 【干货】”首个“ .NET Core 验证码组件
前言 众所周知,Dotnet Core目前没有图形API,以前的System.Drawing程序集并没有包含在Dotnet Core 1.0环境中.不过在dotnet core labs项目里可以见到 ...
- php内核分析(二)-ZTS和zend_try
这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux ZTS 我们会看到文章中有很多地方是: #ifdef ZTS # define CG(v) ZEND_TSRMG(comp ...
- User Growth Using Deeplink. (part1)
转载请注明来源 http://www.cnblogs.com/hucn/p/5917924.html 活跃人数是衡量app一项关键指标, dau, mau, 有了流量才能给业务发展提供养分和空间. a ...
- C# 文件下载之断点续传
注意,本文所说的断点续传特指 HTTP 协议中的断点续传.本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo. 工作原理 HTTP 协议中定义了一些请求/响应头,通过组合使用这些头信息.我 ...
- dicom网络通讯入门(2)
第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了. 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么.同学我告诉你还早着呢. ...
- .Net语言 APP开发平台——Smobiler学习日志:Poplist控件的正确打开方式以及如何快速实现
最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 样式一 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的&qu ...