原文地址: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。
一组已定义的事件更新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便朝函数继承的方向流去,比如,ItemList
给ItemRow
函数传了一个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中。