Reactjs入门官方文档(十一)【Thinking in React】

此文章是翻译thingking in react这篇React(版本v15.5.4)官方文档。

React 的编程思想

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

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

从一个线框图开始

设想你已经根据我们的设计有一个模拟JSON API。这个Mock 看上去如下:

我们的JSON API 返回的数据如下:

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

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

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

但是你该如何拆分组件呢?其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则single responsibility principle,也就是说,理论上一个组件只做一件事。如果组件变大,它应该被拆分成几个小组件。

由于你经常展示一个JSON 数据模型给用户,你会发现如果你的模型被正确构建,那么你的UI(和你的组件框架)会非常好的匹配。那是因为UI 和数据模型总是依附相同的信息架构(information architecture),也就是意味着分离你的UI 成为组件是非常容易的。只要正确地的将你的每一个数据模型对应组件就可以了。

你会发现在我们这个简单的app中有5 个组件。我们使用斜体数据来表示每一个组件

  1. FiterableProductTable(orange):包含整个例子
  2. SearchBar(blue):接受所有的用户输入
  3. ProdutTable(green):根据用户输入来展示过滤的数据集合
  4. ProductCategoryRow(宝石绿turquoise):展示每一个分类的标题
  5. ProductRow(red):每一个产品展示一行

如果你看ProductTable,你会发现表格头(包含”Name” 和”Price” 标签)并不是单独一个组件。这是一个重要的表现,并且这里可以使用两种方式作为参数传入。例如,我们将它作为ProductTable 的一部分,因为因为它是ProductTable 的负责的渲染数据的一部分。无论怎样,如果它的头变复杂了(例如,如果我们添加了一个排序功能),最好的方式的作为一个单独的ProductTableHeader组件。

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

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

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

在CodePen 上尝试

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',name: 'Baseball'},price: '$29.99',stocked: false,name: 'Basketball'},{category: 'Electronics',price: '$99.99',name: 'iPod Touch'},price: '$399.99',name: 'iPhone 5'},price: '$199.99',name: 'Nexus 7'}
];

ReactDOM.render(
  <FilterableProductTable products={PRODUCTS} />,document.getElementById('container')
);

现在你有了自己的组件系统,是时候实现你的app 了。最简单的方式的是个构建一个没有交互的只是将数据渲染成UI。最好解耦这些过程,因为构建一个静态版本需要许多代码不需要思考,添加交互需要更多思考和少量的代码。我们会看到为什么。

为了构建一个静态的app 去渲染你的数据模型(data model),你想要构建组件并复用其他的组件同使用props 去传递数据。props 是从父组件传递给子组件数据的一种方式。如果你了解state 的概念,在构建静态版本时,一点也不需要使用state(don’t use state at all)。State 只用于交互,也就是说,数据可以随时被改变。因为是静态版本,所以你不需要state。

在构建时你可以自上而下(top-down)或自下而上(down-top)。也就是说,你可以从位于高层级的组件(例如,FilterableProductTable)或者是低层级的组件(ProductRow)开始。在简单的例子中,通常是自上而下(top-down),而在大型项目中,通常是从下而上(down-top)这样便于编写测试用例。

在此最后,你有了许多可复用的组件来渲染你的数据模型。因为是静态版本的应用,所以这些组件中仅有render() 方法。在顶层组件(FilterableProductTable)将会接受你的数据模型作为prop。如果你改变你的基础数据模型(underlying data model)并且又调用了ReactDOM
.render()
,这个UI 将会被更新。这是很容易看淡你的UI 是如何更新,并在哪里做出改变,因为没有什么复杂的事情。React 是单向数据流one-way data flow(也叫做单向绑定one-way bind)来保持一切模块化和高性能。

如果你在执行这一步的时候需要帮助,可以简单浏览React doc

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

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

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

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

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

考虑我们在实例应用程序中所有的数据块。我们有:

  • 最初的产品列表(The original list of products
  • 用户键入的搜索文本(The search text the user has entered
  • checkbox 的值(The value of the checkbox
  • 过滤后的产品列表(The filtered list of products

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

  1. 它是父组件通过props 传递的吗?如果是,它很可能不是state。
  2. 是否永远不会发生变化? 如果是这样,它可能不是 state(状态)。
  3. 在组件中,你能够基于其他的state 或props 来计算出它吗?如果是,它不是state。

The original list of products是通过props 进行传递的,所以它不是state 。The search text and the checkbox 似乎是state 因为它们会根据用户的输入发生变化,并且不能从其他数据计算得出。 最后,The filtered list of products 不是state,因为它可以通过结合原始产品列表the original list of products 和搜索文本和复选框的值 the searche text and value of the checkbox 计算出来。

所以最终,我们的state 是:

  • 用户键入的搜索文本(The search text the user has entered
  • checkbox 的值(The value of the checkbox

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

在CodePen 上尝试

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>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


var PRODUCTS = [
  {category: 'Sporting Goods',document.getElementById('container')
);

OK,现在我们已经确认了app state 的最小集合。Next,我们需要确认一个组件改变或拥有这些 state。

记住:React 单向数据流在层级中自上而下进行。它可能并不立即清楚那一个组件应该拥有什么state。这个通常对新手去了解是充满挑战的,所以根据接下来的步骤来搞清楚它:

对于你应用中的每一个state :

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

我们在我们的应用中贯穿这个策略:
* ProductTable 需要这个state 去过滤产品列表(product list),并且SearchBar 需要去展示搜索文字(search text)和选中状态(checked)state
* 公共的父级组件是FilterableProductTable
* 在概念上来说是有道理的,这个filter text 和checked value 位于FilterableProductTable 组件中

那么我们已经决定 state(状态) 保存在 FilterableProductTable 中。首先,添加一个实例属性 this.state = {filterText: ”,inStockOnly: false} 到 FilterableProductTable 的constructor 来反映你应用的初始 state(状态) 。然后,将filterTextinStockOnly作为prop 传递给SearchBarProductTable。最后,使用这些props 过滤ProductTable 中的row 以及在SearchBar 中设置form fields 的值。

你可以看你的应用的行为:设置filterTextball 并刷新你的应用,你会看到table 中数据正确更新了。

步骤5:添加反向数据流

在CodePen 上尝试

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;
    console.log(this.props.inStockOnly)
    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.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this);
    this.handleInStockInputChange = this.handleInStockInputChange.bind(this);
  }

  handleFilterTextInputChange(e) {
    this.props.onFilterTextInput(e.target.value);
  }

  handleInStockInputChange(e) {
    this.props.onInStockInput(e.target.checked);
  }

  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextInputChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockInputChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',inStockOnly: false
    };

    this.handleFilterTextInput = this.handleFilterTextInput.bind(this);
    this.handleInStockInput = this.handleInStockInput.bind(this);
  }

  handleFilterTextInput(filterText) {
    this.setState({
      filterText: filterText
    });
  }

  handleInStockInput(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onFilterTextInput={this.handleFilterTextInput}
          onInStockInput={this.handleInStockInput}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


var PRODUCTS = [
  {category: 'Sporting Goods',document.getElementById('container')
);

到现在为止,我们已经建立了一个应用程序,进行正确渲染将props 和state 作为功能在层级中向下流动。现在,是时候添加支持数据从另一个方法流动:在层级中最深出的form 组件去更新位于FilterableProductTable 中的state。

Reack 为了使数据流明确,使它能够容易的了解你的程序是如何工作的,但是它需要更多的数据相比传统的双线数据绑定(two-way data binding)

如果在这个例子版本中进行键入信息或操作多选框,你会发现React 忽略了你的输入。这是趋势,通过设置inputvalue 等于在FiterableProductTable 中的state

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

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

就这么简单

希望,他能够给你一个思路,在React 中如何思考构建组件和应用。虽然它可能比你之前使用更多的代码,记住代码可读性远超过它的编写, 模块化、结构清晰的代码最利于阅读。当你开始构建大量的组件库,你将感激模块化、结构清晰和可以重用的代码,同时你的代码行数会慢慢减少。:)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom