翻譯一篇讓你輕鬆理解React原理的好文章(下)

原文連接javascript

若是尚未閱讀 翻譯一篇讓你輕鬆理解React原理的好文章(上),請先閱讀。html

第2部分將接着咱們上次講到的內容。這篇文章將着重於改進咱們簡單的待辦事項列表。當前的實現由一組函數組成,它們呈現完整的應用,幷包含一個管理咱們狀態的簡單store。可是,咱們須要作一些事情來改進咱們的應用。 這裏是一個與當前示例和代碼的連接。java

首先,咱們沒有妥善處理咱們的事件。咱們的組件不處理任何事件。在React中,事件觸發數據流。 這意味着咱們的組件應該觸發事件。舉個例子咱們的ItemRow方法應該調用一個經過props傳遞過來的方法來執行click事件。咱們如何作到這一點?node

這是一個初步嘗試。react

function ItemRow(props) {
  var className = props.completed ? 'item completed' : 'item';
  return $('<li>')
    .on('click', props.onUpdate.bind(null, props.id))
    .addClass(className)
    .attr('id', props.id)
    .html(props.text);
}
複製代碼

咱們在列表元素中添加了一個事件監聽器,當單擊該項目時,它將觸發一個點擊事件。onUpdate函數經過props傳遞過來。git

如今咱們須要一個爲咱們建立元素的函數。github

function createElement(tag, attrs, children) {
  var elem = $('<' + tag + '>');
  for (var key in attrs) {
    var val = attrs[key];
    if (key.indexOf('on') === 0) {
      var event = key.substr(2).toLowerCase();
      elem.on(event, val)
    } else {
      elem.attr(key, val);
    }
  }
  return elem.html(children);
}
複製代碼

經過實現createElement函數,咱們能夠將ItemRow函數重構爲以下所示:數組

function ItemRow(props) {
  var className = props.completed ? 'item completed' : 'item';
  return createElement('li', { 
      id: props.id, 
      class: className, 
      onClick: props.onUpdate.bind(null, props.id) 
    }, props.text
  );
}
複製代碼

須要注意的是,在React的 createElement中建立的是javaScript對象,是用來表示Dom元素,而不是Dom元素自己。另外一方面,讓咱們來看看當您編寫JSX時實際發生了什麼。app

如下JSX示例:ide

return ( <div id='el' className:'entry'>Hello</div>)
複製代碼

被轉換爲

var SomeElement = React.createElement('div', {id: 'el', className: 'entry'}, 'Hello');
複製代碼

調用SomeElement()將返回這樣的對象:

{
  // ..
  type: 'div', 
  key: null, 
  ref: null, 
  props: {children: 'Hello', className: 'entry', id: 'el' }, 
}
複製代碼

對於更詳細的認識,能夠讀React Components, Elements, and Instances.

回到咱們的例子。onUpdate從哪裏來?

咱們在render中定義了一個updateState函數,並經過props將它傳遞到ItemList組件。

function render(props, node) {
  function updateState(toggleId) {
    state.items.forEach(function(el) {
      if (el.id === toggleId) {
        el.completed = !el.completed;
      }
    });
    store.setState(state);
  }
  node.empty().append([ItemsList({
    items: props.items,
    onUpdate: updateState
  })]);
}
複製代碼

ItemsList方法自己傳遞onUpdate給每個ItemRow

function extending(base, item) {
  return $.extend({}, item, base);
}
function ItemsList(props) {
  return createElement(‘ul’, {}, props.items
     .map(extending.bind(null, {
       onUpdate: props.onUpdate
     }))
     .map(ItemRow));
}
複製代碼

經過採用這種方法,咱們實現瞭如下步驟: 數據流從組件層次結構向下流而響應事件則向上流。 這也意味着咱們能夠刪除先前定義的全局監聽器移動到render中,這就是前面提到的updateState。

更多的改進。

讓咱們用一個方法生成input和button元素。因此最終咱們的標記只包含一個div。

<div id="app"></app>
複製代碼

例如,能夠很容易地建立這樣的input元素。

var input = createElement(‘input’, { id: ‘input’ });
複製代碼

咱們還能夠移動一個全局的監聽button點擊的方法到SearchBar方法中。SearchBar返回一個input和button元素,經過一個來自props中的回調方法來處理點擊事件。

function SearchBar(props) {
  function onButtonClick(e) {
    var val = $('#input').val();
    $('#input').val('');
    props.update(val);
    e.preventDefault();
  }
  
  var input = createElement('input', { id: 'input' });
  var button = createElement('button', 
   { id: 'add', onClick: onButtonClick.bind(null)}, 'Add');
  return createElement(‘div’, {}, [input, button]);
}
複製代碼

如今咱們的render方法須要調用SearchBar而且傳遞一個適當的props。在更新render方法以前,讓咱們花一點時間來思考下如何重構。讓咱們暫時忽略咱們的store處理高級別組件中的狀態。直到如今,全部的方法都是無狀態的,咱們將建立一個方法來處理狀態而且在合適的時候更新子組件。

容器組件 讓咱們建立高級別容器。也能夠閱讀 Presentational and Container Components 幫助理解。下面應該給你一個更好的建議。

因此咱們實現容器組件App。它所作的就是調用SearchBar和ItemList方法,並返回一個元素數組。咱們要從新考慮Render方法。大部分代碼會簡單地進入App組件。

在看App組件以前,讓咱們快速瀏覽一下咱們的render:

function render(component, node) {
  node.empty().append(component);
}
render(App(state), $(‘#app’));
複製代碼

render如今僅僅負責將應用呈如今給定的節點中。React比這個簡單的實現要複雜的多。咱們只是將一個元素樹附加到一個已定義的根元素中,但它應該足以表達一個高層次的概念。

咱們的App組件如今變成了真正的容器組件。

function App(props) {
  function updateSearchState(value) {
    state.items.push({id:state.id++, text:value, completed:false});
    store.setState(state);
  }
 
  function updateState(toggleId) {
    state.items.forEach(function(el) {
      if (el.id === toggleId) {
        el.completed = !el.completed;
      }
    });
    store.setState(state);
  }
  return [SearchBar({update: updateSearchState}), 
          ItemsList({items: props.items, onUpdate: updateState})];
}
複製代碼

咱們還須要再作一件事。咱們正在訪問一個全局store,並調用setState來從新render。

讓咱們重構App組件,使其從新繪製他的子組件而無需調用store。咱們如何作到這一點?

首先,讓咱們忽略store並找出如何調用setState方法來從新繪製組件和它的子元素。

咱們須要在這個高級組件中跟蹤當前狀態,而且在setState發生變化時也要注意從新繪製DOM。這裏有一個很是原始的方法:

function App(props) {
  function getInitialState(props) {
    return { items: [], id: 0 };
  }
  var _state = getInitialState(), _node = null;
  function setState(state) {
    _state = state;
    render();
  }
  // ...
}
複製代碼

咱們經過調用getInitialState來初始化咱們的狀態,當咱們經過setState更新狀態時,咱們調用在App組件中調用它本身的render方法。

建立屬於App組件本身的render方法。在render方法中要麼建立一個根節點,要麼在狀態更改時簡單地更新節點。

// naive implementation of render…
function render() {
  var children = [SearchBar({ update: updateSearchState}),
                  ItemsList({ items: _state.items,
                              onUpdate: updateState
                            })];
  if (!_node) {
    return _node = createElement(‘div’, { class: ‘top’ }, children);
  } else {
    return _node.html(children);
  }
}
複製代碼

這爲了表達一個事實,在React組件內調用setState,不繪製完整的應用,只繪製組件和它的子元素,即不調用全局render方法只調用App組件的render方法。

這是更新後的全局render方法的調用。咱們建立App組件時沒有任何參數,只是依賴於getInitialState返回初始狀態。

function render(component, node) {
  node.empty().append(component);
}
render(App(), $(‘#app’));
複製代碼

Check the functioning example including the complete code.

細化

若是咱們有一個泛型函數,它會返回一個帶有setState函數的對象,而且可以辨別傳入的props和組件狀態。這即是一個簡單的建立組件的方法。

這樣的第一版:

var App = createClass({
  updateSearchState: function(string) { //... },
  updateState: function(obj) { //... },
  render: function() {
    var children = [SearchBar({ update: this.updateSearchState}),
                  ItemsList({ items: this.state.items,
                              onUpdate: this.updateState
                            })];
    return createElement(‘div’, { class: ‘top’ }, children);
  }
})
複製代碼

好消息是,React提供了多個選項來建立組件,包括使用React.createClass。其餘選項包括ES6中的class和無狀態的函數 for more information consult the docs

咱們的示例應用程序介紹了組件層次結構中的數據流和事件流。咱們已經瞭解瞭如何在組件內部處理狀態。要了解和學習更多關於React的知識。下面的連接應該有幫助。

進一步的學習

Thinking in React

Getting Started

JSX

React-howto

Removing User Interface Complexity, or Why React is Awesome

Presentational and Container Components

React Components, Elements, and Instances

相關文章
相關標籤/搜索