如果還沒有閱讀 翻譯 | 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的知識。下面的連結應該有幫助。
進一步的學習
Removing User Interface Complexity, or Why React is Awesome