Thinking in React - 思考React
在我们看来,React是使用js来创建大的、速度快的web应用的首选方式。它已经在Facebook和Instagram表现的非常好。
React众多伟大部分之一是:创建应用时,它是如何让你思考的。在这个文档中,我们将带领你穿越,使用React来构建搜索产品数据表的整个思维过程

Start With A Mock - 以一个模拟开始
想象一下,我们已经有一个JSON API和来自我们设计师的一个模拟。我们的设计显然不是很好,因为模拟看起来就像下面这样:

我们的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"}];

Step 1: Break The UI Into A Component Hierarchy - 第一步:打破UI,成为一个组件层次结构
你将做的第一件事情是,在模拟环境下给每个组件(和子组件)周围画上方框,并给定它们名称。如果你正在和一个设计师合作,他们可能已经做了这个,因此去和他们交流!他们的PS的图层名可能就是你的React组件名称。
但是你怎么知道哪些应该是组件?当你决定创建一个新函数或对象(创建组件的2种方式:函数和对象)时,就参考下面这些相同的技巧。技巧之一是:单一职责原则,这就是,一个组件原则上制作一件事。如果它逐渐扩展了,就应该被拆分为更细的组件。
由于我们经常展示一个JSON数据模型给用户,你将发现:如果你的模型被正确地建立,你的UI(也就是组件结构)也将很好的映射。这是因为UI和数据模型倾向于支持同样的信息架构,这意味着拆分UI为组件的工作总是琐碎的。我们要做的就是:将UI拆分成准确代表部分数据模型的一个个组件。

这里你将看到,在我们简单的应用中有5个组件,我们用斜体表示代表组件的数据。
1.FilterableProductTable(橙色):包含整个示例
2.SearchBar(蓝色):接收所有的用户输入
3.ProductTable(绿色):基于用户输入,展示和过滤数据集合
4.ProductCategoryRow(青绿色):展示每个分类头
5.ProductRow(红色):展示每个产品的详情
如果你在看 'ProductTable' 组件,你将发现表头(包含 'Name' 和 'Price' 标签)不是它自己的组件。这是一个偏好问题,关于是否这样使用存在争论。针对这个例子,我们留下它作为 'ProductTable' 组件的一部分,因为它是渲染 'data' 集合的一部分,是 'ProductTable' 组件的职责(之前说过:组件单一职责原则)。然而,如果这个表头变的复杂(例如:如果我们增加了字段排序功能),制作一个 'ProductTableHeader' 组件可能更有意义。
现在,我们已经在模拟环境中确定了我们的组件,让我们将它们排列成一个层级结构:

     FilterableProductTableSearchBarProductTableProductCategoryRowProductRow

Step 2: Build A Static Version in React - 第二步:在React中构建一个静态版本

class ProductCategoryRow extends React.Component {render() {return <tr><th colSpan="2">{this.props.category}</th></tr>;}
}class ProductRow extends React.Component {render() {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>);}
}class ProductTable extends React.Component {render() {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>);}
}class SearchBar extends React.Component {render() {return (<form><input type="text" placeholder="Search..." /><p><input type="checkbox" />{' '}Only show products in stock</p></form>);}
}class FilterableProductTable extends React.Component {render() {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')
);

现在,你已经有了你的组件层级结构,是时候实现你的应用了。建立一个版本的最简单的方式是:定义数据模型和渲染UI,但是它们之间没有任何交互。最好解耦这些过程,因为构建一个静态版本需要大量的键入,而且不用思考;而添加交互则需要大量思考,并不需要很多的键入。我们会看到这是为什么。
为了构建你应用的一个静态版本,来渲染你的数据模型,你想要构建组件,这些组件重用其他组件,并通过 'props' 来传递数据。'props' 是从父传递给子数据的一种方式。如果你熟悉 'state' 概念,构建静态版本时,千万不要使用 'state'!'state' 仅用于交互,也就是,数据随着时间改变。由于这是一个静态版本,你不需要 'state'。
你可以自上而下,或自下而上构建静态版本。就是说,你可以从层级结构的顶层开始构建组件(FilterableProductTable),也可以从层级结构的底层开始构建组件(ProductRow)。在更简单的例子中,通常自上而下建立更简单,在大的项目中,自下而上建立则更简单一点。
在这个步骤的最后,你将有一个可重用的组件库,用来渲染你的数据模型。这些组件仅有 'render()' 方法,因为这是你应用的一个静态版本。在层级结构顶层的组件(FilterableProductTable)将你的数据模型,定义为 'props' 属性。如果你修改了数据模型,并再次调用了 'ReactDOM.render()',UI将被更新。查看UI如何更新,以及哪些地方进行了修改,是很简单的。因为没有什么复杂的东西。React的 'one-way data flow'(也称作 'one-way binding'),使得一切都模块化,并且运行的最快。
一个小插曲:
在React中,有2种数据模型:'props' 和 'state'。理解2者的不同很关键;如果不确定2者的不同,可查看之前文档中的解释。

Step 3: Identify The Minimal (but complete) Representation Of UI State - 第三步:确定表示UI状态的最小(但是完整的)集合
为了使你的用户界面交互,你需要能够触发对你的底层数据模型的修改。React使用 'state' 让这个变的很容易。
为了正确构建应用程序,首先你需要考虑应用需要的可变的 'state' 状态的最小集合。关键是 'DRY':Don't Repeat Yourself(不要重复)。找出你应用所需的状态的绝对的最小集合,并计算所有你需要的东西。例如,你正在构建一个 'TODO' 列表,那就仅定义一个 'TODO' 列表条目的数组;不要为计数定义一个单独的 'state' 变量。替代的,当你想渲染 'TODO' 个数时,直接使用 'TODO' 数组的长度。
考虑我们示例应用的所有数据。我们有:
1.产品的原始列表
2.用户已经输入的搜索内容
3.多选框的值
4.产品的筛选列表
让我们依次讨论,找出哪个应该被定义为 'state'。简单地问3个问题,关于每个数据:
1.它是否通过父组件的 'props' 传递?如果是,它可能不应该被定义为 'state'。
2.它是否随着时间保持不变?如果是,它可能不应该被定义为 'state'。
3.在你的组件中,它能不能基于其他 'state' 或 'props' 来计算?如果是,它可能不应该被定义为 'state'。
产品的原始列表,作为一个 'props' 被传入,因此不是 'state'。搜索内容和多选框,貌似可被定义为 'state',因为它们随着时间改变,并且不能通过其他一切来计算。最后,产品的筛选列表不是 'state',因为:它可以通过原始的产品列表和搜索文本或多选框的值,来计算。
因此最终,我们的 'state' 是:
1.用户已经输入的搜索内容
2.多选框的值

Step 4: Identify Where Your State Should Live - 第四步:识别 'state' 应该位于哪里

class ProductCategoryRow extends React.Component {render() {return (<tr><th colSpan="2">{this.props.category}</th></tr>);}
}class ProductRow extends React.Component {render() {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>);}
}class ProductTable extends React.Component {render() {var rows = [];var lastCategory = null;this.props.products.forEach((product) => {if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {return;}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>);}
}class SearchBar extends React.Component {render() {return (<form><input type="text" placeholder="Search..." value={this.props.filterText} /><p><input type="checkbox" checked={this.props.inStockOnly} />{' '}Only show products in stock</p></form>);}
}class FilterableProductTable extends React.Component {constructor(props) {super(props);this.state = {filterText: '',inStockOnly: false};}render() {return (<div><SearchBarfilterText={this.state.filterText}inStockOnly={this.state.inStockOnly}/><ProductTableproducts={this.props.products}filterText={this.state.filterText}inStockOnly={this.state.inStockOnly}/></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')
);

我们已经定义了应用 'state' 的最小集合。接下来,我们需要定义,哪些组件使这些状态改变(哪些组件拥有这些 'state',也就是 'state' 定义在哪个组件内)。
记住:React中,数据是随着组件层级结构自上而下的单向数据流。可能不会马上明确哪个组件应该拥有哪些 'state'。这往往是新手理解中最具挑战性的一部分,因此通过下面的步骤来弄明白它:
1.对于应用程序的每个 'state':
1>识别基于 'state' 的每个组件(哪些组件需要state)
2>寻找一个拥有这些 'state' 的公共组件(层级结构中,所有需要 'state' 的组件的一个公共父组件)
3>公共组件或在层级结构上,更高层次上的另一个组件,应该拥有 'state'
4>如果你不能找到一个组件,拥有这个 'state',创建一个新组件仅仅为了拥有这个 'state',并且将这个新组件添加到公共组件的上层。
2.让我们针对我们的应用来使用这个策略:
1>'ProductTable' 需要基于 'state' 来过滤产品列表,'SearchBar' 需要基于 'state' 来展示 '搜索文本state' 和 '多选框state' 
2>拥有这些 'state' 的公共组件是 'FilterableProductTable'
3>概念上,'过滤文本' 和 '多选框值' 这2个 'state' 位于 'FilterableProductTable' 组件上,是有意义的。
我们已经决定我们的state位于 'FilterableProductTable' 组件。首先,添加一个属性:this.state = {filterText: '', inStockOnly: false} 到 'FilterableProductTable' 的 'constructor' 方法,表示应用的初始化 'state'。接着,传递 'filterText' 和 'inStockOnly' 给 'ProductTable' 和 'SearchBar' 组件,作为它们的属性。最后,使用这些属性,在 'ProductTable' 来过滤行,以及在 'SearchBar' 中设置表单字段的值。
你可以看到你的应用将如何工作:设置 'filterText' 为 'ball',并刷新你的应用。你将看到数据表正确的更新了。

Step 5: Add Inverse Data Flow - 第五步:添加一个反向数据流

class ProductCategoryRow extends React.Component {render() {return (<tr><th colSpan="2">{this.props.category}</th></tr>);}
}class ProductRow extends React.Component {render() {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>);}
}class ProductTable extends React.Component {render() {var rows = [];var lastCategory = null;this.props.products.forEach((product) => {if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {return;}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>);}
}class SearchBar extends React.Component {constructor(props) {super(props);this.handleChange = this.handleChange.bind(this);}handleChange() {this.props.onUserInput(this.refs.filterTextInput.value,this.refs.inStockOnlyInput.checked);}render() {return (<form><inputtype="text"placeholder="Search..."value={this.props.filterText}ref="filterTextInput"onChange={this.handleChange}/><p><inputtype="checkbox"checked={this.props.inStockOnly}ref="inStockOnlyInput"onChange={this.handleChange}/>{' '}Only show products in stock</p></form>);}
}class FilterableProductTable extends React.Component {constructor(props) {super(props);this.state = {filterText: '',inStockOnly: false};this.handleUserInput = this.handleUserInput.bind(this);}handleUserInput(filterText, inStockOnly) {this.setState({filterText: filterText,inStockOnly: inStockOnly});}render() {return (<div><SearchBarfilterText={this.state.filterText}inStockOnly={this.state.inStockOnly}onUserInput={this.handleUserInput}/><ProductTableproducts={this.props.products}filterText={this.state.filterText}inStockOnly={this.state.inStockOnly}/></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')
);

到目前为止,我们已经创建了一个应用,。现在是时候以另一种方式支持数据流:在层级结构中的深层表单组件,需要在 'FilterableProductTable' 中更新 'state'。
React的数据流很明确,使得理解你的应用程序是如何工作的变的很容易,但相比于传统的双向数据绑定,它确实需要输入更多。
在示例中的当前版本中,如果你尝试输入或者勾选多选框,你将看到React会忽略你的输入。这是故意的,因为我们输入的值,总是等于从 'FilterableProductTable' 传入的 'state' 值。
让我们思考我们想要发生什么。我们想要确保:每当用户更改了表单,我们更新 'state' 来反映用户输入。因为组件仅应该更新它们自己的 'state', 'FilterableProductTable' 将传递一个回调函数给 'SearchBarla',来触发 'state' 更新。我们可以在表单元素上,使用 'onChange' 事件来通知它。通过 'FilterableProductTable' 传递的回调,将调用 'setState()' 方法,应用将被更新。
虽然这听起来很复杂,但它确实只是几行代码。而且贯穿整个应用中,你的数据是如何流动,是很明确的。

And That's It - 这就是它
希望,通过这篇教程让你知道,使用React如何来创建组件和应用。虽然相比于你之前使用过的其他类库,React可能需要写更多,但是记住:代码的阅读远比书写更重要,并且阅读通过React创建的模块化、清晰的代码是极其容易的。当你开始建立大型的组件库时,你将会欣赏这个明确化和模块化,重用性代码,你的代码行将减少!

React中文文档之Thinking in React相关推荐

  1. react中文文档、英文文档及JavaScript相关文档及web前端相关资料

    一. react中文文档 https://doc.react-china.org 二. react英文文档 https://reactjs.org 三.react Github https://git ...

  2. React中文文档之Rendering Elements

    Rendering Elements - 渲染元素 元素是React应用的最小构建块 一个元素描述了你想要在屏幕上看到的内容: const element = <h1>Hello, wor ...

  3. React中文文档之Forms

    Forms - 表单 在React中,HTML表单元素同其他DOM元素,有点不同.因为表单元素天生具备一些内部的state状态.例如:下面的HTML表单接收一个名字: <form>Name ...

  4. React中文文档之Handling Events

    Handling Events - 事件处理 React元素的事件处理同DOM元素的事件处理非常相似. 有一些语法不同: 1.React事件使用 'camelCase-驼峰式' 命名,而不是 'low ...

  5. React中文文档之Conditional Rendering

    Conditional Rendering - 有条件的渲染 在React中,你可以创建唯一的组件,来封装你需要的行为.之后,你可以仅仅渲染它们中的一些,这取决于你应用的状态. React中的有条件的 ...

  6. React中文文档之introducing JSX

    introducing JSX 思考下面的变量声明: const element = <h1>Hello world!</h1>; 这个有趣的标签解析,既不是字符串,也不是HT ...

  7. React中文文档之State and Lifecycle

    state 和 生命周期 到目前为止,我们仅仅学习了一种方式来更新UI. 我们调用 'ReactDOM.render()' 来改变输出渲染: function tick() {const elemen ...

  8. React中文文档 8. 列表 Key

    1.遍历 const numbers = [1, 2, 3, 4, 5];const listItems = numbers.map((number) =><li>{number}& ...

  9. React中文文档之Components and Props

    Components and Props - 组件和属性 组件允许你分隔UI为独立的.可重用的零件,每个零件是隔离的. 概念上,组件就像js的函数.它们接收任意的输入(被称为 'props'),并返回 ...

最新文章

  1. vs2017开发Node.js控制台程序
  2. 最全面的卷积神经网络介绍,都在这里了(附代码)
  3. AppDynamics把业务交易跟踪扩展到SAP环境
  4. SAP Spartacus OccEndpointsService调用getBaseEndpoint的一些场景
  5. 行测(爆发篇)之语句表达,像说话一样自然
  6. linux ti 电池驱动_全球跨国车企电动汽车平台和电池系统对比
  7. 传输层学习之五(TCP的SACK,F-RTO)
  8. XP cmd命令 部分
  9. Appium1.22.3下载安装与配置
  10. 神经网络和决策树,神经网络 选股
  11. 明翰英语教学系列之名词篇
  12. 支付宝 ECSHOP 支付接口 PHP
  13. DirextX Training笔记
  14. 圆圈中间一个乘号:克罗内克积
  15. spring cloud聚合项目打jar包报错
  16. HyperAttentionDTI:基于注意机制的序列深度学习改进药物-蛋白质相互作用预测
  17. GitLab Projects 2020 插件配置
  18. 什么是Apple Music 杜比全景声?如何设置开启?
  19. SEO优化怎么发外链,SEO外链发布的技巧
  20. 新手Python爬虫教学(Request+BeautifulSoup)

热门文章

  1. wcs开发_WCS 5.2的评论—用于Webcast和Webcam开发人员的WebRTC服务器
  2. 写信中“敬启者”与“敬启”的区别
  3. 基于心电芯片 KS1081的微小穿戴心电方案
  4. jMonkeyEngine译文 FlagRush7(1)——拥抱大地让我们驾驶的不再是Box
  5. java 汇率换算_已知外汇牌价折算汇率
  6. Scrapy中的item和pipline
  7. 解读 Java 并发队列 BlockingQueue
  8. java http请求发送unicode_Java发送http请求
  9. BLDC四大方案(转)
  10. 像个黑客一样在网络上来无影去无踪之IP代理理论篇