今天看啥  ›  专栏  ›  薛定喵的谔

React 设计哲学

薛定喵的谔  · 掘金  ·  · 2019-07-18 09:23
阅读 149

React 设计哲学

React 最棒的部分之一是引导我们思考如何构建一个应用。在这篇文档中,我们将会通过 React 构建一个可搜索的产品数据表格来更深刻地领会 React 哲学。 本文参考官方文档文档链接

  • 从产品应用思考技术实现

    • 例如我们有这样一个设计稿

设计稿
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"}
];
复制代码

设计稿划分

  1. FilterableProductTable (橙色): 是整个示例应用的整体
  2. SearchBar (蓝色): 接受所有的用户输入
  3. ProductTable (绿色): 展示数据内容并根据用户输入筛选结果
  4. ProductCategoryRow (天蓝色): 为每一个产品类别展示标题
  5. ProductRow (红色): 每一行展示一个产品

抽象层级

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

现在我们用代码展现

把架子搭建出来

量太多 详见 codepen codepen

确定状态 [state]

什么是状态? 我们可以吧用户是|| 否点击 ,用户的search且这些都是动态的状态,动态的状态对应这上图最小化UI的动作, 对应上图 就是根据蓝框的状态来变化绿框 简单的概况就是: 数据驱动视图

state放置在那一层

注意: 单从React 组件设计考虑,不涉及context redux mobx 等复杂跨组件交互,

  • 找到根据这个 state 进行渲染的所有组件。
  • 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
  • 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
  • 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。

根据以上策略重新考虑我们的示例应用:

  • ProductTable 需要根据 state 筛选产品列表。SearchBar 需要展示搜索词和复选框的状态。
  • 他们的共同所有者是 FilterableProductTable。
  • 因此,搜索词和复选框的值应该很自然地存放在 FilterableProductTable 组件中。
  • 很好,我们已经决定把这些 state 存放在 FilterableProductTable 组件中。首先,将实例属性 this.state = {filterText: '', inStockOnly: false} 添加到 FilterableProductTable 的 constructor 中,设置应用的初始 state;接着,将 filterText 和 inStockOnly 作为 props 传入 ProductTable 和 SearchBar;最后,用这些 props 筛选 ProductTable 中的产品信息,并设置 SearchBar 的表单值。

反向数据流

我们知道react 是一个单向数据流 也就是数据变化视图变化,但是视图中的input变化我们的数据还没变化,这就需要我们来实现,通过 fn 来触发 改变state 中被监听的数据,从而改变视图

最后贴出完整代码

codepen在线查看

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.name.indexOf(filterText) === -1) {
        return;
      }
      if (inStockOnly && !product.stocked) {
        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.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }
  
  handleFilterTextChange(e) {
    this.props.onFilterTextChange(e.target.value);
  }
  
  handleInStockChange(e) {
    this.props.onInStockChange(e.target.checked);
  }
  
  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

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

  handleFilterTextChange(filterText) {
    this.setState({
      filterText: filterText
    });
  }
  
  handleInStockChange(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onFilterTextChange={this.handleFilterTextChange}
          onInStockChange={this.handleInStockChange}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


const 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')
);

复制代码



原文地址:访问原文地址
快照地址: 访问文章快照