JS中的不可变性
在这篇文章中,我们将要来学习一个在JS应用中广泛应用的概念:不可变性。
我们将要去学习关于JS中的不可变性,以及这个概念是如何帮助我们写好应用,更好的管理数据,以便于当我们每天用它的时候提高我们的代码质量。
我们写代码的方式改变的非常快,每天都有一些东西发布,一些新的概念给创建,一些新的框架或者库出现帮助我们做一些特定的任务。
随着函数式编程的流行,有一个被谈论最多的流行的概念之一就是不可变性。
不变性的概念
不变性这个概念是简单的和强大的。总的来讲,一个不变值就是值不会被改变。尤其是当我们在开发应用时,我们可能会遇到一些情况,我们想在我们的代码里创建一个新的对象,包含一个新的属性或者值同时保持原始值。不变性的概念就是能帮助我们创建一个新的对象,并且确保不会改变原始值。
在JS中,我们有基本数据类型和引用类型。基本数据类型包含numbers, strings, boolean, null, undefined。引用数据类型包含objects, arrays and functions。
两者的区别就是不可变性(或者不可更改),引用类型是可更改的。例如,string类型就是不可变的。
let myAge = "22";
let myNewAge = myAge;
myAge = "23";
上面的例子里,我们创建了两个变量,把myAge变量赋值给myNewAge。但是修改了myAge的值,我们发现他们并不一样。
console.log(myAge === myNewAge); // false
const vs let
ES6允许我们用常量来替换变量通过使用const
关键字。但是有一点需要注意,const
关键字是不可变的。
const myName = "Leonardo Maldonado";
const
关键字创建一个值只读的引用,这意味着这个变量不能被重新赋值。
MDN里面讲的
const
声明了值的只读引用。这并不是说这个值是不可更改的,仅仅是因为变量的标识符不能被重新指定。
如果我们试着去改变常量的值,就会报错。
const myName = "Leonardo Maldonado";
myName = "Leo"; // Identifier 'myName' has already been declared
let
关键字允许我们创建一个变量并且是可更改的。利用let
这个关键字,我们可以指派一个新的值。
let myName = "Leonardo Maldonado";
myName = "Leo";
console.log(myName) // Leo
Objects
Object是前端应用的支柱。我们使用object在应用的任何地方,从前端到后端,从复杂组件到简单组件。
假设我们有个对象叫做myCar,有如下属性:
const myCar = {
model: "Tesla",
year: 2019,
owner: "Leonardo"
};
我们可以直接修改对象的属性。
const myCar = {
model: "Tesla",
year: 2019,
owner: "Leonardo"
};
myCar.owner = "Lucas";
但这是不好的实践,我们不应该直接修改对象的属性,这不是不可变性工作的方式。就像Redux文档里推荐的那样,我们应该创建一个修改过的对象的拷贝,并且赋值给它。
Object.assign
Object.assign
方法允许我们拷贝或者传递值从一个对象到另外一个对象,并且返回目标对象。
Object.assign(target, source);
1、这个方法首先接受一个参数就是target,target就是我们要更改的对象。
2、第二个参数就是来源,这个方法会把source对象并入target。
const objectOne = {
oneName: "OB1"
};
const objectTwo = {
twoName: "OB2"
};
const objectThree = Object.assign(objectOne, objectTwo);
console.log(objectThree);
// Result -> { oneName: "OB1", twoName: "OB2" }
想象一下,我们要从一个特定的对象里传递一个值到一个新的变量。如下所示:
const myName = {
name: "Leonardo"
};
const myPerson = Object.assign({}, myName);
console.log(myPerson);
// Result -> { name: "Leonardo" }
通过拷贝myName的值和属性,并且把它分配给myPerson这个对象。
想象一下,我们想拷贝myName这个对象的所有值和对象,并且添加新的属性给myPerson。这时候,就可以通过传递第三个参数,并且增加新的属性,如下所示:
const myName = {
name: "Leonardo"
};
const myPerson = Object.assign({}, myName, {
age: 23
});
console.log(myPerson);
// Result -> { name: "Leonardo", age: 23 }
Spread Operator
另一种方式拷贝并且传递新的值给对象就是 扩展运算符。扩展运算符,允许我们从已经存在的对象里拷贝属性。
const myName = {
name: "Leonardo"
};
const myPerson = {
...myName
}
console.log(myPerson);
// Result -> { name: "Leonardo" }
如果我们想拷贝myName的全部属性,并且增加一个新的属性,如下所示:
const myName = {
name: "Leonardo"
};
const myPerson = {
...myName,
age: 23
}
console.log(myPerson);
// Result -> { name: "Leonardo", age: 23 }
Redux
Redux的第一个原则就是不可变性。Redux不仅仅是React应用中最著名的和使用最广泛的状态管理库,而且由于它核心概念里的不可变性。使用Redux的正确方式是不可变的Reducers
。
1、Redux允许你保持数据或者状态在一个单独的对象,我们把它叫做store
。这会帮助我们的应用在可扩展性和可维护性上达到一个好的水平。想像一下,我们有一个store,store里有原始的state。
const initialState = {
name: "Leonardo Maldonado",
age: 22
}
2、如果我们想改变我们的state
,我们应该dispatch
一个action
,Redux里的action是一个简单对象,有两个属性
3、type - 描述了action的类型,描述了action具体做的事情。
4、payload - 准确描述了 我们要改变的东西。
如下所示
const changeAge = payload => ({
type: 'CHANGE_AGE',
payload
})
首先有原始的state,创建action将会dispatch来改变state。
一个Reducer
,是一个函数通过访问dispath的action的类型,生产出下一个state,并且合并action携带的payload到新的state里。如下所示,
const initialState = {
name: "Leonardo Maldonado"
age: 22
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CHANGE_AGE':
return {
...state,
age: action.payload
}
default:
return state;
}
}
在上面的例子里,当CHANGE_AGE
action被dispatch,reducer就会基于action
的类型执行后续的任务。
在上面的例子里,我们修改了age的值,但是他也同样保持了最初state里的原始值,name的值。这是非常重要的保持原始的state。这是非常重要的保持原始的state。另外,我们可能会丢失数据,而且跟踪数据这是非常困难的。这也是为什么Redux首重的原理是不可变性。
Immer
如果你在开发React应用的时候,不再使用Redux,但是还想用不可变状态。你可以使用immer作为代替。下面示例是immer的工作方式:
1、当前state
2、你可以应用你的改变到draftState
,从根本上来讲 是currentState
的拷贝。
3、完成所有的变更之后,生产出nextstate
基于draftState
。
如下所示,我们有current state,我们想给这个数组添加一个新的对象。可以用produce
函数。
import produce from "immer";
const state = [
{
name: "Leonardo",
age: 23
},
{
name: "Lucas",
age: 20
}
];
const nextState = produce(state, draftState => {
draftState.push({
name: "Carlos",
age: 18
})
});
刚开始,produce函数接受两个参数,currentState
和一个回调函数,回调函数用来修改draftState
。这个函数用来生产nextState
。
Conclusion
不可变性不是JS中特定的主题,可以被应用到每一门语言中。