如何使用MobX管理JavaScript应用程序状态

如何使用MobX管理JavaScript应用程序状态_第1张图片

本文由Michel Weststrate和Aaron Boyer进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

如果您除了使用jQuery编写一个非常简单的应用程序以外还编写了其他任何内容,那么您可能会遇到使UI的不同部分保持同步的问题。 通常,数据更改需要反映在多个位置,并且随着应用程序的增长,您会发现自己陷入困境。 为了驯服这种疯狂,通常会使用事件让应用程序的不同部分知道发生了什么变化。

那么,您今天如何管理应用程序的状态? 我要大步走出去,说您已经订阅了变更。 那就对了。 我什至不认识你,我会叫你出来。 如果您还没有订阅,那么我确定您的工作太辛苦了。

除非您使用MobX,否则……

无论如何,“状态”是什么?

这是一个人。 嘿,就是我! 我有一个firstNamelastNameage
此外,如果遇到麻烦,可能会发出fullName()函数。

var person = {
  firstName: 'Matt',
  lastName: 'Ruby',
  age: 37,
  fullName: function () {
    this.firstName + ' ' + this.lastName;
  }
};

您如何将对该人所做的修改通知您的各种输出(视图,服务器,调试日志)? 您何时触发这些通知? 在MobX之前,我将使用会触发自定义jQuery事件或js- 信号的设置器 。 这些选项对我很有用,但是,我对它们的用法远非精细。 如果person对象的任何部分发生更改,我都会触发一个“更改”事件。

假设我有一段显示我的名字的视图代码。 如果更改年龄,则该视图将更新,因为该视图与该personchanged事件有关。

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

我们怎样才能收紧那场过度? 简单。 只需为每个字段设置一个设置者,为每个更改设置单独的事件。 等一下,如果您想同时更改agefirstName ,则可能会开始过度射击。 您必须创建一种方法来延迟事件触发,直到两个更改都完成为止。 听起来像工作,我很懒...

MobX进行救援

MobX是由Michel Weststrate开发的一个简单,专注,高效且不干扰状态的管理库。

从MobX文档中:

只需对状态做些事情,MobX将确保您的应用尊重更改。

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; // prints nothing
person.lastName = 'RUBY!'; // still nothing
person.firstName = 'Matthew!'; // that one fired

使用autorun ,MobX将仅观察已访问的内容。

如果您认为这很简洁,请查看以下内容:

mobx.autorun(function () {
  console.log('Full name: ' + person.fullName);
});

person.age = 38; // print's nothing
person.lastName = 'RUBY!'; // Fires
person.firstName = 'Matthew!'; // Also fires

感兴趣吗? 我知道你是。

MobX核心概念

可观察的

var log = function(data) {
  $('#output').append('
' +data+ '
'); } 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);

在CodePen上运行

MobX可观察对象只是对象。 在这个例子中我没有观察到任何东西。 此示例说明如何开始将MobX应用于现有代码库。 只需使用mobx.observable()mobx.extendObservable()即可开始。

自动运行

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

在CodePen上运行

您想在可观察值发生变化时采取措施,对吗? 请允许我介绍autorun() ,只要引用的可观察到的更改将触发回调。 请注意,在上面的示例中,更改age autorun()如何不会触发。

计算的

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

在CodePen上运行

看到fullName函数,并注意它不带参数和get怎么get MobX将自动为您创建一个计算值。 这是我最喜欢的MobX功能之一。 注意到有关person.fullName任何怪异内容吗? 再看一遍。 这是一个函数,您无需调用即可查看结果! 通常,您将调用person.fullName()而不是person.fullName 您刚刚遇到了第一个JS 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';

在CodePen上运行

在这里,您可以看到我已经person.fullName计算了person.fullName ,但是运行该函数的唯一时间是更改了firstNamelastName 这是MobX可以大大加快应用程序速度的方法之一。

更多!

我将不再继续重写MobX的出色文档 。 查看文档以获取更多使用和创建可观察对象的方法。

使MobX正常工作

在让您感到厌烦之前,让我们先做些事情。

这是一个简单的非MobX人物示例,只要人物发生变化,该示例就会打印该人的全名。

请参阅CodePen上SitePoint ( @SitePoint )的Pen Simple MobX jQuery示例 。

请注意,即使我们从未更改名字或姓氏,该名称也被呈现了10次。 您可以通过许多事件来优化此功能,或者检查某种已更改的有效负载。 那是太多的工作。

这是使用MobX构建的相同示例:

请参阅CodePen上SitePoint ( @SitePoint )的Pen Simple MobX jQuery示例 。

请注意,没有任何eventstriggeron 使用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 + '
'; 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(); });

在CodePen上运行

在这里,我们可以编辑整个人员对象并自动查看数据输出。 现在,在此示例中有几个问题,最值得注意的是输入值与人员对象不同步。 让我们修复一下:

mobx.autorun(function(){
  $('#person').html(printObject(person));
  // update the input values
  _.forIn(person, function(value, key) {
    $('input[name="'+key+'"]').val(value);
  });
});

在CodePen上运行

我知道,您还有另外一个抱怨:​​“ Ruby,您过度渲染了!” 你是对的。 您在这里看到的是为什么许多人选择使用React。 React使您可以轻松地将输出分成可以单独呈现的小组件。

为了完整起见,这是我已优化的jQuery示例 。

我会在真实的应用程序中做类似的事情吗? 可能不是。 如果需要这种级别的粒度,我会每天使用React。 在实际的应用程序中使用MobX和jQuery时,我使用的autorun()足够精细,因此我不会在每次更改时都重新构建整个DOM。

到目前为止,您已经做到了,所以这是使用React和MobX构建的相同示例

让我们建立一个幻灯片

您将如何表示幻灯片的状态?
让我们从单个幻灯片工厂开始:

var slideModelFactory = function (text, active) {
  // id is not observable
  var slide = {
    id: _.uniqueId('slide_')
  };

  return mobx.extendObservable(slide, {
    // observable fields
    active: active || false,
    imageText: text,
    // computed
    get imageMain() {
      return 'https://placeholdit.imgix.net/~text?txtsize=33&txt=' + slide.imageText + '&w=350&h=150';
    },
    get imageThumb() {
      return 'https://placeholdit.imgix.net/~text?txtsize=22&txt=' + slide.imageText + '&w=400&h=50';
    }
  });
};

我们应该有一些东西可以汇总我们所有的幻灯片。 让我们现在构建它:

var slideShowModelFactory = function (slides) {
  return mobx.observable({
    // observable
    slides: _.map(slides, function (slide) {
      return slideModelFactory(slide.text, slide.active);
    }),
    // computed
    get activeSlide() {
      return _.find(this.slides, {
        active: true
      });
    }
  });
};

幻灯片放映! 这更有趣,因为我们有一个可观察的幻灯片数组,该数组可让我们从集合中添加和删除幻灯片,并相应地更新UI。 接下来,我们添加activeSlide计算值,该值将根据需要使自身保持最新。

让我们渲染幻灯片。 我们还没有准备好HTML输出,因此我们只需要打印到控制台即可。

var slideShowModel = slideShowModelFactory([
  {
    text: 'Heloo!',
    active: true
  }, {
    text: 'Cool!'
  }, {
    text: 'MobX!'
  }
]);

// this will output our data to the console
mobx.autorun(function () {
  _.forEach(slideShowModel.slides, function(slide) {
    console.log(slide.imageText + ' active: ' + slide.active);
  });
});

// Console outputs:
// Heloo! active: true
// Cool! active: false
// MobX! active: false

太酷了,我们有几张幻灯片, autorun只是打印出它们的当前状态。 让我们换一张或两张幻灯片:

slideShowModel.slides[1].imageText = 'Super cool!';
// Console outputs:
// Heloo! active: true
// Super cool! active: false
// MobX! active: false

看起来我们的autorun正在运行。 如果您更改了autorun正在监视的任何内容,它将触发。 让我们将输出派生从控制台更改为HTML:

var $slideShowContainer = $('#slideShow');
mobx.autorun(function () {
  var html = '
'; html += '
'; _.forEach(slideShowModel.slides, function (slide) { html += '
'; html += '' html += '
'; }); html += '
'; $slideShowContainer.html(html); });

现在,我们已经有了该幻灯片显示的基础知识,但是,还没有交互性。 您不能单击缩略图并更改主图像。 但是,您可以使用控制台轻松更改图像文本并添加幻灯片:

// add a new slide
slideShowModel.slides.push(slideModelFactory('TEST'));
// change an existing slide's text
slideShowModel.slides[1].imageText = 'Super cool!';

让我们创建第一个也是唯一的动作,以便设置所选的幻灯片。 我们必须通过添加以下操作来修改slideShowModelFactory

// action
setActiveSlide: mobx.action('set active slide', function (slideId) {
  // deactivate the current slide
  this.activeSlide.active = false;
  // set the next slide as active
  _.find(this.slides, {id: slideId}).active = true;
})

为什么要使用您要求的action 好问题! 正如我在其他示例中已显示的有关更改可观察值的示例所示,不需要MobX操作。

动作可以通过几种方式帮助您。 首先,MobX操作都在事务中运行。 这意味着我们的autorun和其他MobX反应将等到动作完成后再触发。 这点考虑一下吧。 如果我尝试停用活动幻灯片并在事务之外激活下一张幻灯片,将会发生什么情况? 我们的autorun将被触发两次。 第一次运行会很尴尬,因为没有活动的幻灯片可以显示。

除了它们的事务性质外,MobX动作还倾向于使调试更简单。 我传递给mobx.action的第一个可选参数是字符串'set active slide' 该字符串可以与MobX的调试API一起输出。

因此,我们采取了行动,让我们使用jQuery连接其用法:

$slideShowContainer.on('click', '.slide', function () {
  slideShowModel.setActiveSlide($(this).data('slideId'));
});

而已。 现在,您可以单击缩略图,并且活动状态会按预期传播。 这是幻灯片的工作示例:

请参阅CodePen上SitePoint ( @SitePoint )的Pen Simple MobX jQuery示例 。

这是同一张幻灯片的React示例 。

请注意,我怎么都没有更改模型? 就MobX而言,React只是数据的另一种派生,例如jQuery或控制台。

jQuery幻灯片示例的警告

请注意,我还没有以任何方式优化jQuery示例。 我们会在每次更改时破坏整个幻灯片DOM。 通过破坏,我的意思是我们每次单击都将替换幻灯片显示的所有HTML。 如果要构建基于jQuery的健壮幻灯片,则可能会在初始渲染后通过设置和删除活动类并更改mainImagesrc属性来调整DOM。

想了解更多?

如果我有兴趣了解有关MobX的更多信息,请查看以下一些其他有用的资源:

  • MobX博客,教程和视频
  • Egghead.io课程:使用MobX管理React Apps中的复杂状态
  • MobX的实用React
  • 简单的MobX示例
  • MobX API参考

如有任何疑问,请在下面的评论中打我,或在MobX gitter频道上找到我。

From: https://www.sitepoint.com/manage-javascript-application-state-mobx/

你可能感兴趣的:(如何使用MobX管理JavaScript应用程序状态)