• 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
    }
    
    • 1
    • 2
    • 3
    • 4

    */

    return {
    …state,
    clicks: state.clicks + 1,
    count: state.count - 1
    }
    }

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

    function reducer(state, action) {
    /*
    State looks like:

    state = {
      house: {
        name: "Ravenclaw",
        points: 17
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    */

    // 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
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    */

    // 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];
    
    • 1

    */

    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];
    
    • 1

    */

    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;
    
    • 1
    • 2

    });
    }

    (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'
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    */
    //通过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;
    
    • 1
    • 2

    });
    }

    (8)在数组中间插入一个item

    .splice函数将插入一个item,但会改变数组。可以先试用slice复制数组,再使用splice插入item,或者复制新item之前的元素,插入新item,再复制item之后的元素

    function reducer(state, action) {
    /*
    State looks like:

    state = [1, 2, 3, 5, 6];
    
    • 1

    */

    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];
    
    • 1

    */

    return state.map((item, index) => {
    // Replace the item at index 2
    if(index === 2) {
    return 3;
    }

    // Leave every other item unchanged
    return item;
    
    • 1
    • 2

    });
    }

    (10)从数组中删除一个item

    通过filter,传入每个item,返回一个只包含判断函数返回true的item的新数组

    function reducer(state, action) {
    /*
    State looks like:

    state = [1, 2, "X", 4];
    
    • 1

    */

    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;
    
    • 1
    • 2

    });
    }

  • 相关阅读:
    Spring Boot中的类初始化方法的执行先后顺序
    网约车出行新“风”向,曹操出行为什么死磕车内空气?
    冥想第五百一十六天
    Git用户名/密码/邮箱,及设置git配置
    MSVC 和 Visual Studio 代码诊断的未来
    驱动隐藏进程(eprocess断链)
    为啥50岁以后,病就增多了?中老年人想要少生病,该做些什么?
    【机器学习】聚类【Ⅰ】基础知识与距离度量
    19.声明式事务的事务隔离级别和事务传播行为
    罗马数字转整数------题解报告
  • 原文地址:https://blog.csdn.net/dawnyi_yang/article/details/132599053