【问题标题】:Redux, normalised entities and lodash mergeRedux、规范化实体和 lodash 合并
【发布时间】:2017-01-26 11:02:58
【问题描述】:

我正在使用具有相当标准的规范化实体存储的 Redux、React 和 Lodash。

当我在 redux reducer 中合并新实体时,对我所有现有实体的引用都会更改(尽管没有被修改),导致任何纯组件重新渲染。

是否有替代 lodash 的合并的替代方法,可以在合并的同时保持对不在被合并对象中的值的现有引用?

let entities = { 
  [1]: {a: true },
  [2]: {a: true, b: true },
}
let response = { 
  [2]: {a: false }
}
let newEntities = _.merge({}, entities, response)

console.log(entities[1] === newEntities[1]) // false

我不能在这里使用 Object.assign/ES6 Spread,因为 newEntities[2].b 将被删除。

我确实意识到还有其他解决方案,例如自定义 sCU 和重新选择,但是在 reducer 级别处理这个问题会更干净,而不是必须修改每个对其属性进行相等引用检查的单个组件。

【问题讨论】:

    标签: javascript reactjs redux lodash


    【解决方案1】:

    mergeWith 与定制器一起使用:

    let keepRef = (objValue, srcValue) => (
      objValue === undefined ? srcValue : _.mergeWith({}, objValue, srcValue, keepRef)
    )
    let newEntities = _.mergeWith({}, entities, response, keepRef)
    

    【讨论】:

    • 我们是否需要测试 objValue 是否未定义?如果是,默认机制不会返回 srcValue 吗?反之亦然(其中 objValue 未定义但 srcValue 未定义)?
    • 请您查看我的解决方案,它支持数组和“按 id 收集”的数组 - stackoverflow.com/a/49437903/1828637
    【解决方案2】:

    我扩展了@Pavlo 的精彩回答。我添加了对数组和集合的支持。我将集合定义为对象数组,其中每个对象都有一个id 键。这在 react/redux 和规范化数据中很常见。

    import { mergeWith, isPlainObject, isEmpty, keyBy } from 'lodash'
    
    // https://stackoverflow.com/a/49437903/1828637
    // mergeWith customizer.
    // by default mergeWith keeps refs to everything,
    // this customizer makes it so that ref is only kept if unchanged
    // and a shallow copy is made if changed. this shallow copy continues deeply.
    // supports arrays of collections (by id).
    function keepUnchangedRefsOnly(objValue, srcValue) {
        if (objValue === undefined) { // do i need this?
            return srcValue;
        } else if (srcValue === undefined) { // do i need this?
            return objValue;
        } else if (isPlainObject(objValue)) {
            return mergeWith({}, objValue, srcValue, keepUnchangedRefsOnly);
        } else if (Array.isArray(objValue)) {
            if (isEmpty(objValue) && !isEmpty(srcValue))return [...srcValue];
            else if (!isEmpty(objValue) && isEmpty(srcValue)) return objValue;
            else if (isEmpty(objValue) && isEmpty(srcValue)) return objValue; // both empty
            else {
                // if array is array of objects, then assume each object has id, and merge based on id
                // so create new array, based objValue. id should match in each spot
    
                if (isPlainObject(objValue[0]) && objValue[0].hasOwnProperty('id')) {
                    const srcCollection = keyBy(srcValue, 'id');
    
                    const aligned = objValue.map(el => {
                        const { id } = el;
                        if (srcCollection.hasOwnProperty(id)) {
                            const srcEl = srcCollection[id];
                            delete srcCollection[id];
                            return mergeWith({}, el, srcEl, keepUnchangedRefsOnly);
                        } else {
                            return el;
                        }
                    });
    
                    aligned.push(...Object.values(srcCollection));
    
                    return aligned;
                } else {
                    return [ ...objValue, ...srcValue ];
                }
            }
        }
    }
    

    用法:

    const state = {
        chars: ['a', 'b'],
        messages: [
            {
                id: 1,
                text: 'one'
            },
            {
                id: 2,
                text: 'ref to this entry will be unchanged'
            }
        ]
    }
    
    const response = {
        chars: ['c', 'd'],
        messages: [
            {
                id: 1,
                text: 'changed ref text one'
            },
            {
                id: 3,
                text: 'three'
            }
        ]
    }
    
    const stateNext = mergeWith({}, state, response, keepUnchangedRefsOnly)
    

    结果stateNext 是:

    {
        chars: [
            'a',
            'b',
            'c',
            'd'
        ],
        messages: [
            {
                id: 1,
                text: 'changed ref text one'
            },
            {
                'id': 2,
                text: 'ref to this entry will be unchanged'
            },
            {
                'id': 3,
                text: 'three'
            }
        ]
    }
    

    如果您想保留 undefined 值,请将自定义程序中的 mergeWith 和您的用例替换为 assignWith。示例 - https://stackoverflow.com/a/49455981/1828637

    【讨论】:

      猜你喜欢
      • 2017-11-07
      • 2017-06-14
      • 2020-02-04
      • 2018-08-29
      • 2017-08-16
      • 1970-01-01
      • 2016-10-28
      • 2016-11-08
      • 1970-01-01
      相关资源
      最近更新 更多