拋開 React 學習 React 第一部分

大家好,我是「前端外刊評論」的新成員追客,我想要成為一名前端魔法師。

當我們談起 React 的時候總是有很多疑惑。下面簡單地介紹了 React 以及他的一些底層原理。

你能學到什麼? 當你第一部分和第二部分都學習完之後,你也許就會知道你為什麼需要 React 以及 Redux 類似的 state container(狀態管理器)。

在學習之前你要掌握什麼? 不需要了解 JSX,ES6/ES*,Webpack,Hot Reloading,也不需要理解 Virtual DOM,甚至連 React 本身都不需要。

好了,首先閱讀下 jQuery 實現的 TodoMVC 源碼。

也許你會注意到有一個叫 render 的方法,他會在某個事件觸發或者數據更新的時候被調用。現在,我們從頭來實現一個例子:當 input 的值改變時,調用 render 函數,並且更新 DOM 元素。

var state = {value: null}$("#input").on("keyup", function () { state.value = $(this).val().trim() render()})function render () { $("#output").html(state.value)}render()

我們使用一個全局變數 state 來同步所有的東西。也就是說,當 input 的值改變時會更新兩樣東西:

  1. 更新整個應用的 state
  2. 更新 DOM(根據應用當前的 state 來調用 render 函數)

先記住這些,我們等一下就會返回來。

現在,我們有了一個新想法:

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

上面的函數都是基於一個 text(input) 然後返回一個 string(text) 。調用 header 的時候傳入相同的參數(input),都會得到相同的字元串(output)。如果你在想思考 React 中的 stateless functions的話,那麼這個其實就是一個簡化版。只是 Stateless functions 會返回一個 React Element 而不是一個簡單的 string,但是思路是一樣的。

既然這樣,我們就把這個想法應用到我們之前的例子中。我們添加了一個 button,用來添加 todo item 。

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", function () { var toggleId = parseInt($(this).attr("id")) state.items.forEach(function (el) { 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()

效果圖如下。我們的應用現在可以顯示所有的 todo,也可以改變每個 todo 的狀態(進行中或者完成)。

在上面,我們定義了兩個 click 事件,當他們觸發時就會更新我們的 state 以及調用 render 函數。而 render 函數會創建一個 todo list 。 state 作為中間媒介,簡化了事件和 DOM 元素之間的交互,而不是通過事件來直接操作 DOM (不需要定義每個 DOM 元素和每個事件以及他們之間的關係)。當某個 action(如 click 事件) 觸發之後,state 就會更新,接著調用 render 函數,最後我們的應用就會更新。這樣一來,就簡化了好多複雜的交互。

上面的例子已經很好了,我們可以通過填寫輸入框來增加一個 todo,當點擊 todo 的時候,能夠改變他的狀態。但,我們不妨再來重構他。

可以看到,render 函數有一點亂。我們不妨創建一個函數,他接收一個參數(input),然後基於這個參數返回一個字元串(output)。

function ItemRow (props) { var className = props.completed ? " item completed" : "item" return "<li className="" + 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 函數並不知道 state 是什麼,而是期望一個 input 作為參數呢?好吧,現在我們可以重構一下 render 函數,他期望接收一個 props 對象(這其實就是 React Component 所期望的。

function render (props) { $("#list").html(ItemsList({ items: props.items }))}

現在,render 函數並不依賴外部的狀態(state),這使得我們在調用 render 時可以隨便傳入一個input ,也就意味著我們的應用重新渲染時,相同的 input 會有相同的 output 。需要注意的是,DOM 操作其實是一個 side effect,但是現在我們暫時忽略他。

把 state 從 render 函數中分離出來,可以使得我們很容易實現 Undo/Redo。這也意味著每噹噹前的 state 改變時,我們能夠創建一個 history ,保存這個當前的 state 。

另外一個優化就是傳一個 root node 作為參數,而不是寫死在 render 函數裡面:

function render (props, node) { node.html(ItemsList({ items: props.items }))}

因此,我們可以這樣調用 render 函數:

render(state, $("#list"))

我們會很容易想到:當 state 改變的時候,能不能自動地更新應用?也就是,不用手動地調用render 函數。

現在,我們來創建一個 store ,他的作用是當 state 改變之後,就立馬用 render 函數。下面的實現雖然簡單,但也是一個 advanced state container 的雛形。

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}) } }}

現在,我們更新 state 只需要簡單地調用 setState 方法。只要 state 一改變,我們的 render 函數就會被調用:

var store = createStore(state)store.onUpdate("rootRender", function (state) { render(state, $("#list"))})

點擊這裡可查看完整的代碼

現在我們學會了什麼? 我們知道了簡單的單向數據流(one-way data flow)的原則。我們給 render函數傳了一個 state 參數,然後 state 就會像流水一樣,流到 render 函數的每個層次中。比如,ItemRow 函數需要 ItemsList 給他傳進正確的參數。

我們已經創建了多個組件(component),並且我們把這些組件組合(compose)在一起。回想一下前面的 header 例子,我們把 div 和 h2 函數組合成了一個 header 函數。並且,這些函數都是pure function ,這使得所有更新都是可預測的。

並且,我們使用了 store 來管理我們的 state。

而,React 會用更好更優美的方法來實現上面這些東西。組件(組合),使用 Virtual DOM 優化渲染,單向數據流等等。

…we can focus on examining React』s true strengths: composition, unidirectional data flow, freedom from DSLs, explicit mutation and static mental model.

出自 Dan Abramov - you"re missing the point of react

我們可以優化的東西還有很多,比如繼續優化 state container,重構我們的 listeners,實現 undo/redo,以及更多更好的 feature。這些東西我們都會在第二部分中呈現。

原文鏈接:Learning React Without Using React Part 1


推薦閱讀:

有沒有推薦設UI計師學的代碼,例如xcode,JQ等輕體量上手快的IDE,主要為了實現動效和前端對接?
新手如何學習 jQuery?
為什麼有很多人明明基礎都不會,卻一直在討論jQuery?
jQuery UI 有哪些缺點?
jQuery 解決了怎樣的問題?

TAG:React | Redux | WebComponents | jQuery |