首先了解概念然後學習框架
當第一次接觸React會有很多的困惑。文章將介紹React和它的基本理念。
通過閱讀文章你將會對於為什麼需要React和Redux或者其他狀態容器有一個更好的理解。
學習時你不需要使用 JSX,ES6/ES*, Webpack,熱載入,理解虛擬DOM,甚至不需要使用React自己。
首先:
讓我們看一下 TodoMVC code for jQuery。
您將注意到有一個方法render(),每當事件被觸發或資料被更新時,它就會被呼叫。讓我們構建一個例子,當改變一個輸入值,觸發render方法更新DOM。
var state = {value: null};
$(`#input`).on(`keyup`, function() {
state.value = $(this).val().trim();
render();
});
function render() {
$(`#output`).html(state.value);
}
render();
複製程式碼
我們用一個全域性狀態state變數來儲存同步所有的值。在一次輸入後做了兩件事:
- 它更新了全域性狀態state。
- 呼叫render方法。現在render方法根據全域性狀態state來更新DOM。
記住這個例子。我們馬上就會講到。
下面是另一個需要考慮的地方:
function output(text) {
return `<div>` + text + `</div>`;
}
複製程式碼
呼叫output(`foo`) 將返回 `<div>foo</div>`。
現在考慮下面的例子:
function h2(text) {
return `<h2>` + text + `</h2>`;
}
function div(text) {
return `<div>` + text + `</div>`;
}
function header(text) {
return div(h2(text));
}
console.log(header(`foo`) === `<div><h2>foo</h2></div>`); //true;
複製程式碼
我們編寫的方法的返回值基於一個字串引數。用同樣的引數呼叫header方法總是返回一個同樣的元素字串。如果你曾經在React中考慮過無狀態元件的話,這是一個簡化版本。React中無狀態元件將返回一個React元素而不是一個簡單的元素字串。
我們知道我們可以建立返回元素字串的函式。讓我們回到最初的示例,並擴充套件它顯示add按鈕和專案列表。
var state = {items: [], id: 0};
$(`#add`).on(`click`, function (e) {
var value = $(`#input`).val().trim();
$(`#input`).val(``);
state.items.push({id: state.id++, text: value, completed: false});
render();
});
$(`#list`).on(`click`, `.item`, function () {
var toggleId = parseInt($(this).attr(`id`));
state.items.forEach(function (el) {
if (el.id === toggleId) {
el.completed = !el.completed;
}
});
render();
});
function render() {
var items = state.items.map(function (item) {
var completed = item.completed ? `completed` : ``;
return `<li class="item + ` + completed + `" id="` + item.id + `">(` + item.id + `) ` + item.text + `</li>`;
}).join(``);
var html = `<ul>` + items + `</ul>`;
$(`#list`).html(html);
}
render();
複製程式碼
我們有一個簡單的待辦列表,包括切換專案狀態(待辦或完成)的能力。
用一組已定義的事件函式更新全域性狀態state然後呼叫我們的render方法。然後render方法建立專案列表並且新增專案到專案列表中。我們新增了狀態來簡化事件和元素之間的互動。而不是定義每個事件和元素以及它們各自的關係,我們總是在一個行為被觸發後立即更新。它簡化了處理複雜的互動。當狀態改變時,我們總是呼叫render方法。
這已經很有效了。我們可以通過輸入框輸入標題來新增條目,我們可以通過點選條目本身來切換條目狀態。
現在render方法看起來有些亂。讓我們嘗試建立一個基於傳入的props來返回元素字串的方法。
function ItemRow(props) {
var className = props.completed? ` item completed` : `item`;
return `<li class="` + className +`">` + props.text + `</li>`;
}
function ItemsList(props) {
return `<ul>` + props.items.map(ItemRow).join(``) + `</ul>`;
}
複製程式碼
我們已經清理了render函式。
function render() {
$(`#list`).html(ItemsList({items : state.items}));
}
複製程式碼
如果render自身不知道狀態,並且期望使用一個輸入的引數代替呢?這很容易實現,我們可以用props簡單的重構render方法。(這就是React元件所期望的。)
function render(props) {
$(`#list`).html(ItemsList({items : props.items}));
}
複製程式碼
render不需要知道外部狀態。這使我們能夠簡單地呼叫render方法傳入任何已定義的狀態。這也意味著傳入的狀態不改變那麼重新繪製將一次又一次地返回相同的結果。雖然我們應該記住,這是Dom的一個副作用,但讓我們暫時忽略這個事實。
通過將顯式狀態與繪製部分分離,我們可以輕鬆實現撤銷/重做。這意味著我們可以建立一個歷史記錄並在每次更改時儲存。
另一個優化是將根節點作為引數傳入,而不是在render函式中顯式地定義節點。
function render(props, node) {
node.html(ItemsList({items : props.items}));
}
複製程式碼
因此,現在我們可以簡單地呼叫render方法並且傳入已定義的狀態和根節點。
render(state, $(`#list`));
複製程式碼
但是,在更新狀態之後,如何不需要顯式地呼叫render方法呢?
讓我們構建一個store,當狀態從應用的任何地方更新時,只需呼叫我們的render方法。這是第一次嘗試。雖然這個實現是非常基礎的,這是建立一個更高階的狀態容器的好起點。
function createStore(initialState) {
var _state = initialState || {}, _listeners = [];
function updateListeners(state) {
_listeners.forEach(function(listener) {
listener.cb(state);
});
}
return {
setState: function(state) {
_state = state;
updateListeners(state);
},
getState: function() {
return _state;
},
onUpdate: function(name, cb) {
_listeners.push({name: name, cb: cb});
}
};
}
複製程式碼
我們可以簡單地通過儲存setState方法更新狀態。一旦狀態改變後,我們的render函式會被呼叫。
var store = createStore(state);
store.onUpdate(`rootRender`, function(state) {
render(state, $(`#list`));
});
複製程式碼
我們現在看到了什麼?
我們已經看到單向資料流的簡單原理。我們將狀態傳遞給render函式,狀態沿著函式的層次結構傳入。例如ItemList傳遞一個適當的props給ItemRow。我們已經建立了元件,並將這些元件組合成更大的元件。記得標題的例子,我們將div和h2函式組合到header中。這裡我們處理的是純函式。這使得我們所有的更新都可以預測。我們現在對我們的狀態也有一個清晰的認識。
在React中,這會以一種非常有效的方式進行。通過實現虛擬DOM、單向資料流等實現架構,優化渲染。
…我們可以專注於研究React真正的優勢:架構,單向資料流,從DSLs中解脫,明確的預期和靜態思想模型。
Dan Abramov(medium.com/@dan_abramo…)
我們能做的還有很多,包括建立一個改進的狀態容器, 重構監聽器,實現撤銷/重做和更多好的功能。