React和Redux中的不变性

https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/

一、不变性和副作用

1.不变:不断创造新值来替换旧值

2.不变性规则:

(1)当给定相同的输入时,纯函数必须始终返回相同的值

(2)纯函数不能有任何副作用

3.副作用:修改直接函数范围之外的东西

改变/修改传入的参数(的属性)

修改函数外部的任何其他状态,例如全局变量

进行API调用

console.log()

Math.random()

4.会改变数组的数组方法:push,pop,shift,unshift,sort,reverse,splic

如果想对数组进行不可变的操作,可以先复制该数组,再对副本进行操作

5.纯函数只能调用其他纯函数

二、React更喜欢不变性

1.不变性和PureComponent

继承了React.Component的function和class类型的react组件会在父级组件重新渲染或者使用setState时重新渲染。

继承了React.PureComponent的class仅在其状态更新或者其props更新时才会重新渲染。

将props传入到PureComponent中时,需要保证不可变的更新,因为如果直接修改内部或者属性,但是引用不变,则组件无法注意到它已经改变,则不会重新渲染。

2.JS中的引用相等性

对象和数组存储在内存中,当重新分配变量时,变量会指向新的内存地址,但如果仅改变变量的内部结构,变量仍指向相同的地址,地址里面的内容发生变化。

使用===比较对象和数组时,本质是在比较引用地址,即"引用相等"。

修改一个对象时,会修改对象的内容,但不会更改其引用。将一个对象分配给另一个对象时,指向相同的内存位置,对第二个对象的操作也会影响第一个对象的值。

为什么不深入检查是否相等:速度慢,时间复杂度O(N)

比较引用:O(1),不管对象内部有多复杂

3.const只阻止重新分配引用, 不阻止改变对象内容

4.思考:React框架从性能方面考虑,为了保证复杂场景下的性能,在比较的时候没有挨个比较object内部的值,而是比较引用地址,保证每次比较的时间复杂度都是O(1)。因此从这个设计思想出发,在更新State的时候,都是基于旧值返回新值(引用地址改变),而不是直接通过object.属性=xxx来直接修改object的内容。所以大部分语言修改一个object的属性的时候是通过object.属性=xxx来修改,React是基于旧值返回新值。

三、在Redux中更新state(主要内容,前面都是为了解释这个)

1.要求reducers是纯函数,不能直接修改state:接受state和action,基于旧的state,返回一个新的state。上面的部分解释了可变和不可变,下面介绍了,如何不可变的更新state

  1. 对象扩展运算符...:创建包含与另一个对象或数组完全相同的内容的新对象或数组

3.更新state:会浅合并传入的this.setState()对象

在redux的reducer中:

(1)更新普通的state

return {

...state,

(updates here)

}

(2)更新对象最顶层的属性时,需要复制现有的state,通过...展开对象,然后加入想要更改的属性及其值

function reducer(state, action) {

/*

State looks like:

复制代码
state = {
  clicks: 0,
  count: 0
}

*/

return {

...state,

clicks: state.clicks + 1,

count: state.count - 1

}

}

(3)更新对象中的对象,当想要更新的部分位于深层时,需要把每一级展开并创建副本

function reducer(state, action) {

/*

State looks like:

复制代码
state = {
  house: {
    name: "Ravenclaw",
    points: 17
  }
}

*/

// Two points for Ravenclaw

return {

...state, // copy the state (level 0)

house: {

...state.house, // copy the nested object (level 1)

points: state.house.points + 2

}

}

(4)通过key来更新对象

function reducer(state, action) {

/*

State looks like:

复制代码
const state = {
  houses: {
    gryffindor: {
      points: 15
    },
    ravenclaw: {
      points: 18
    },
    hufflepuff: {
      points: 7
    },
    slytherin: {
      points: 5
    }
  }
}

*/

// Add 3 points to Ravenclaw,

// when the name is stored in a variable

const key = "ravenclaw";

return {

...state, // copy state

houses: {

...state.houses, // copy houses

key\]: { // update one specific house (using Computed Property syntax) ...state.houses\[key\], // copy that specific house's properties points: state.houses\[key\].points + 3 // update its `points` property } } } (5)将一个Item添加到数组中 通过Array.prototype.unshift和push将Item添加到数组前面/后面会改变数组,希望通过不可变的方式添加Item(对象展开复制,或者通过.slice复制后再push) function reducer(state, action) { /\* State looks like: state = [1, 2, 3]; \*/ const newItem = 0; return \[ // a new array newItem, // add the new item first ...state // then explode the old state at the end 添加到前面 \]; return \[ // a new array newItem, // add the new item first ...state // then explode the old state at the end 添加到后面 \]; //或者通过.slice复制数组,在push到末尾 function reducer(state, action) { const newItem = 0; const newState = state.slice(); newState.push(newItem); return newState; (6)通过map更新数组中的item 通过.map来遍历每个item,找到要修改的item,使用返回值作为这个item新的值,最后map返回一个新的数组,需要筛选则使用.filter function reducer(state, action) { /\* State looks like: state = [1, 2, "X", 4]; \*/ return state.map((item, index) =\> { // Replace "X" with 3 // alternatively: you could look for a specific index if(item === "X") { return 3; } // Leave every other item unchanged return item; }); } (7)更新数组中的对象 通过.map遍历每个item,找到要修改的对象,复制这个对象并对副本进行修改,作为一个新的对象返回,最后map返回一个新的数组,需要筛选则使用.filter function reducer(state, action) { /\* State looks like: state = [ { id: 1, email: 'jen@reynholmindustries.com' }, { id: 2, email: 'peter@initech.com' } ] Action contains the new info: action = { type: "UPDATE_EMAIL" payload: { userId: 2, // Peter's ID newEmail: 'peter@construction.co' } } \*/ //通过map返回的是一个新的数组 return state.map((item, index) =\> { // Find the item with the matching id if(item.id === action.payload.userId) { // Return a new object 修改后,返回的是一个新对象 return { ...item, // copy the existing item 复制这个对象不需要修改的部分 email: action.payload.newEmail // replace the email addr } } // Leave every other item unchanged return item; }); } (8)在数组中间插入一个item .splice函数将插入一个item,但会改变数组。可以先试用slice复制数组,再使用splice插入item,或者复制新item之前的元素,插入新item,再复制item之后的元素 function reducer(state, action) { /\* State looks like: state = [1, 2, 3, 5, 6]; \*/ const newItem = 4; // make a copy const newState = state.slice(); // insert the new item at index 3 newState.splice(3, 0, newItem) return newState; /\* // You can also do it this way: return \[ // make a new array ...state.slice(0, 3), // copy the first 3 items unchanged newItem, // insert the new item ...state.slice(3) // copy the rest, starting at index 3 \]; \*/ } (9)按索引更新数组中的item 使用map找到这个item并返回新值 function reducer(state, action) { /\* State looks like: state = [1, 2, "X", 4]; \*/ return state.map((item, index) =\> { // Replace the item at index 2 if(index === 2) { return 3; } // Leave every other item unchanged return item; }); } (10)从数组中删除一个item 通过filter,传入每个item,返回一个只包含判断函数返回true的item的新数组 function reducer(state, action) { /\* State looks like: state = [1, 2, "X", 4]; \*/ return state.filter((item, index) =\> { // Remove item "X" // alternatively: you could look for a specific index if(item === "X") { return false; } // Every other item stays return true; }); }

相关推荐
全球网站建设22 天前
从结构到交互:HTML5进阶开发全解析——语义化标签、Canvas绘图与表单设计实战
javascript·前端框架·php·reactjs·css3·html5
光影少年1 个月前
react18更新哪些东西
前端·学习·reactjs
cxr8281 个月前
Vercel AI SDK 3.0 学习入门指南
前端·人工智能·学习·react.js·typescript·npm·reactjs
光影少年1 个月前
react17更新哪些新特性
前端·reactjs
解道Jdon1 个月前
AI IDE冲击下JetBrains作死,IDEA埋订阅陷阱
javascript·reactjs
止观止1 个月前
React虚拟DOM的进化之路
前端·react.js·前端框架·reactjs·react
解道Jdon2 个月前
最新苹果液体玻璃Liquid Glass效果CSS实现
javascript·reactjs
菜鸡爱上编程2 个月前
React16,17,18,19更新对比
前端·javascript·reactjs·react
stormsha3 个月前
React与原生事件:核心差异与性能对比解析
前端·javascript·react.js·云原生·reactjs