【React系列】如何構建React應用程序

前面幾篇內容簡單整理了一些React的基礎知識點,以方便你們可以簡單的瞭解React的一些相關概念。在本節中,咱們來試着以一個簡單的例子來分析,如何構建一個React應用程序,該如何去思考。css

首先,咱們來選擇比較常見的一個示例程序:TodoList來做爲本次的分析案例。html

圖片描述

上面是一個簡單的TodoList的的樣子,此處並無進行樣式修飾,你們能夠後面本身完善。咱們將按照如下幾個步驟進行分析設計:react

步驟1:將 UI 拆解到組件層次結構中

當咱們拿到一個UI設計時,須要咱們將之進行拆解,使之成爲由每一個組件(和子組件)的結構構成的一個總體,而且能夠根據功能給各個部分進行命名。編程

可是你該如何拆分組件呢?其實只須要像拆分一個新方法或新對象同樣的方式便可。一個經常使用的技巧是單一職責原則,即一個組件理想狀況下只處理一件事。若是一個組件持續膨脹,就應該將其拆分爲多個更小的組件中。數組

React最大的賣點是輕量組件化。咱們分析一下以上截圖中的頁面,若是要分組件的話,咱們大約能夠分紅一個總組件和兩個子組件。一個輸入內容的組件,一個顯示內容列表(帶刪除功能)的組件,外面再用一個總組件將兩個子組件包括起來。app

圖片描述

這樣,咱們的應用程序的結構就清晰了:dom

  • todoList --- 整個應用程序用例模塊化

    • typeNew --- 接收用戶的輸入
    • listTodo --- 顯示全部的list item

步驟2: 用 React 構建一個靜態版本

經過上面的結構劃分,咱們代碼的總體結構大體以下:函數

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';


// TodoList 組件是一個總體的組件,最終的React渲染也將只渲染這一個組件
// 該組件用於將『新增』和『列表』兩個組件集成起來
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todoList: []
    }
  }
  render() {
      return (
          <div>
              <TypeNew  />
              <ListTodo />
          </div>
      );
  }
};

// TypeNew 組件用於新增數據,
class TypeNew extends Component {
  render() {
      return (
          <form>
              <input type="text" placeholder="typing a newthing todo" autoComplete="off" />
          </form>
      );
  }
};

// ListTodo 組件用於展現列表,並能夠刪除某一項內容,
class ListTodo extends Component {
  render() {
      return (
          <ul id="todo-list">
              {/* 其中顯示數據列表 */}
          </ul>
      );
  }
};

// 將 TodoList 組件渲染到頁面
ReactDOM.render(<TodoList />, document.getElementById('root'));

目前爲止你已經有了組件層次結構,如今是時候實現你的 app 了。最簡單的方法是構建一個採用數據模型並渲染 UI 但沒有交互性的版本。最好解耦這些處理,由於構建靜態版本須要 大量的代碼 和 少許的思考,而添加交互須要 大量思考 和 少許的代碼。咱們將看到緣由。組件化

要構建你 app 的一個靜態版本,用於渲染數據模型, 您將須要構建複用其餘組件並使用 props 傳遞數據的組件。props 是將數據從 父級組件 傳遞到 子級 的一種方式。若是你熟悉 state 的概念,在構建靜態版本時 不要使用 state ** 。state 只用於交互,也就是說,數據能夠隨時被改變。因爲這是一個靜態版本 app,因此你並不須要使用 state 。

您能夠 自上而下 或 自下而上 構建。也就是說,您能夠從構建層次結構中頂端的組件開始(即從 FilterableProductTable 開始),也能夠從構建層次結構中底層的組件開始(即 ProductRow )。在更簡單的例子中,一般 自上而下 更容易,而在較大的項目中,自下而上,更有利於編寫測試。

在這一步結束時,你已經有了一個可重用的組件庫,用於渲染你的數據模型。組件將只有 render() 方法,由於這是你應用程序的靜態版本。層次結構頂部的組件( FilterableProductTable )應該接收你的數據模型做爲 prop 。若是您對基礎數據模型進行更改,並再次調用 ReactDOM.render(),UI 將同步更新。這有利於觀測UI的更新以及相關的數據變化,由於這中間沒有作什麼複雜的事情。React 的 單向數據流(也稱爲 單向綁定 )使全部模塊化和高性能。

♥ 小插曲 區分: Props(屬性) vs State(狀態)

步驟3: 肯定 UI state(狀態) 的最小(但完整)表示

爲了你的 UI 能夠交互,你須要可以觸發更改底層的數據模型。React 經過 state 使其變得容易。

要正確的構建應用程序,你首先須要考慮你的應用程序須要的可變 state(狀態) 的最小集合。這裏的關鍵是:不要重複你本身 (DRY,don't repeat yourself)。找出你的應用程序所需 state(狀態) 的絕對最小表示,而且能夠以此計算出你所需的全部其餘數據內容。正如咱們在構建一個 TODO 列表,只保留一個 TODO 元素數組便可;不須要爲元素數量保留一個單獨的 state(狀態) 變量。相反,當你要渲染 TODO 計數時,只須要獲取 TODO 數組的長度便可。

在咱們的例子中,主要出現的數據有兩個,一個是todo列表,一個是用戶輸入的新的todo項目,todo列表會根據用戶的添加和刪除發生變化,能夠認爲是屬於state的,對於用戶輸入的todo項目,咱們發現它只是一箇中間的臨時值,並不須要設置相應的變量進行存儲,因此咱們的最終的State是:

  • todo列表

步驟4:肯定 state(狀態) 的位置

既然是展現數據,首先要考慮數據存儲在哪裏,來自於哪裏。如今這裏放一句話——React提倡全部的數據都是由父組件來管理,經過props的形式傳遞給子組件來處理——先記住,接下來再解釋這句話。

上面提到,作一個todolist頁面須要一個父組件,兩個子組件。父組件固然就是todolist的『總指揮』,兩個子組件分別用來add和show、delete。用通俗的方式講來,父組件就是領導,兩個子組件就是協助領導開展工做的,一切的資源和調動資源的權利,都在領導層級,子組件配合領導工做,須要資源或者調動資源,只能申請領導的批准。

這麼說來就明白了吧。數據徹底由父組件來管理和控制,子組件用來顯示、操做數據,得通過父組件的批准,即——父組件經過props的形式將數據傳遞給子組件,子組件拿到父組件傳遞過來的數據,再進行展現。

另外,根據React開發的規範,組件內部的數據由state控制,外部對內部傳遞數據時使用 props 。這麼看來,針對父組件來講,要存儲todolist的數據,那就是內部信息(自己就是本身可控的資源,而不是『領導』控制的資源),用state來存儲便可。而父組件要將todolist數據傳遞給子組件,對子組件來講,那就是傳遞進來的外部信息(是『領導』的資源,交付給你來處理),須要使用props。

此時咱們的代碼能夠修改成:

// TodoList 組件是一個總體的組件,最終的React渲染也將只渲染這一個組件
// 該組件用於將『新增』和『列表』兩個組件集成起來
class TodoList extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        todoList: []
      }
    }

    render() {
        return (
            <div>
                <TypeNew  />
                <ListTodo />
            </div>
        );
    };
};

如今,已經肯定了應用所需 state(狀態) 的最小集合。接下來,須要肯定是哪一個組件可變,或者說哪一個組件擁有這些 state(狀態) 。

記住:React 單向數據流在層級中自上而下進行。這樣有可能不能當即判斷出狀態屬於哪一個組件。這經常是新手最難理解的一部分,試着按下面的步驟分析操做:

對於你應用中的每個 state(狀態) :

  • 肯定每一個基於這個 state(狀態) 渲染的組件。
  • 找出公共父級組件(一個單獨的組件,在組件層級中位於全部須要這個 state(狀態) 的組件的上面。愚人碼頭注:父級組件)。
  • 公共父級組件 或者 另外一個更高級組件擁有這個 state(狀態) 。
  • 若是找不出一個擁有該 state(狀態) 的合適組件,能夠建立一個簡單的新組件來保留這個 state(狀態) ,並將其添加到公共父級組件的上層便可。

步驟5:添加反向數據流

目前,構建的應用已經具有了正確渲染 props(屬性) 和 state(狀態) 沿着層次結構向下傳播的功能。如今是時候實現另外一種數據流方式:層次結構中深層的 form(表單) 組件須要更新 TodoList中的 state(狀態) 。

想一想咱們但願發生什麼。咱們指望當用戶改變表單輸入進行提交的時候,咱們更新 state(狀態) 來反映用戶的輸入。因爲組件只能更新它們本身的 state(狀態) ,TodoList將傳遞迴調到 TypeNew,而後在 state(狀態) 被更新的時候觸發。咱們可使用表單的onSubmit事件來接收通知。並且經過 TodoList傳遞的回調調用 setState(),而後應用被更新。同理刪除處理在ListTodo中觸發按鈕的點擊onClick事件來調用TodoList傳遞的回掉函數來更新刪除後的state。

最終咱們的代碼結果以下:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';


// TodoList 組件是一個總體的組件,最終的React渲染也將只渲染這一個組件
// 該組件用於將『新增』和『列表』兩個組件集成起來
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todoList: []
    }
    this.handleAddNewItem = this.handleAddNewItem.bind(this);
    this.handleDelItem = this.handleDelItem.bind(this);
  }

  handleAddNewItem(todo) {
    var todoList = this.state.todoList;
    if(todo === "") {
      return;
    }
    todoList.push(todo);
    this.setState({
      todoList: todoList
    })
  }

  handleDelItem(index) {
    var todoList = this.state.todoList;
    todoList.splice(index, 1);
    this.setState({
      todoList: todoList
    })
  }

  render() {
      return (
          <div>
              <TypeNew typeNewItem={this.handleAddNewItem} />
              <ListTodo todoList={this.state.todoList} onDelItem={this.handleDelItem} />
          </div>
      );
  }
};

// TypeNew 組件用於新增數據,
class TypeNew extends Component {
  constructor(props){
    super(props);
    this.onHandleAddNewItem = this.onHandleAddNewItem.bind(this);
  }
  onHandleAddNewItem(e) {
    e.preventDefault();
    var inputDom = this.textInput;
    var newthing = inputDom.value.trim();
    this.props.typeNewItem(newthing);
    inputDom.value = '';
  }
  render() {
      return (
          <form onSubmit={this.onHandleAddNewItem}>
              <input type="text" ref={(input) => { this.textInput = input; }} placeholder="typing a newthing todo" autoComplete="off" />
          </form>
      );
  }
};

// ListTodo 組件用於展現列表,並能夠刪除某一項內容,
class ListTodo extends Component {
  constructor(props){
    super(props);
    this.onHandleDelItem = this.onHandleDelItem.bind(this);
  }

  onHandleDelItem(e){
    this.props.onDelItem(e.target.getAttribute("data-key"));
  }

  render() {
      return (
          <ul id="todo-list">
              {
                // this.props.todo 獲取父組件傳遞過來的數據
                // {/* 遍歷數據 */}
                this.props.todoList.map(function (item, i) {
                    return (
                        <li>
                            <label>{item}</label>
                            <button onClick={this.onHandleDelItem} data-key={i}>delete</button>
                        </li>                     
                      );
                }.bind(this))
              }
          </ul>
      );
  }
};

// 將 TodoList 組件渲染到頁面
ReactDOM.render(<TodoList />, document.getElementById('root'));

上面就是咱們一步步進行分析拆分來設計咱們的應用程序的過程。因爲例子比較簡單,因此相對來講思路更加清晰,當咱們面對大型的應用程序時,每每分析方式會有所變化,可是根本的原理是不變的。


參考資料

React 的編程思想
使用React並作一個簡單的to-do-list

相關文章
相關標籤/搜索