拋開 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 的值改變時會更新兩樣東西:
- 更新整個應用的
state
- 更新 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(store)
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.
我們可以優化的東西還有很多,比如繼續優化 state container,重構我們的 listeners,實現 undo/redo,以及更多更好的 feature。這些東西我們都會在第二部分中呈現。
原文鏈接:Learning React Without Using React Part 1
來自:http://qianduan.guru/2016/03/27/Learning-React-Without-Using-React-Part1/