翻译:immutability-in-javascript
image.png

JS中的不可变性

在这篇文章中,我们将要来学习一个在JS应用中广泛应用的概念:不可变性。

我们将要去学习关于JS中的不可变性,以及这个概念是如何帮助我们写好应用,更好的管理数据,以便于当我们每天用它的时候提高我们的代码质量。

我们写代码的方式改变的非常快,每天都有一些东西发布,一些新的概念给创建,一些新的框架或者库出现帮助我们做一些特定的任务。

随着函数式编程的流行,有一个被谈论最多的流行的概念之一就是不可变性。

不变性的概念

不变性这个概念是简单的和强大的。总的来讲,一个不变值就是值不会被改变。尤其是当我们在开发应用时,我们可能会遇到一些情况,我们想在我们的代码里创建一个新的对象,包含一个新的属性或者值同时保持原始值。不变性的概念就是能帮助我们创建一个新的对象,并且确保不会改变原始值。

在JS中,我们有基本数据类型和引用类型。基本数据类型包含numbers, strings, boolean, null, undefined。引用数据类型包含objects, arrays and functions。

两者的区别就是不可变性(或者不可更改),引用类型是可更改的。例如,string类型就是不可变的。

  1. let myAge = "22";
  2. let myNewAge = myAge;
  3. myAge = "23";

上面的例子里,我们创建了两个变量,把myAge变量赋值给myNewAge。但是修改了myAge的值,我们发现他们并不一样。

  1. console.log(myAge === myNewAge); // false

const vs let

ES6允许我们用常量来替换变量通过使用const关键字。但是有一点需要注意,const关键字是不可变的。

  1. const myName = "Leonardo Maldonado";

const 关键字创建一个值只读的引用,这意味着这个变量不能被重新赋值。

MDN里面讲的

const 声明了值的只读引用。这并不是说这个值是不可更改的,仅仅是因为变量的标识符不能被重新指定。

如果我们试着去改变常量的值,就会报错。

  1. const myName = "Leonardo Maldonado";
  2. myName = "Leo"; // Identifier 'myName' has already been declared

let关键字允许我们创建一个变量并且是可更改的。利用let这个关键字,我们可以指派一个新的值。

  1. let myName = "Leonardo Maldonado";
  2. myName = "Leo";
  3. console.log(myName) // Leo

Objects

Object是前端应用的支柱。我们使用object在应用的任何地方,从前端到后端,从复杂组件到简单组件。
假设我们有个对象叫做myCar,有如下属性:

  1. const myCar = {
  2. model: "Tesla",
  3. year: 2019,
  4. owner: "Leonardo"
  5. };

我们可以直接修改对象的属性。

  1. const myCar = {
  2. model: "Tesla",
  3. year: 2019,
  4. owner: "Leonardo"
  5. };
  6. myCar.owner = "Lucas";

但这是不好的实践,我们不应该直接修改对象的属性,这不是不可变性工作的方式。就像Redux文档里推荐的那样,我们应该创建一个修改过的对象的拷贝,并且赋值给它。

Object.assign

Object.assign方法允许我们拷贝或者传递值从一个对象到另外一个对象,并且返回目标对象。

  1. Object.assign(target, source);

1、这个方法首先接受一个参数就是target,target就是我们要更改的对象。
2、第二个参数就是来源,这个方法会把source对象并入target。

  1. const objectOne = {
  2. oneName: "OB1"
  3. };
  4. const objectTwo = {
  5. twoName: "OB2"
  6. };
  7. const objectThree = Object.assign(objectOne, objectTwo);
  8. console.log(objectThree);
  9. // Result -> { oneName: "OB1", twoName: "OB2" }

想象一下,我们要从一个特定的对象里传递一个值到一个新的变量。如下所示:

  1. const myName = {
  2. name: "Leonardo"
  3. };
  4. const myPerson = Object.assign({}, myName);
  5. console.log(myPerson);
  6. // Result -> { name: "Leonardo" }

通过拷贝myName的值和属性,并且把它分配给myPerson这个对象。

想象一下,我们想拷贝myName这个对象的所有值和对象,并且添加新的属性给myPerson。这时候,就可以通过传递第三个参数,并且增加新的属性,如下所示:

  1. const myName = {
  2. name: "Leonardo"
  3. };
  4. const myPerson = Object.assign({}, myName, {
  5. age: 23
  6. });
  7. console.log(myPerson);
  8. // Result -> { name: "Leonardo", age: 23 }

Spread Operator

另一种方式拷贝并且传递新的值给对象就是 扩展运算符。扩展运算符,允许我们从已经存在的对象里拷贝属性。

  1. const myName = {
  2. name: "Leonardo"
  3. };
  4. const myPerson = {
  5. ...myName
  6. }
  7. console.log(myPerson);
  8. // Result -> { name: "Leonardo" }

如果我们想拷贝myName的全部属性,并且增加一个新的属性,如下所示:

  1. const myName = {
  2. name: "Leonardo"
  3. };
  4. const myPerson = {
  5. ...myName,
  6. age: 23
  7. }
  8. console.log(myPerson);
  9. // Result -> { name: "Leonardo", age: 23 }

Redux

Redux的第一个原则就是不可变性。Redux不仅仅是React应用中最著名的和使用最广泛的状态管理库,而且由于它核心概念里的不可变性。使用Redux的正确方式是不可变的Reducers

1、Redux允许你保持数据或者状态在一个单独的对象,我们把它叫做store。这会帮助我们的应用在可扩展性和可维护性上达到一个好的水平。想像一下,我们有一个store,store里有原始的state。

  1. const initialState = {
  2. name: "Leonardo Maldonado",
  3. age: 22
  4. }

2、如果我们想改变我们的state,我们应该dispatch一个action,Redux里的action是一个简单对象,有两个属性
3、type - 描述了action的类型,描述了action具体做的事情。
4、payload - 准确描述了 我们要改变的东西
如下所示

  1. const changeAge = payload => ({
  2. type: 'CHANGE_AGE',
  3. payload
  4. })

首先有原始的state,创建action将会dispatch来改变state。
一个Reducer,是一个函数通过访问dispath的action的类型,生产出下一个state,并且合并action携带的payload到新的state里。如下所示,

  1. const initialState = {
  2. name: "Leonardo Maldonado"
  3. age: 22
  4. }
  5. const reducer = (state = initialState, action) => {
  6. switch (action.type) {
  7. case 'CHANGE_AGE':
  8. return {
  9. ...state,
  10. age: action.payload
  11. }
  12. default:
  13. return state;
  14. }
  15. }

在上面的例子里,当CHANGE_AGE action被dispatch,reducer就会基于action的类型执行后续的任务。
在上面的例子里,我们修改了age的值,但是他也同样保持了最初state里的原始值,name的值。这是非常重要的保持原始的state。这是非常重要的保持原始的state。另外,我们可能会丢失数据,而且跟踪数据这是非常困难的。这也是为什么Redux首重的原理是不可变性。

Immer

如果你在开发React应用的时候,不再使用Redux,但是还想用不可变状态。你可以使用immer作为代替。下面示例是immer的工作方式:
image.png
1、当前state
2、你可以应用你的改变到draftState,从根本上来讲 是currentState的拷贝。
3、完成所有的变更之后,生产出nextstate基于draftState
如下所示,我们有current state,我们想给这个数组添加一个新的对象。可以用produce 函数。

  1. import produce from "immer";
  2. const state = [
  3. {
  4. name: "Leonardo",
  5. age: 23
  6. },
  7. {
  8. name: "Lucas",
  9. age: 20
  10. }
  11. ];
  12. const nextState = produce(state, draftState => {
  13. draftState.push({
  14. name: "Carlos",
  15. age: 18
  16. })
  17. });

刚开始,produce函数接受两个参数,currentState和一个回调函数,回调函数用来修改draftState。这个函数用来生产nextState

Conclusion

不可变性不是JS中特定的主题,可以被应用到每一门语言中。