美文网首页
不看React学会React(一)

不看React学会React(一)

作者: 一个废人 | 来源:发表于2018-12-09 21:18 被阅读8次

    原文地址:https://medium.com/javascript-inside/learn-the-concepts-part-1-418952d968cb

    学习React的时候总会有许多疑惑。接下来的文章会简单介绍React及其底层原理。

    你将从(一)(二)中学到什么?对为什么需要React(也许还包括Redux和其他状态管理器)高层次上的理解。

    你不需要什么:不需要了解JSX,ES6/ES*,Webpack,热重载,虚拟DOM如何工作,甚至React本身。

    首先要做的事:

    让我们看一看这段代码TodoMVC code for jQuery.

    你会看到一个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.更新app的state;2.调用render函数。现在,render函数会根据app的state更新DOM。

    记住这个例子,我们一会再回头看看。

    另外一个想法:

    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;
    

    我们写了一堆函数,返回值是根据input值得来的字符串。以相同的参数调用header函数时,它始终返回相同的字符串。如果你曾经对React的无状态函数不知所云,那么这就是一个简单的例子。只不过React的无状态函数返回的是一个React元素而不是个字符串,不过你懂是这个意思就行了。

    好了,现在我们可以创建返回字符串的函数了。让我们回头去扩展我们一开始的例子,给它增加一个add按钮和一个items列表。

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

    下面是现在的视图。一个简单的拥有更改items state功能(从active到finished)的todolist。


    todolist.png

    一组已定义的事件更新state然后调用render函数。render函数创建了item列表,并向list元素加入字符串。为了简化事件和DOM元素的交互,我们加入了state。更新一发生,就出发相应动作,而不必要非得去定义每一个事件、每一个元素以及它们间的关系。这样,我们就简化了复杂的交互。当state发生变化,我们就调用render方法。

    这已经很棒了。我们能通过在input框输入标题向list中加入items,同时我们也能通过点击item本身来改变它们的状态。

    render方法看起来很乱。我们试着创建一个以input为参数的方法,它返回一个基于参数input的字符串。

    function ItemRow(props) { 
        var className = props.completed? ' item completed' : 'item';
        return '<li class="' + 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是什么。这样,只要传进来一个input,我们就简单以其为参数调用render方法就好了,同时也意味着,render会一次又一次返回相同的结果。我们也该牢记,Dom会起到副作用,但我们先不管它。

    从render分离出精确的state后,我们就能非常容易的实现Undo/Redo的例子了。现在我们能够创建历史记录,每当改变发生就保存当前的state。

    另一个优化是,给render传入根节点,这样就不必在render方法内部定义一个指定的节点了。

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

    现在我们能简单地以传入state和根节点的方式来调用render了。

    render(state, $('#list'));
    

    那么,我们如何做到在改变state的时候不显式地调用render呢?

    我们创建一个store,只要我们的应用内任何state发生改变,它都会去调用render方法。下面代码是第一次尝试,实现起来很简单,是创建更高级的state容器的好的开始。

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

    通过store的setState方法改变state。当state改变时,就触发render方法。

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

    请看这里的例子

    至此,我们看到了什么?我们看到了单向数据流的原理。我们给render函数传入一个state,state便朝函数继承的方向流去,比如,ItemListItemRow函数传了一个props。看到了创建了一些组件,同时将这些组件组成了更大的玩意。还记得header的例子吗,我们将div和h2方法都写进了header,这使得我们所有的状态改变都得以预测。我们也对state有了一个清晰了认识。

    React就是如此做事的,不过是以更高效的方式。composition、通过实现虚拟DOM对render函数进行优化、单向数据流,等等。

    让我们来看看React的真实力量:composition、单向数据流、freedom from DSLs、显式地mutation和静态mental model.(译:不明所以)。
    Dan Abramov (https://medium.com/@dan_abramov/youre-missing-the-point-of-react-a20e34a51e1a)

    我们还有更多要做,包括改造state容器,重构listeners,实现undo/redo和更多有趣的特性。这些都会在在part2中。

    更新:Part 2 is online.

    相关文章

      网友评论

          本文标题:不看React学会React(一)

          本文链接:https://www.haomeiwen.com/subject/kcrzzftx.html