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; }); }

相关推荐
解道Jdon19 天前
最新苹果液体玻璃Liquid Glass效果CSS实现
javascript·reactjs
菜鸡爱上编程20 天前
React16,17,18,19更新对比
前端·javascript·reactjs·react
stormsha1 个月前
React与原生事件:核心差异与性能对比解析
前端·javascript·react.js·云原生·reactjs
码界奇点1 个月前
React 生命周期与 Hook:从原理到实战全解析
前端·react.js·前端框架·生活·reactjs·photoshop
风清云淡_A1 个月前
【react18】在styled-components中引入图片报错
前端·reactjs
解道Jdon2 个月前
Redis宣布再次开源
javascript·reactjs
Dragon Wu3 个月前
前端 React 弹窗式 滑动验证码实现
前端·javascript·react.js·typescript·前端框架·reactjs
彭铖洋3 个月前
idea版的cursor:Windsurf Wave 7
javascript·reactjs
彭铖洋3 个月前
VSCode会击败Cursor和Windsurf吗?
javascript·reactjs