翻譯--Thinking in React

無聊翻譯篇react入門文章,去年學習react時看了一遍,很不錯的一篇文章。html

https://reactjs.org/docs/thinking-in-react.htmlreact

部分爲意譯,旨在讓newcomers 容易理解。web

()內均爲譯者注

React會是我快速構建大型webapp的首要js框架選擇。
其在FacebookInstagram上的實踐給予了咱們充足的自信。json

React衆多閃光點中的一個就是讓你開始思考如何設計、構建應用。(主要就是react是數據驅動設計,因此如何設計state成了很重要的一部分)api

本文,將以一個商品下拉列表加搜索框的例子來展現react架構


Start With A Mock
本例子大概長這樣,數據源來自構建的一個mock,以json api方式進行訪問。
app

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

第一步:拆分組件,分析組件結構

譯者注:組件結構拆分沒有一個標準,例子裏是拆的很細,實際工做中通常是統一規範較重要,可讀性至上。webapp

首先拆分紅多個組件跟子組件,而且命名。這裏已經用虛線框標識了。工做中,如何有設計或者產品輔助你的話,這個工做可能不須要你來作,交給他們便可。他們會用ps以圖層的方式拆分好組件。(大廠嗎?!)模塊化

這裏的組件是已經拆分好了,可是若是本身拆分,該如何作呢?答案是用必定的標準進行規範。好比你能夠選擇的一個標準就是:單一職責原則。即一個組件只負責一件事情。(這個事情範圍就廣了,好比一個動做,一個請求。原則就是方便管理與維護)若是還能細分,就再拆成更小層次的組件。(react就是一層層組件嵌套,各類組件與子組件)

咱們通常常常用json data model返回給用戶,在react中,只要data model格式正確,界面ui(組件)就渲染得很舒服了,通常相差不到哪兒去。這是由於uidata model 傾向於遵循相同的架構。跟這些帶來的好處相比,拆分組件的基礎工做就顯得微不足道了。把界面打散拆分紅多個組件,每一個組件表明data model的某一部分。(這一大段啥意思呢?就是誇了一下react數據驅動的好處)

看上圖,咱們把ui拆成了5個組件,下面是各個組件對應的職責,斜體突出表示下。
1.FilterableProductTable(orange):contains the entirety of the example
(包裹全部子組件的外殼)
2.SearchBar(blue):處理用戶交互
3.ProductTable(green):用戶交互後過濾出的商品列表數據展現
4.ProductCategoryRow(turquiso 亮青色):顯示商品列表分類名稱
5.ProductRow(red):展現每一個商品信息

這邊注意觀察下組件ProductTable下有兩個label做爲header--「Name」「Price」。這兩個label沒有做爲單獨的組件如ProductCategoryRowProductRow存在,這裏僅僅是做爲ProductTable的一部分存在。固然你能夠將其做爲單獨一個子組件開闢出來,只是在這個例子裏不必,若是這個header再複雜一點,好比加上點擊排序功能,那麼能夠再建一個子組件--ProductTableHeader

如今ui已經拆分完成,來分一下組件層次。
(其實就是個樹狀圖,不少狀況下,你的json data model 長啥樣,組件層次基本上就差不離了)

·FilterableProductTable
·SearchBar
·ProductTable
·ProductCategoryRow
·ProductRow

(小問題,上面提到的ProductTableHeader若是存在,應該放在樹狀圖的哪一個位置呢?)

第二步:構建靜態頁面

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 rows = [];
    let lastCategory = null;
    
    this.props.products.forEach((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>
    );
  }
}


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

如今組件拆分好了,json data model有了,開始實現界面代碼吧。先作一個最簡單的版本,只有界面,沒有交互。交互留到後面作,這樣分開作的好處是先作靜態界面只用堆代碼而不須要考慮邏輯交互,交互邏輯到後面作。(事情一件件作,也正符合組件拆分的標準之一,single responsibility principle,單一職責)

實現靜態版本從構建組件開始,實現構建複用的基礎之一就是經過使用props。何謂props?props就是將數據從樹狀圖由上到下傳遞的快遞員。(或者說從parent到child,這個parent或child是相對的,針對不一樣的兩個組件,隨時變化的,因此用樹狀圖來理解舒服點)若是你有必定的react基礎,熟悉state的話,(state也能傳遞數據)在這裏先不要考慮用state,只有在交互的時候,隨時間變化的數據須要用到state。

你能夠從上到下或者由下至上構建組件,意思就是你能夠先構建最上面的FilterableProductTable或者最下面的ProductRow。在簡單的項目中,通常從上到下構建組件更簡單。大點稍微複雜點的項目中能夠由下至上構建組件,這樣也方便編寫測試實例。(簡單例子怎樣都行,複雜的項目,都是一個個組件嵌套的,缺什麼補什麼,通常不存在思考這個,除非整個項目是由你來從零架構的)

如今咱們已經構建了一組可用於渲染data mod的複用組件構成的組件庫。每一個組件內只有一個方法:render(),由於如今還只是靜態頁面。樹狀圖最上端的組件FilterableProductTable會把data model打包成一個props。若是你對data model進行更改並再次調用ReactDom.render(),ui界面就會更新。代碼很簡單,很容易觀察ui如何更新以及在哪裏進行更改。React另外一個特性:單向數據流(也叫單項綁定 one way binding)使代碼模塊化且運行快速。

小注:Props vs State

react中有兩類存儲data model的對象,props跟state。瞭解二者的區別仍是很重要的。
詳細:【🔗】https://reactjs.org/docs/interactivity-and-dynamic-uis.html

第三步:定義完整而簡潔的state

交互就是ui界面能對data model 的變化做出相應。React經過state使其實現變得簡單。

繼續構建app前,須要肯定好完整且簡潔的state。遵循的規則就是:DRY(don't repeat yourself)。舉個例子,在作TODO List例子時,咱們通常會用到List Count這個屬性,但不必將Count做爲state的一個字段存在,由於經過計算List的length也能獲取到Count。(只保留必要的屬性字段在state中)

針對咱們的應用,現有的數據以下:
a·原始商品列表數據
b·用戶輸入搜索文本
c·複選框選中與否
d·過濾後的商品列表數據

那麼如何肯定以上哪些是應該做爲state的部分而存在的呢?能夠簡單的問問本身下面這三個問題:

1.該數據是經過props傳遞的嗎?若是是,那就應該不屬於state
2·該數據不是實時變化(即不隨交互而變化)的,那就應該不屬於state
3·能夠經過其餘state跟props計算而出(如上面說的List Count),那就應該不屬於state

而後帶着這三個問題,咱們來看看上面四個數據。靜態頁面版本中已經看出,a是經過props傳遞的,因此a不屬於state,b跟c是根據用戶輸入來肯定的,隨交互而變化,因此bc應該屬於state的一部分,d能夠經過abc結合計算而得,因此雖然會變化但也不屬於state。

總結,state:
·用戶輸入文本
·複選框選中與否

state = {
      filterText:'',
      isStockOnly:false
}

第四步:構建state

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 {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={filterText} />
        <p>
          <input
            type="checkbox"
            checked={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>
    );
  }
}


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

如今state已經肯定好了,開始要與組件交互關聯了,知道state的那部分應該放在哪一個組件中。

再提醒一句:React是單向數據流導向的,從樹狀圖的上端往下。

哪一個組件應該有怎樣的state對於react新手來說多是很困惑的一點,下面幾點可能對你會有幫助:

·肯定哪些組件在渲染過程當中要用到state
·可能多個組件同時須要用到state的一部分,那邊找到一個它們共同的parent component,把state放在這個組件裏
·若是已有的組件中找不到這樣的parent component,那就建立一個。
(意譯)

依照以上標準分析下咱們的應用:
·ProductTable 顯示過濾後的商品數據,這須要經過state跟原始數據(在props中),SearchBar須要顯示過濾文本跟複選框勾選狀況。
·上面二者的common parent component就能夠是FilterableProductTable。
·因此講state中的filterText跟checkvalue放在FilterableProductTable,沒毛病。

咱們state中也就這兩,因此放在哪一個組件也肯定了,開始碼代碼了。
首先,在FilterableProductTable中的構造函數裏初始化state對象,而後將state裏的內容做爲props傳遞到對應的child component中去(交互=》觸發預先定義的事件=》state變化=》做爲state內容載體的props傳遞到對應組件=》具體組件render=》用戶看到交互結果)

第五步:實現交互事件(add inverse data flow ,難以描述)

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

目前爲止,經過state,props已經構建好了咱們的頁面,如今實現交互事件。

React使用的單向數據流跟單向數據綁定使react的工做易於理解,雖然這相比雙向綁定的確須要寫多點代碼。(使黑魔法再也不神祕,react不須要黑魔法)

下面這段過程總結,我以爲仍是我上面註解的那段拿過來:
交互=》觸發預先定義的事件=》state變化=》做爲state內容載體的props傳遞到對應組件=》具體組件render=》用戶看到交互結果

(數據綁定體如今,state一旦發生變化,跟state關聯的數據都將重現計算,而經過數據驅動的頁面也將從新渲染。)

that's all

歡迎討論~
感謝閱讀~

我的公衆號:

原文:http://www.cnblogs.com/joeymary/p/8530336.html

相關文章
相關標籤/搜索