翻譯 | Learning React Without Using React Part 2

魚籽醬發表於2019-02-24

原文連結

如果還沒有閱讀 翻譯 | Learning React Without Using React Part 1,請先閱讀。

第2部分將接著我們上次講到的內容。這篇文章將著重於改進我們簡單的待辦事項列表。當前的實現由一組函式組成,它們呈現完整的應用,幷包含一個管理我們狀態的簡單store。但是,我們需要做一些事情來改進我們的應用。 這裡是一個與當前示例和程式碼的連結。

首先,我們沒有妥善處理我們的事件。我們的元件不處理任何事件。在React中,事件觸發資料流。 這意味著我們的元件應該觸發事件。舉個例子我們的ItemRow方法應該呼叫一個通過props傳遞過來的方法來執行click事件。我們如何做到這一點?

這是一個初步嘗試。

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傳遞過來。

現在我們需要一個為我們建立元素的函式。

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時實際發生了什麼。

以下JSX示例:

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

相關文章