React

持久化数据结构

Immutable

每次修改一个immutable对象时都会创建一个新的不可变对象,在新对象上操作并不会影响到源对象的数据

  1. Persistent Data Structure(持久化数据结构)
  2. 使用旧数据创建新数据时,要保证旧数据同时可用且不变
  3. 同时为了避免deepCopy把所有节点都复制一遍带来的性能损耗
  4. immutable使用了Structural Sharing(结构共享)
  5. 如果对象树中的一个节点发生变化,仅修改这个节点和受它影响的父节点,其它节点则进行共享

代码示例:

let obj01 = {name: 'name'}
let obj02 = obj01  // 引用复制(浅复制)
let obj03 = {...obj01}  // 比浅复制多复制了一层(非严格意义上的深复制,如果展开的是数组等类型而不是字符串等简单类型,那么值也会跟着变化)

let obj04 = {name: 'name', arr: [1, 2, 3]}
let obj05 = JSON.parse(JSON.stringify(obj04))  // 有缺陷的深复制(对象中不能有undefined,否则对应的值会被忽略)
// src/App.jsx

import React from "react";
import {createRoot} from "react-dom/client";
import ImmuTest02 from "./immutable/ImmuTest02";

createRoot(document.getElementById('root')).render(<ImmuTest02/>)
// src/immutable/App.jsx

import React, {useState} from "react";
import {Map} from 'immutable'

/*
let obj = {
    name: 'Foo',
    age: 24
}
let oldObj = Map(obj)
let newObj = oldObj.set('name', 'Bar')
console.log(oldObj, newObj)
console.log(oldObj.get('name'), newObj.get('name'))
console.log(oldObj.toJS(), newObj.toJS())  转换为原始对象
*/

export default function App() {
    let [state, setState] = useState({
        // info: Map({
        //     name: 'Foo',
        //     age: 24
        // })
        info: {
            name: 'Foo',
            age: 24
        }
    })

    return (
        <div>
            <button onClick={() => {
                // setState({
                //     info: state.info.set('name', 'Bar').set('age',18)
                // })
                let old = Map(state.info)
                let newImmu = old.set('name', 'Bar').set('age', 18)

                setState({
                    info: newImmu.toJS()
                })
            }}>Click
            </button>
            {/* {state.info.get('name')} */}
            {/* {state.info.get('age')} */}
            {state.info.name}
            {state.info.age}
        </div>
    )
}
// src/Immutable02.jsx

import React, {useState, Component} from "react";
import {Map} from "immutable";

class Child extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.filter !== nextProps.filter;
    }

    render() {
        return (
            <div>
                Child
            </div>
        )
    }

    componentDidUpdate() {
        console.log('componentDidUpdate')
    }
}

export default function Immutable02() {
    const [state, setState] = useState({
        info: Map({
            name: 'Foo',
            filter: Map({
                text: '1024',
                up: true,
                down: false
            })
        })
    })

    return (
        <div>
            <button onClick={() => {
                setState({
                    info: state.info.set('name', 'Bar')
                })
            }}>Click
            </button>
            {state.info.get('name')}
            <Child filter={state.info.get('filter')}/>
        </div>
    )
}
// src/Immutable03.jsx

import React, {useState} from "react";
import {List} from "immutable";

export default function Immutable03() {
    let arr01 = List([1, 2, 3])
    let arr02 = arr01.push(4)  // 不会影响源数据
    let arr03 = arr02.concat([5, 6, 7])
    // console.log(arr01, arr02)
    console.log(arr01.toJS(), arr02.toJS(), arr03.toJS())
    const [state] = useState({
        favor: List(['a', 'b', 'c'])
    })

    return (
        <div>
            {
                state.favor.map(item =>
                    <li key={item}>{item}</li>)
            }
        </div>
    )
}
// src/immutable/ImmuTest.jsx

import React, {useState} from "react";
import {List, Map} from "immutable";

export default function ImmuTest() {
    const [state, setState] = useState({
        info: Map({
            name: 'Foo',
            location: Map({
                province: '幻想乡',
                city: '博丽神社'
            }),
            favor: List(['心理学', '哲学', '法学'])
        })
    })

    return (
        <div>
            {state.info.get('name')}
            <br/>
            {state.info.get('location').get('province')}-
            {state.info.get('location').get('city')}
            <br/>
            {
                state.info.get('favor').map((item, index) =>
                    <li key={item}>{item}
                        <button onClick={() => {
                            setState({
                                info: state.info.set('favor', state.info.get('favor').splice(index, 1))
                            })
                        }}>
                            删除
                        </button>
                    </li>)
            }
            <button onClick={() => {
                setState({
                    info: state.info.set('name', 'Bar').set('location', state.info.get('location').set('city', '红魔馆'))
                })
            }}>修改
            </button>
        </div>
    )
}
// src/immutable/ImmuTest02.jsx

import React, {useState} from "react";
import {fromJS} from "immutable";

export default function ImmuTest02() {
    const [state, setState] = useState({
        info: fromJS({
            name: 'Foo',
            location: {
                province: '幻想乡',
                city: '博丽神社'
            },
            favor: ['心理学', '哲学', '法学']
        })
    })

    return (
        <div>
            {state.info.get('name')}
            <br/>
            {state.info.get('location').get('province')}-
            {state.info.get('location').get('city')}
            <br/>
            {
                state.info.get('favor').map((item, index) =>
                    <li key={item}>{item}
                        <button onClick={() => {
                            setState({
                                info: state.info.updateIn(['favor'], (list) => list.splice(index, 1))
                                // 因为数组的本质就是对象结构的
                            })
                        }}>
                            删除
                        </button>
                    </li>)
            }
            <button onClick={() => {
                setState({
                    info: state.info.setIn(['name'], 'Bar').setIn(['location', 'city'], '红魔馆')
                })
            }}>修改
            </button>
        </div>
    )
}