拋開 React 學習 React 第一部分

hubuke 8年前發布 | 9K 次閱讀 React JavaScript開發

當我們談起 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 的狀態(進行中或者完成)。

拋開 React 學習 React 第一部分

在上面,我們定義了兩個 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,但是現在我們暫時忽略他。

staterender 函數中分離出來,可以使得我們很容易實現 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 例子,我們把 divh2 函數組合成了一個 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

來自:http://qianduan.guru/2016/03/27/Learning-React-Without-Using-React-Part1/

 本文由用戶 hubuke 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!