作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
伊万·罗奇的头像

伊凡Rogic

Ivan第一次开始编程是在2007年,当时他刚开始上大学, 他对此充满了热情.

工作经验

5

分享

反应, 回来的和不可变的.js是目前最流行的JavaScript库之一,并且正在迅速成为开发人员的首选 前端开发. 在我参与的几个反应和回来的项目中, 我意识到很多刚开始使用反应的开发人员并不完全理解反应以及如何编写高效的代码来充分利用它的潜力.

在这个 不可变的.js 在教程中,我们将使用 反应回来的,并确定一些最常见的反应误用和避免它们的方法.

数据参考问题

反应 都是关于性能的. 它是从头开始建造的,性能非常高, 仅重新呈现DOM的最小部分以满足新的数据更改. 任何反应应用程序都应该主要由简单的小组件(或无状态函数)组成. 它们很容易理解,而且大多数都可以理解 should组件Update 函数返回 .

should组件Update(nextProps, nextState) {
   返回错误;
}

性能方面,最重要的组件生命周期功能是 should组件Update 如果可能的话,它应该总是返回 . 这确保了该组件永远不会重新渲染(除了初始渲染),有效地使反应应用程序感觉非常快.

但事实并非如此, 我们的目标是对旧的道具/状态和新的道具/状态做一个便宜的相等性检查,如果数据没有改变,就跳过重新渲染.

让我们先回顾一下JavaScript是如何对不同的数据类型执行相等性检查的.

基本数据类型的相等性检查 布尔, 字符串整数 非常简单,因为它们总是按实际值进行比较:

1 === 1
' 字符串 ' === ' 字符串 '
True === True

另一方面,对于复杂类型,如 对象, 数组功能 是完全不同的. 如果两个对象具有相同的引用(指向内存中的相同对象),则它们是相同的。.

const obj1 = {prop: ' someValue '};
const obj2 = {prop: ' someValue '};
控制台.日志(obj1 === obj2);   // 假

尽管obj1和obj2看起来是相同的,但它们的引用是不同的. 既然它们是不同的,就天真地把它们比较在一起 should组件Update 函数将导致我们的组件不必要地重新渲染.

需要注意的重要一点是,数据来自回来的 reducer, 如果设置不正确, 将总是服务于不同的引用,这将导致组件重新渲染每次.

这是我们避免组件重新渲染的核心问题.

处理引用

让我们举一个例子,其中我们有深度嵌套的对象,我们想将它与之前的版本进行比较. 我们可以递归地循环遍历嵌套的对象道具并比较每个道具, 但很明显,这将是非常昂贵的,是不可能的.

这样就只剩下一个解了, 那就是检查参考文献, 但新的问题很快就出现了:

  • 如果没有任何更改,则保留引用
  • 如果任何嵌套的对象/数组道具值发生变化,则更改引用

如果我们想以一种漂亮、干净和性能优化的方式来完成它,这不是一件容易的事. Facebook很久以前就意识到了这个问题,并称之为“不可变”.帮助他人.

从' immutable '中导入{Map};

//将对象转换为不可变映射
let obj1 = Map({prop: ' someValue '});  
Const obj2 = obj1;
控制台.日志(obj1 === obj2);  // true

Obj1 = Obj1.集(‘prop’, ’someValue’);  // set same old value
控制台.日志(obj1 === obj2);  // true | does not break reference beca使用 nothing has changed 

Obj1 = Obj1.集(‘prop’, ’someNewValue’);   // set new value
控制台.日志(obj1 === obj2);  // 假 | breaks reference 

没有不可变的.Js函数对给定的数据执行直接的突变. 相反,数据在内部进行克隆和突变,如果有任何更改,则返回新的引用. 否则,它返回初始引用. 新引用必须显式设置,如 Obj1 = Obj1.集(...);.

反应, 回来的和不可变的.js的例子

最好的方式来展示的力量 这些库 是建立一个简单的应用程序. 还有什么比待办事项应用程序更简单的呢?

为简便起见, 在本文中, 我们将只介绍应用程序中对这些概念至关重要的部分. 整个源代码的应用程序代码可以 在GitHub上找到.

当应用程序启动时,你会注意到调用 控制台.日志 方便地放置在关键区域,以清楚地显示DOM重新渲染的数量,这是最小的.

像任何其他待办事项应用程序一样,我们希望显示待办事项列表. 当用户单击待办事项时,我们会将其标记为已完成. 此外,我们需要一个小的输入字段在顶部添加新的待办事项,并在底部3个过滤器,这将允许用户之间切换:

  • 所有
  • 完成
  • 活跃的

回来的减速机

回来的应用程序中的所有数据都存在于单个存储对象中,我们可以将reducer看作是一种方便的方式,将存储分成更小的部分,这样更容易进行推理. 由于减速器也是一个函数,它也可以被分成更小的部分.

我们的减速机将由2个小部件组成:

  • 基于网络
  • activeFilter
/ /还原剂/待办事项.js
import * as types from 'constants/ActionTypes';
//我们可以把List/Map看作JS数组/对象的不可变表示
从'immutable'中导入{List, Map};  
从'redux'中导入{combineReducers};  

函数基于网络(state = List(), action){//默认状态为空List()
  开关(行动.类型){
  案例类型.ADD_TODO:
    返回状态.push(Map({//每个switch/case必须总是返回一个不可变的 
      id:行动.id, //或原始状态数据(如activeFilter)  
      文字:行动.//我们让不可变的来决定数据是否被改变
      is完成:假的,
    }));

  //其他情况...

  默认值:
    返回状态;
  }
}


函数activeFilter(state = 'all', action) {
  开关(行动.类型){
  案例类型.CHANGE_FILTER:
    返回操作.filter;  // This is primitive data so there’s no need to worry

  默认值:
    返回状态;
  }
}

// combineReducers将reducer组合成一个对象
//它允许我们创建任意数量或组合的reducer来适应我们的情况
导出默认combineReducers({
  activeFilter,
  基于网络,
});

连接回来的

现在我们已经用不可变的设置了一个回来的 reducer.让我们将它与反应组件连接以传递数据.

/ /组件/应用程序.js
从'react-redux'导入{connect};
// ….组件代码
const mapStateToProps = state => ({ 
    activeFilter:状态.待办事项.activeFilter,
    基于网络状态.待办事项.基于网络,
});

导出默认连接(mapStateToProps)(App);

在一个完美的世界, Connect应该只在顶层路由组件上执行, 在mapStateToProps中提取数据,其余的是基本的反应将道具传递给子元素. 在大规模的应用程序中,很难跟踪所有的连接,所以我们希望将它们保持在最低限度.

注意这个状态是非常重要的.待办事项是从回来的返回的常规JavaScript对象 combineReducers 函数(待办事项是reducer的名字),但是状态.待办事项.基于网络是一个不可变列表,在它通过之前保持这种形式是至关重要的 should组件Update 检查.

避免组件重新渲染

在我们深入探讨之前, 了解必须为组件提供什么类型的数据是很重要的:

  • 任何类型的原始类型
  • 对象/数组只能以不可变的形式存在

有了这些类型的数据,我们可以粗略地比较进入反应组件的道具.

下一个例子展示了如何用最简单的方式区分道具:

$ NPM install react-pure-render
从'react-pure-render/shallowEqual'导入shallowEqual;

should组件Update(nextProps, nextState) {
  返回 !shallowEqual(这.道具, nextProps) || !shallowEqual(这.状态,nextState);
}

函数 shallowEqual 检查道具/状态是否只有1级深度. 它的工作速度非常快,并且与不可变数据具有完美的协同作用. 必须写这个 should组件Update 在每个组件中都会很不方便,但幸运的是有一个简单的解决方案.

提取 should组件Update 变成一个特殊的独立组件:

/ /组件/ Pure组件.js
从“反应”中导入反应;
从'react-pure-render/shallowEqual'导入shallowEqual;

导出默认类Pure组件扩展反应.组件{
  should组件Update(nextProps, nextState) {
    返回 !shallowEqual(这.道具, nextProps) || !shallowEqual(这.状态,nextState);
  }
}

然后扩展任何需要使用should组件Update逻辑的组件:

/ /组件/待办事项.js
导出默认类待办事项扩展Pure组件{
  //组件代码
}

在大多数情况下,这是一种非常干净和有效的避免组件重新渲染的方法, 之后,如果应用程序变得更复杂,突然需要自定义解决方案,它可以很容易地改变.

使用时有一个小问题 Pure组件 同时将函数作为道具传递. 从反应到ES6 class,不会自动绑定 对于函数,我们必须手动操作. 我们可以通过以下方法之一实现这一目标:

  • 使用ES6的箭头函数绑定: <组件 onClick={() => 这.h和leClick()} />
  • 使用 绑定: <组件 onClick={这.h和leClick.绑定(这)} />

这两种方法都会导致 组件 重新渲染,因为已经传递了不同的引用 onClick 每一次.

为了解决这个问题,我们可以在 构造函数 方法如下:

  构造函数(){
    超级();
    这.h和leClick =这个.h和leClick.绑定 ();
  }
 //然后简单地传递函数
  呈现(){
    返回 <组件 onClick={这.h和leClick} />
  }

如果您发现自己在大多数情况下预绑定了多个函数, 我们可以导出和重用小的辅助函数:

/ / / 绑定-功能跑龙套.js
导出默认函数绑定函数s(功能) {
  功能.forEach(f => 这[f] = 这[f].绑定 ());
}
//某个组件
  构造函数(){
    超级();
    绑定函数s.call(这, ['h和leClick']);   // Second argument is array of 函数 names
  }

如果没有一个解决方案适合你,你可以一直写作 should组件Update 手动条件.

在组件内部处理不可变数据

使用当前不可变数据设置, 避免了重新渲染,我们在组件的道具中留下了不可变的数据. 有许多方法可以使用这种不可变数据, 但最常见的错误是使用不可变将数据直接转换为纯JS toJS 函数.

使用 toJS 将不可变数据深度转换为纯JS否定了避免重新渲染的全部目的,因为正如预期的那样, 这是非常缓慢的,因此应该避免. 如何处理不可变数据?

它需要按原样使用,这就是为什么不可变API提供了各种各样的函数, map得到 在反应组件中最常用. 基于网络 来自回来的减速机的数据结构是一个不可变形式的对象数组, 每个对象代表一个待办事项:

[{
  id: 1,
  文字:“待办事项1”,
  is完成:假的,
}, {
  id: 2,
  文字:“待办事项2”,
  is完成:假的,
}]

不可变的.js API与常规JavaScript非常相似, 所以我们会像使用其他对象数组一样使用基于网络. Map函数在大多数情况下被证明是最好的.

在map回调中,我们得到 待办事项,它仍然是一个不可变形式的对象,我们可以安全地传递它 待办事项 组件.

/ /组件/基于网络.js
呈现(){
   回报(
      // ….
            {基于网络.map(待办事项 => {
              回报(
                <待办事项 key={待办事项.得到 (id)}
                    待办事项={待办事项}/>
              );
            })}
      //  ….
    );
}

如果您计划对不可变数据执行多个链式迭代,如:

关联.过滤器(somePred).排序(someComp)

那么首先把它转换成……是非常重要的 Seq 使用 toSeq 迭代后,将其转回所需的形式,如:

关联.toSeq ().过滤器(somePred).排序(someComp).toOrderedMap ()

因为不变的.Js从不直接改变给定的数据, 它总是需要另一个副本, 执行像这样的多次迭代可能非常昂贵. Seq是惰性不可变的数据序列, 这意味着它将执行尽可能少的操作来完成任务,同时跳过中间副本的创建. Seq就是为了这种方式而建立的.

内部 待办事项 组件使用 得到 or 捷信 为了得到道具.

很简单,对吧??

嗯,我意识到很多时候,如果有大量的 得到 () 特别是 捷信(). 所以我决定在性能和可读性之间找到一个最佳点,经过一些简单的实验,我发现不可变的.js toObjecttoArray 函数运行得很好.

这些函数浅层转换(1级深)不可变.js对象/数组转换为纯JavaScript对象/数组. 如果我们有任何数据深嵌在内部,它们将保持不可变的形式,随时可以传递给 <待办事项> 组件子组件,这正是我们需要的.

它比 得到 () 差距可以忽略不计,但看起来干净多了:

/ /组件/待办事项.js
呈现(){
  const {id, text, is完成} = 这.道具.待办事项.toObject ();
  // …..
}

让我们一起来看看吧

如果您还没有克隆 代码来自GitHub 然而,现在正是这样做的好时机:

Git克隆http://github.com/rogic89/ToDo-react-redux-immutable.git
cd ToDo-react-redux-immutable

启动服务器也很简单(确保Node . js.js和NPM是这样安装的:

npm安装
npm开始

不可变的.示例:待办事项 App, 带有“输入待办事项”字段, 五件待办事项(划掉第二件和第四件), 所有vs的单选器. 完成了对. 激活后,还有一个“全部删除”按钮.

在web浏览器中导航到http://localhost:3000. 打开开发人员控制台, 在添加一些待办事项时查看日志, 将它们标记为完成并更改过滤器:

  • 添加5个待办事项
  • 将滤镜从' 所有 '更改为' 活跃的 ',然后再更改为' 所有 '
    • 无需重新渲染,只需更改滤镜
  • 标记完成的待办事项
    • 两个待办事项被重新渲染,但一次只有一个
  • 将滤镜从' 所有 '更改为' 活跃的 ',然后再更改为' 所有 '
    • 只有2个已完成的待办事项被挂载/卸载
    • 活动的不会被重新渲染
  • 从列表中间删除一个待办事项
    • 只有被删除的待办事项会受到影响,其他的不会被重新渲染

总结

反应, 回来的和不可变的的协同作用.js, 正确使用时, 为大型web应用程序中经常遇到的许多性能问题提供一些优雅的解决方案.

不可变的.js允许我们检测JavaScript对象/数组的变化,而无需求助于效率低下的深度相等性检查, 这反过来又允许反应在不需要时避免昂贵的重新渲染操作. 这意味着不可变.Js的性能在大多数情况下都很好.

我希望您喜欢这篇文章,并发现它对构建有用 反应创新解决方案 在你未来的项目中.

了解基本知识

  • 回来的是什么?

    回来的是一个开源的JavaScript可预测状态容器. 回来的通常用于创建用户界面, 与反应或Angular等库一起使用.

  • 什么是不可变的.js?

    不可变的.Js是一个为创建不可变数据集合而设计的库. 它通常用于反应/回来的开发. 不可变的.js是由Facebook创建的.

  • 不可变是什么意思?

    创建后不能修改的对象被认为是不可变对象, 因为它们的状态和属性一旦被实例化就不能被修改. 这与可变对象形成对比,可变对象顾名思义是可以改变的.

就这一主题咨询作者或专家.
预约电话
伊万·罗奇的头像
伊凡Rogic

位于 Osijek,克罗地亚

成员自 2015年9月30日

作者简介

Ivan第一次开始编程是在2007年,当时他刚开始上大学, 他对此充满了热情.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

工作经验

5

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.