使用MobX怎樣管理JavaScript應用狀態
如果你曾經用jQuery寫過復雜的應用,你可能就會遇到管理不同頁面部分UI內容同步的問題。常常,數據修改后需要體現在多個地方,隨著項目的復雜化,你可能很難去管理。為了解決這個問題,我們常常需要使用事件來讓頁面的多個部分知道數據改變了。
所以,現在你是怎樣管理這個狀態的問題的呢?我確定你是通過訂閱的方式來做的。這是對的,我敢肯定,如果你沒有使用訂閱的方式,那你實現起來太辛苦了,除非你使用了MobX。
什么是狀態?
這里是一個person對象,比如某個人,他有firstName,lastName和age,另外fullName()方法將顯示他的全名。
var person = {
firstName: 'Matt',
lastName: 'Ruby',
age: 37,
fullName: function () {
this.firstName + ' ' + this.lastName;
}
};
那么當這個人的信息修改時,是怎樣體現到你輸出內容上的呢?你在什么時候觸發這些通知呢?在MobX之前,你可以使用setter來觸發jQuery事件或者js信號。這些方案很好,但是使用它們不夠清晰。我想在person對象的任何部分改變時就去自動觸發。
這里是一段修改firstName信息的代碼,如果我修改age,那么這是會觸發修改通知的,因為我們將數據訂閱綁定在person的整個對象上了。
person.events = {};
person.setData = function (data) {
$.extend(person, data);
$(person.events).trigger('changed');
};
$(person.events).on('changed', function () {
console.log('first name: ' + person.firstName);
});
person.setData({age: 38});
我們怎樣解決這個問題呢?很簡單,為person對象的每個屬性設置一個setter來觸發每一個事件。等等,但是這樣如果age和firstName同時改變了該怎么辦。所以你得設置一個延時來保證兩個屬性都改變完成時才觸發。這似乎行得通并且我就是這么干的。
通過MobX來解決
?? MobX 是一個簡單、高效的前端狀態管理腳本庫。 根據文檔,Just do something to the state and MobX will make sure your app respects the changes。
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 37,
fullName: function () {
this.firstName + ' ' + this.lastName;
}
});
注意下區別,mobx.observable是我做的唯一區別,我們再來看下console.log輸出的內容。
mobx.autorun(function () {
console.log('first name: ' + person.firstName);
});
person.age = 38; // 不打印內容
person.lastName = 'RUBY!'; // 仍然不打印內容
person.firstName = 'Matthew!'; // 打印內容
通過使用autorun, MobX只會檢測使用到的內容。如果你覺得不夠清楚,來看下下面的:
mobx.autorun(function () {
console.log('Full name: ' + person.fullName);
});
person.age = 38; // 不打印內容
person.lastName = 'RUBY!'; // 打印內容
person.firstName = 'Matthew!'; // 打印內容
相信你已經理解了。
MobX核心概念
-
observer
var log = function(data) {
$('#output').append('<pre>' +data+ '</pre>');
}
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 34
});
log(person.firstName);
person.firstName = 'Mike';
log(person.firstName);
person.firstName = 'Lissy';
log(person.firstName);
MobX可觀察的對象都是普通的對象。這個例子中我們沒有觀察任何內容,這里例子也向你展示了怎樣將MobX使用到你的項目中。只需要使用mobx.observable()或 mobx.extendObservable()就可以了。
-
autorun
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 0
});
mobx.autorun(function () {
log(person.firstName + ' ' + person.age);
});
// this will print Matt NN 10 times
_.times(10, function () {
person.age = _.random(40);
});
// this will print nothing
_.times(10, function () {
person.lastName = _.random(40);
});
當變量值改變時,你肯定想做一些事情,所以使用autorun(),將會在任何一個觀察的內容改變時觸發回調。注意下上面的例子中age改變時autorun()為什么不會觸發。
-
computed
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 0,
get fullName () {
return this.firstName + ' ' + this.lastName;
}
});
log(person.fullName);
person.firstName = 'Mike';
log(person.fullName);
person.firstName = 'Lissy';
log(person.fullName);
注意下fullName()方法沒有使用參數和get的用法,MobX會自動創建一個計算的值。這是我最喜歡MobX的一個原因。注意下person.fullName有什么不同的地方?這是一個函數,但是你沒有調用就獲取到了結果!通常,你使用的是person.fullName(),而不是person.fullName。這里就是你遇到的第一個getter函數。
不僅如此,MobX還會監聽你計算值得依賴的變量,并且只在它們修改的時候運行觸發更新。如果沒有修改,將會直接返回之前緩存中的值。看下面一個例子。
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 0,
get fullName () {
// Note how this computed value is cached.
// We only hit this function 3 times.
log('-- hit fullName --');
return this.firstName + ' ' + this.lastName;
}
});
mobx.autorun(function () {
log(person.fullName + ' ' + person.age);
});
// this will print Matt Ruby NN 10 times
_.times(10, function () {
person.age = _.random(40);
});
person.firstName = 'Mike';
person.firstName = 'Lissy';
這里我們使用了person.fullName很多次,但是函數只有在firstName和lastName修改時才會運行。這就是MobX可以提供你應用速度的一種方式。
使用MobX進行項目實踐
讓我們直接看例子:
<h1>Test</h1>
<script>
var person = {
events: {},
firstName: 'Matt',
lastName: 'Ruby',
age: 37,
fullName: function() {
return this.firstName + ' ' + this.lastName;
},
setPersonData: function(personData) {
$.extend(this, personData);
$(this.events).trigger('changed', personData);
}
};
var renderCount = 0;
$(person.events).on('changed', function() {
renderCount += 1;
$('h1').text(person.fullName() + ' render count: '+ renderCount);
});
// this will trigger every time
_.times(10, function() {
person.setPersonData({age: _.random(20)});
});
</script>
注意下這里name被重復渲染了10次,盡管我們沒有修改firstName或lastName,你可使用多個事件來優化這里,在渲染之前做判斷。但是太復雜了。下面是MobX的例子;
<h1>Test</h1>
<script>
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 37,
get fullName () {
return this.firstName + ' ' + this.lastName;
}
});
var renderCount = 0;
mobx.autorun(function() {
renderCount++;
$('h1').text(person.fullName + ' render count: ' + renderCount);
});
// this will trigger the render one time
_.times(10, function() {
person.setPersonData({
age: _.random(20)
});
});
</script>
注意下這里為什么沒有事件、觸發器或on綁定。使用MobX,你會使用修改后的最新值。而且它只被渲染一次,這是因為我們沒有修改內容時autorun只是一直在監聽。
// observable person
var person = mobx.observable({
firstName: 'Matt',
lastName: 'Ruby',
age: 37
});
// reduce the person to simple html
var printObject = function(objectToPrint) {
return _.reduce(objectToPrint, function(result, value, key) {
result += key + ': ' + value + '<br/>';
return result;
}, '');
};
// print out the person anytime there's a change
mobx.autorun(function(){
$('#person').html(printObject(person));
});
// watch all the input for changes and update the person
// object accordingly.
$('input').on('keyup', function(event) {
person[event.target.name] = $(this).val();
});
這里我們能編輯person的所有信息來觀察輸出的內容情況,現在這個例子中有個小小的問題,這里input的值和person的值不同步,我們改下:
mobx.autorun(function(){
$('#person').html(printObject(person));
// update the input values
_.forIn(person, function(value, key) {
$('input[name="'+key+'"]').val(value);
});
});
或許你會說,你這里有重新渲染了啊。是的,你這看到的就是為什么很多人選擇使用React的原因。React允許你將輸出內容拆分小的組件進行個別的渲染。
MobX可能在實際項目中使用嗎?不一定,如果我需要這種細粒度的操作使用React就可以了。當我在實際項目中使用MobX和jQuery時,我使用autorun()就可以解決很多問題了。
來自:http://jixianqianduan.com/article-translation/2016/10/12/how-to-manage-state-with-mobx.html