抛开 React 学习 React(1)

首先阅读下 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>'
    }

那么接下来:

   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};
    //click回调事件操作了数据,并没有直接操作DOM
    $('#add').on('click', function (e) {
      var value = $('#input').val().trim()
      $('#input').val('')

      state.items.push({
         id: state.id++,
         text: value,
         completed: false
      })

      render();
    })
    //click回调事件操作了数据,并没有直接操作DOM
    $('#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();

在上面,我们定义了两个 click 事件,当他们触发时就会更新我们的 state 以及调用 render 函数。而 render 函数会创建一个 todo list 。 state 作为中间媒介,简化了事件和 DOM 元素之间的交互,而不是通过事件来直接操作 DOM (不需要定义每个 DOM 元素和每个事件以及他们之间的关系)。当某个 action (如 click 事件) 触发之后, state 就会更新,接着调用 render 函数,最后我们的应用就会更新。这样一来,就简化了好多复杂的交互。
可以看到, 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>'
    }
    function render (props) {
      $('#list').html(ItemsList({
        items: props.items
      }))
    }

render 函数并不依赖外部的状态(state),这使得我们在调用 render 时可以随便传入一个 input ,也就意味着我们的应用重新渲染时,相同的 input 会有相同的 output 。需要注意的是,DOM 操作其实是一个 side effect,但是现在我们暂时忽略他。
把 state 从 render 函数中分离出来,可以使得我们很容易实现 Undo/Redo 。
另外一个优化就是传一个 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 优化渲染,单向数据流等等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值