如何在Javascript中设置不可变状态的值?

时间:2018-06-07 21:22:37

标签: javascript immutability

鉴于这样的不可变状态:

alerts: {
  5a8c76171bbb57b2950000c4: [
    {
      _id:5af7c8652552070000000064
      device_id:5a8c76171bbb57b2950000c4
      count: 1
    },
    {
      _id:5af7c8722552070000000068
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    }
  ]
}

和这样的对象:

{
   _id:5af7c8652552070000000064
   device_id:5a8c76171bbb57b2950000c4
   count: 2
}

我想在警报状态(不可变)中替换具有相同id的对象,这样最终结果如下所示:

alerts: {
  5a12356ws13tch: [
    {
      _id:5af7c8652552070000000064
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    },
    {
      _id:5af7c8722552070000000068
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    }
  ]
}

我该怎么做?使用在List,Map或OrderedMap上找到的mergeDeep,getIn,setIn和updateIn?

我尝试过这样的事情......其中index为0,deviceId为5a12356ws13tch

虽然不起作用。

export const oneAlertFetched = (state, {deviceId, index, alert}) => state.setIn(['alerts', deviceId, index], alert).merge({fetching: false})

我也试过这个。不起作用。

export const oneAlertFetched = (state, {deviceId, index, alert}) => {
  const a = state.alerts[deviceId][index]
  state.alerts[deviceId][index] = Object.assign({}, a, alert)
  return
}

3 个答案:

答案 0 :(得分:1)

根据不可变,您的意思是您的财产不可写

如果您想就地修改对象(不推荐),则需要该属性至少可配置

const device = alerts['5a12356ws13tch'][0];

if (Object.getOwnPropertyDescriptor(device, 'count').configurable) {
    // Manually make it `writable`
    Object.defineProperty(device, 'count', {
        writable: true
    });

    // Update property's value
    device.count++;

    // Set it back to `non-writable`
    Object.defineProperty(device, 'count', {
        writable: false
    });
}
console.log(device.count); // 2

如果它不可配置(不能使其可写),或者您不想破坏您的应用程序(它必须是故意不可写的),那么您应该处理副本。

const device = alerts['5a12356ws13tch'][0];
alerts['5a12356ws13tch'][0] = Object.assign({}, device, {count: device.count + 1});

Object.assign()适用于扁平物体。如果您需要深层复制,请查看my SO answer there

答案 1 :(得分:0)

我认为你的意思是你想要使用更新的有效负载返回一个新对象?

function getNextAlerts(alerts, parentDeviceId, payload) {
  const alertsForDevice = alerts[parentDeviceId];
  
  if (!alertsForDevice || alertsForDevice.length === 0) {
     console.log('No alerts for device', deviceId);
     return;
  }

  return {
    ...alerts,
    [parentDeviceId]: alerts[parentDeviceId].map(item =>
      item._id === payload._id ? payload : item
    ),
  }
}

const alerts = {
  '5a12356ws13tch': [
    {
      _id: '5af7c8652552070000000064',
      device_id: '5a8c76171bbb57b2950000c4',
      count: 1
    },
    {
      _id: '5af7c8722552070000000068',
      device_id: '5a8c76171bbb57b2950000c4',
      count: 2
    }
  ]
};

const nextAlerts = getNextAlerts(alerts, '5a12356ws13tch', {
   _id: '5af7c8652552070000000064',
   device_id: '5a8c76171bbb57b2950000c4',
   count: 2,
});

console.log('nextAlerts:', nextAlerts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

答案 2 :(得分:0)

如果您正在使用纯JavaScript对象并希望保持&#34; immutable&#34;方法你必须在状态对象的嵌套结构中使用遍布。

但是,有一些工具已经针对这个问题 - 镜头。

这是两种方法的示例,阵列/对象传播和镜头 - ramda repl

简而言之,您的例子是通过点差:

const oneAlertFetched = (state, { deviceId, index, alert }) => ({
  ...state,
  alerts: {
    ...state.alerts,
    [deviceId]: [
      ...state.alerts[deviceId].slice(0, index),
      { ...state.alerts[deviceId][index], ...alert },
      ...state.alerts[deviceId].slice(index + 1)
    ],
  }
})

使用Ramda&#39; over, lensPath, merge and __ *:

const oneAlertFetched = (state, { deviceId, index, alert }) =>
  R.over(
    R.lensPath(['alerts', deviceId, index]),
    R.merge(R.__, alert),
    state
  )

* R.__占位符用于交换1st&amp; R.merge

的第二个参数

PS:有意调整镜头解决方案以匹配您的功能声明,因此您可以轻松比较两种方法。然而,在现实生活中,借助如此强大而灵活的工具,我们可以将该功能重写为更具可读性,可重用性和高性能。