抛开 React 学习 React 第一部分

hubuke 8年前
   <p>当我们谈起 <strong>React</strong> 的时候总是有很多疑惑。下面简单地介绍了 React 以及他的一些底层原理。</p>    <p><strong>你能学到什么?</strong> 当你第一部分和第二部分都学习完之后,你也许就会知道你为什么需要 React 以及 Redux 类似的 state container (状态管理器)。</p>    <p><strong>在学习之前你要掌握什么?</strong> 不需要了解 JSX,ES6/ES*,Webpack,Hot Reloading,也不需要理解 Virtual DOM,甚至连 React 本身都不需要。</p>    <p>好了,首先阅读下 <a href="/misc/goto?guid=4959671216531574725">jQuery 实现的 TodoMVC</a> 源码。</p>    <p>也许你会注意到有一个叫 <code>render</code> 的方法,他会在某个事件触发或者数据更新的时候被调用。现在,我们从头来实现一个例子:当 <code>input</code> 的值改变时,调用 <code>render</code> 函数,并且更新 DOM 元素。</p>    <pre>  <code class="language-javascript">var state = {value: null}    $('#input').on('keyup', function () {    state.value = $(this).val().trim()    render()  })    function render () {    $('#output').html(state.value)  }    render()  </code></pre>    <p>我们使用一个全局变量 <code>state</code> 来同步所有的东西。也就是说,当 <em>input</em> 的值改变时会更新两样东西:</p>    <ol>     <li>更新整个应用的 <code>state</code></li>     <li>更新 DOM(根据应用当前的 <code>state</code> 来调用 <code>render</code> 函数)</li>    </ol>    <p>先记住这些,我们等一下就会返回来。</p>    <p>现在,我们有了一个新想法:</p>    <pre>  <code class="language-javascript">function output(text) {    return '<div>' + text + '</div>'  }  </code></pre>    <p>显然,调用 <code>output(foo)</code> 就会返回 <code>'<div>foo</div>'</code>。</p>    <p>那么接下来:</p>    <pre>  <code class="language-javascript">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  </code></pre>    <p>上面的函数都是基于一个 <em>text(input)</em> 然后返回一个 <em>string(text)</em> 。调用 <code>header</code> 的时候传入相同的参数(<em>input</em>),都会得到相同的字符串(<em>output</em>)。如果你在想思考 React 中的 <strong>stateless functions</strong> 的话,那么这个其实就是一个简化版。只是 Stateless functions 会返回一个 React Element 而不是一个简单的 string,但是思路是一样的。</p>    <p>既然这样,我们就把这个想法应用到我们之前的例子中。我们添加了一个 <code>button</code>,用来添加 todo item 。</p>    <pre>  <code class="language-javascript">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()  </code></pre>    <p>效果图如下。我们的应用现在可以显示所有的 todo,也可以改变每个 todo 的状态(进行中或者完成)。</p>    <p><img alt="抛开 React 学习 React 第一部分" src="https://simg.open-open.com/show/f52529408db7ea03e9dfbe3845497923.png" width="800" height="270"></p>    <p>在上面,我们定义了两个 <code>click</code> 事件,当他们触发时就会更新我们的 <code>state</code> 以及调用 <code>render</code> 函数。而 <code>render</code> 函数会创建一个 todo list 。 <code>state</code> 作为中间媒介,简化了事件和 DOM 元素之间的交互,而不是** 通过事件来直接操作 DOM ** (不需要定义每个 DOM 元素和每个事件以及他们之间的关系)。当某个 <em>action</em>(如 click 事件) 触发之后,<code>state</code> 就会更新,接着调用 <code>render</code> 函数,最后我们的应用就会更新。这样一来,就简化了好多复杂的交互。</p>    <p>上面的例子是很好的,我们可以通过填输入框来增加一个 todo,当点击 todo 的时候,能够改变他的状态。但,我们不妨再来重构他。</p>    <p>可以看到,<code>render</code> 函数有一点乱。我们不妨创建一个函数,他接收一个参数(input),然后基于这个参数返回一个字符串(output)。</p>    <pre>  <code class="language-javascript">function ItemRow (props) {    var className = props.completed ? ' item completed' : 'item'    return '<li className="' + className +' ">' + props.text + '</li>'  }  </code></pre>    <pre>  <code class="language-javascript">function ItemsList (props) {    return '<ul>' + props.items.map(ItemRow).join('') + '</ul>'  }  </code></pre>    <p>看,现在我们的 <code>render</code> 函数优美多了:</p>    <pre>  <code class="language-javascript">function render () {    $('#list').html(ItemsList({      items: state.items    }))  }  </code></pre>    <p>如果 <code>render</code> 函数并不知道 <code>state</code> 是什么,而是期望一个 <em>input</em> 作为参数呢?好吧,现在我们可以重构一下 <code>render</code> 函数,他期望接收一个 <code>props</code> 对象(这其实就是 React Component 所期望的。</p>    <pre>  <code class="language-javascript">function render (props) {    $('#list').html(ItemsList({      items: props.items    }))  }  </code></pre>    <p>现在,<code>render</code> 函数并不依赖外部的状态(state),这使得我们在调用 <code>render</code> 时可以随便传入一个 <em>input</em> ,也就意味着我们的应用重新渲染时,相同的 <em>input</em> 会有相同的 <em>output</em> 。需要注意的是,DOM 操作其实是一个 side effect,但是现在我们暂时忽略他。</p>    <p>把 <code>state</code> 从 <code>render</code> 函数中分离出来,可以使得我们很容易实现 <code>Undo/Redo</code>。这也意味着每当 * 当前的 state * 改变时,我们能够创建一个 history ,保存这个当前的 state 。</p>    <p>另外一个优化就是传一个 root node 作为参数,而不是写死在 <code>render</code> 函数里面:</p>    <pre>  <code class="language-javascript">function render (props, node) {    node.html(ItemsList({      items: props.items    }))  }  </code></pre>    <p>因此,我们可以这样调用 <code>render</code> 函数:</p>    <pre>  <code class="language-javascript">render(state, $('#list'))  </code></pre>    <p>我们会很容易想到:当 <code>state</code> 改变的时候,能不能自动地更新应用?也就是,不用手动地调用 <code>render</code> 函数。</p>    <p>现在,我们来创建一个 <em>store</em> ,他的作用是当 <code>state</code> 改变之后,就立马调用 <code>render</code> 函数。下面的实现虽然简单,但也是一个 advanced state container 的雏形。</p>    <pre>  <code class="language-javascript">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})      }    }  }  </code></pre>    <p>现在,我们更新 <code>state</code> 只需要简单地调用 <code>setState</code> 方法。只要 <code>state</code> 一改变,我们的 <code>render</code> 函数就会被调用:</p>    <pre>  <code class="language-javascript">var store = createStore(store)    store.onUpdate('rootRender', function (state) {    render(state, $('#list'))  })  </code></pre>    <p><a href="/misc/goto?guid=4959671216640253051">点击这里可查看完整的代码</a></p>    <p><strong>现在我们学会了什么?</strong> 我们知道了简单的单向数据流(one-way data flow)的原则。我们给 <code>render</code> 函数传了一个 <code>state</code> 参数,然后 <code>state</code> 就会像流水一样,流到 <code>render</code> 函数的每个层次中。比如,<code>ItemRow</code> 函数需要 <code>ItemsList</code> 给他传进正确的参数。</p>    <p>我们已经创建了多个组件(component),并且我们把这些组件组合(compose)在一起。回想一下前面的 <code>header</code> 例子,我们把 <code>div</code> 和 <code>h2</code> 函数组合成了一个 <code>header</code> 函数。并且,这些函数都是 <em>pure function</em> ,这使得所有更新都是可预测的。</p>    <p>并且,我们使用了 <em>store</em> 来管理我们的 <code>state</code>。</p>    <p>而,React 会用更好更优美的方法来实现上面这些东西。组件(组合),使用 Virtual DOM 优化渲染,单向数据流等等。</p>    <blockquote>     <p>…we can focus on examining React’s true strengths: composition, unidirectional data flow, freedom from DSLs, explicit mutation and static mental model.</p>     <p style="text-align:right">出自 <a href="/misc/goto?guid=4959671216741985980">Dan Abramov - you're missing the point of react</a></p>    </blockquote>    <p>我们可以优化的东西还有很多,比如继续优化 state container,重构我们的 listeners,实现 undo/redo,以及更多更好的 feature。<strong>这些东西我们都会在第二部分中呈现</strong>。</p>    <p><em>原文链接:<a href="/misc/goto?guid=4959671216839618689">Learning React Without Using React Part 1</a></em></p>    <p>来自:http://qianduan.guru/2016/03/27/Learning-React-Without-Using-React-Part1/</p>