javascript - 在代理数组上调用 concat 会引发错误

标签 javascript ecmascript-6 redux

我是 ES6 Proxy 对象的新手,在尝试对已代理的数组调用 concat 时遇到了一个我不明白的错误。

背景:

我认为 ES6 Proxy 可以完美地作为一种方式来验证我的 React/Redux 应用程序中 reducer 函数的“纯度”。我可以将我的状态对象包装在代理中,如果我尝试改变该对象,该代理会抛出错误。我正在使用基于 on-change 的东西库来执行此操作:

const triggersOnChange = (object, onChange) => {
  const handler = {
    get (target, property, receiver) {
      try {
        return new Proxy(target[property], handler)
      } catch (err) {
        return Reflect.get(target, property, receiver);
      }
    }

    defineProperty (target, property, descriptor) {
      onChange()
      return Reflect.defineProperty(target, property, descriptor)
    }

    deleteProperty (target, property) {
      onChange()
      return Reflect.deleteProperty(target, property)
    }
  }

  return new Proxy(object, handler)
}

这是我打算如何使用代理包装器的示例测试:

describe('reducer', () => {
  test('it returns an updated state object', () => {
    const state = triggersOnChange({ items: [] }, () => {
      throw new Error('Oops! You mutated the state object')
    })

    const action = {
      payload: { item: 'foobar' }
    }

    expect(reducer(state, action)).toEqual({
      items: [action.payload.item]
    })
  })
})

如果我实现了一个会改变状态对象的“坏”reducer,我的测试会按预期抛出错误:

const reducer = (state, action) => {
  state.items.push(action.payload.item) // bad
  return state
}

// test throws error "Oops! You mutated the state object"

但是当我通过返回一个新的状态对象来“净化”我的 reducer 时,我得到了一个我不太理解的不同错误:

const reducer = (state, action) => {
  return Object.assign({}, state, {
    items: state.items.concat(action.payload.item)
  })
}

/* 
  TypeError: 'get' on proxy: property 'prototype' is a read-only and
  non-configurable data property on the proxy target but the proxy did
  not return its actual value (expected '[object Array]' but got
  '[object Object]')
      at Proxy.concat (<anonymous>)
*/

我在这里遗漏了一些有关代理行为的信息吗?或者这可能是我的 get 陷阱导致的代理链接行为的问题?我最初认为这是在 Object.assign 中使用代理的问题,但是在我的 reducer 返回语句之前调试时遇到了同样的错误,我实际上在其中使用了 Object.assign >。救命!

编辑:很高兴修改这个问题,使其更加通用,但我不是 100% 知道问题是什么,所以我会等待,看看是否能得到任何答案。

最佳答案

可以使用以下代码复制您的问题:

var obj = {};
Object.defineProperty(obj, "prop", { 
  configurable: false,
  value: {},
});

var p = new Proxy(obj, {
  get(target, property, receiver) {
    return new Proxy(Reflect.get(target, property, receiver), {});
  },
});

var val = p.prop;

问题的核心是对象具有必须保持一致的不变量,即使是通过代理对象访问时也是如此,在这种情况下,您将破坏这些不变量之一。如果你看the specification for Proxy's get ,它指出:

[[Get]] for proxy objects enforces the following invariants:

  • The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable own data property.
  • The value reported for a property must be undefined if the corresponding target object property is a non-configurable own accessor property that has undefined as its [[Get]] attribute.

在您的情况下,您不会维护第一个不变式,因为即使属性不可写且不可配置,您也会返回一个包装代理。最简单的方法是确保在这种情况下返回正确的值。

当我们这样做时,我还建议显式使用 typeof 而不是使用 try/catch,这样会更清晰。

get(target, property, receiver) {
  const desc = Object.getOwnPropertyDescriptor(target, property);
  const value = Reflect.get(target, property, receiver);

  if (desc && !desc.writable && !desc.configurable) return value;

  if (typeof value === "object" && value !== null) return new Proxy(value, handler);
  else return value;
},

关于javascript - 在代理数组上调用 concat 会引发错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48494674/

相关文章:

reactjs - 当多个组件使用复杂对象的一部分作为其源时,redux 如何工作

node.js - 部署到 heroku 的 React/Node 应用程序显示空白屏幕

javascript - DOM 节点和 javascript 命名空间之间有什么关系?

javascript - 如何根据第二个响应用axios更新第一个响应的内容

node.js - 如何配置 .babelrc 以支持 ES6 模块导入和异步/等待?

javascript - 具有自身默认值的对象解构赋值

javascript - Javascript 中的时区问题将日期转换为 "Today"、 "Yesterday"、 "2 days ago"等

javascript - watchify 和 gulp.watch 的区别

javascript - 将复选框与 React 中的嵌套对象数组绑定(bind)

reactjs - 如何为 React 添加 antd 单选按钮?