react 中的不可变性_React的不可变性

react 中的不可变性

上一篇文章中,我写了关于不变性的方式和原因 。 我认为,不变性真正发挥作用的一个领域是跟踪变化,这是现代前端框架中的重大挑战之一。 在本文中,我将为您提供一个示例,说明如何将不可变性与Facebook开发的React库一起使用。

本文不会讨论React的基础。 如果您需要对该库的介绍,可以阅读文章React JavaScript库介绍

如何在React中使用不变性

在上一篇文章中,我实现了Minesweeper的核心游戏逻辑。 在本文中,我们将研究UI层。 在React中使用immutable.js对象时,我们需要解决一些陷阱。 第一个是您不能像这样将不可变的映射或列表直接传递给React组件:

var data = Immutable.Map();
React.render(MyComponent(data), element);

之所以不起作用,是因为React逐个属性复制该对象属性的内容并将它们与现有props合并。 这意味着React不会获得不可变的实例。 它也不会获取地图中包含的数据,因为它不会作为对象的属性公开-您可以使用get()方法访问数据。

解决方案很简单:

var data = Immutable.Map();
React.render(MyComponent({data: data}), element);

在本文中,所有组件都将使用不可变数据,因此我们将创建一个小的包装程序,以避免一遍又一遍地重复此过程:

function createComponent(def) {
  var component = React.createFactory(React.createClass(def));
  return function (data) {
    return component({data: data});
  };
}

有了这个组件,我们就可以忘记包装器,直到需要在render()函数中访问它(在这里需要将其称为this.props.data )。 我们的组件只会定义render()函数,因此我们可以使包装器为我们做更多的工作:

function createComponent(render) {
  var component = React.createFactory(React.createClass({
    render: function () {
      return render(this.props.data);
    }
  }));

  return function (data) {
    return component({data: data});
  };
}

有了这个,定义和使用可处理不可变数据的组件变得轻而易举:

var div = React.DOM.div;

var Tile = createComponent(function (tile) {
  if (tile.get('isRevealed')) {
    return div({className: 'tile' + (tile.get('isMine') ? ' mine' : '')},
               tile.get('threatCount') > 0 ? tile.get('threatCount') : '');
  }

  return div({
    className: 'tile'
  }, div({className: 'lid'}, ''));
});

如果显示了该图块,则如果存在该图块,则我们渲染一个图。 否则,将渲染附近的地雷数量,除非该数量为0。如果未显示地雷,我们将使用“盖”来渲染它,CSS将使它看起来像可单击的图块。

其余的React组件同样很简单。 还有一点要注意的障碍。 React组件可以采用一系列子组件。 我们必须确保将不可变列表转换为数组,然后再将它们传递给React:

var Row = createComponent(function (tiles) {
  return div({className: 'row'}, tiles.map(Tile).toJS());
});

在不可变列表上调用map()会生成一个新的不可变列表,并且toJS()返回一个React可以使用的数组表示形式。 这些和其他UI组件可以在此Codepen中完整看到,您也可以在其中玩游戏。

加快速度

在我的上一篇文章中,我提到可以大大改善跟踪更改,因为我们可以缩短React等库中昂贵的diffing算法。 当给React提供一些新数据时,它会在所有组件上调用shouldComponentUpdate()函数。 如果此函数返回false ,React不会将该组件与现有版本进行比较,因此该库将不会重新渲染构成该组件的元素。 这潜在地节省了很多工作,并可能导致性能的大幅提高。

让我们考虑一下我们的游戏。 当您显示图块时,整个游戏将再次渲染。 但是,由于我们具有不变的数据模型,所有未更改的图块仍将是完全相同的引用。 这些不需要重新渲染,因为对于不变的数据,相同的引用表示没有更改。 为了让React了解这一细节,我们可以如下改进组件包装器:

function createComponent(render) {
  var component = React.createFactory(React.createClass({
    shouldComponentUpdate: function (newProps, newState) {
      // Simplified, this app only uses props
      return newProps.data !== this.props.data;
    },

    render: function() {
      return render.call(this, this.props.data);
    }
  }));

  return function (data) {
    return component({data: data});
  };
}

这段简单的代码足以使我们的应用程序从基于可变数据的速度显着降低到几乎两倍的速度。 这就是高效的变更跟踪所产生的影响! 此改进的版本也可以在CodePen上获得 。 如果您想进一步了解一些数字,那么还有一个GitHub存储库 ,其中包含一些粗略的基准测试和多种实现。

降低复杂度

可以说,不变数据的最大好处是它如何减少意外的复杂性。 可变数据在本质上比不可变数据复杂,因为它纠缠了状态和时间。 在可变数据中,时间是一个内置因素。 实际上,在两个不同的时间点访问该值可能会给您两个不同的值。 另一方面,不可变数据没有此功能。 如果您在两个不同的时间点检索不可变数据的值,则可以确保获得相同的值。 这迫使我们对数据随时间的变化采取更加自觉的态度。

对于不可变的数据,难以实现的某些类型的功能,甚至对于可变数据而言,甚至是不可能的,这些功能都将变得微不足道。 这种功能的一个示例是应用范围内的撤消。 如果您的应用程序状态可以用不可变的值表示,则实现撤消就是保留应用程序状态版本的列表,并提供一个将应用程序状态重置为先前版本的按钮。

让我们向Minesweeper项目添加“撤消”功能。 每当用户单击磁贴时,我们都会渲染一个新的游戏对象。 为了支持此功能,我们将保留旧版本。 下面列出了实现此功能的代码:

var newGame = revealTile(game, tile);

if (newGame !== game) {
  gameHistory = gameHistory.push(newGame);
  game = newGame;
}

render();

然后是“撤消”按钮:

var UndoButton = createComponent(function () {
  return React.DOM.button({
    onClick: function () {
      channel.emit('undo');
    }
  }, 'Undo');
});

channel对象是事件发射器,因此我们需要一个侦听此事件的代码。 除非我们用尽历史记录,否则我们会弹出最后一个版本,并将倒数第二个版本恢复为当前状态,然后重新渲染游戏。 这是通过以下代码完成的:

channel.on('undo', function () {
  if (history.size > 1) {
    gameHistory = gameHistory.pop();
    game = gameHistory.last();
    render();
  }
});

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

很简单,不是吗? 您甚至可以想象如何在状态由可变对象组成的应用程序中执行类似的操作? 通过访问我准备CodePen或下面的演示,使用撤消(基本上是作弊,但是……)玩Minesweeper。

请参阅CodePen上的SitePoint( @SitePoint )的Pen qdObZy

还记得我在上一篇文章中提到“结构共享”吗? 结构共享意味着存储应用程序状态的两个几乎相同的版本实际上不会存储所有相同状态的两个副本。 不变的数据不会更改,这再次启用了此功能。 如果要用可变状态解决“撤消”问题,则需要以某种方式存储突变,以便在内存使用方面与不可变的解决方案相匹配。 否则,您最终将存储一堆功能完善的副本。

应用快照

您可能会认为大多数应用程序都无法享受“撤消”功能。 但是还有其他一些用例会很有趣。 例如,如果整个应用程序状态存储在一个不可变值中,则可以轻松地将快照添加到应用程序中。 那有什么用? 好吧,为一个保存任意复杂的UI的当前状态。 调试是另一回事。

想象一下,您得到的是一个JSON字符串,可以将其转储到浏览器中以完全与使用时相同的状态启动,而不是获取带有大量错误描述步骤的错误报告,而不是如何进行重现。 这只是将其添加到状态存储为不可变值的应用程序中的琐碎事的又一个示例。

结论

我已经将源代码上传到了前面提到的Minesweeper实现中的GitHub ,您可以在其中深入了解细节。 您会发现使用可变和不变数据结构的实现,以及有关执行简单基准测试的说明。 现在继续在您的应用程序中部署不变性!


翻译自: https://www.sitepoint.com/immutability-react/

react 中的不可变性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值