MobX 没有在我的 class 中正确设置 observable

MobX not setting observable properly in my class

我正在尝试让我的 React 应用程序尽可能干,对于像休息这样的常见事情 api,我创建了 类 作为具有预定义操作的商店修改起来很容易。

看,大码:

import {autorun, action, observable} from 'mobx'

export function getResourceMethods(name) {
  let lname = name.toLowerCase()
  let obj = {
    methods: {
      plural: (lname + 's'),
      add: ('add' + name),
      addPlural: ('add' + name + 's'),
      rename: ('rename' + name),
    },
    refMethods: {
      add: ('add' + name + 'ByRef'),
      addPlural: ('add' + name + 'sByRef'),
      rename: ('rename' + name + 'ByRef'),
      setRef: ('set' + name + 'Ref'),
    },
    fetchMethods: {
      pending: (lname + 'Pending'),
      fulfilled: (lname + 'Fulfilled'),
      rejected: (lname + 'Rejected'),
    }
  }
  return obj
}

class ResourceItem {
  @observable data;
  @observable fetched = false;
  @observable stats = 'pending';
  @observable error = null;

  constructor(data) {
    this.data = data;
  }
}

class ResourceList {
  @observable items = [];
  @observable fetched = false;
  @observable status = 'pending';

  constructor(name) {
    this['add' + name + 's'] = action((items) => {
      items.forEach((item, iterator) => {
        this.items.push(item.id)
      })
    })
  }
}

class ResourceStore {
  constructor(name, resourceItem, middleware) {
    let {methods} = getResourceMethods(name)

    this.middleware = middleware || []

    let items = methods.plural.toLowerCase()
    this[items] = observable({}) // <--------------- THIS DOES NOT WORK!

    // Add resource item
    this[methods.add] = action((id, resource) => {
      let item = this[items][id], data;

      if (item && item.fetched) {
        data = item.data
      } else {
        data = resource || {}
      }

      this[items][id] = new resourceItem(data)
      this.runMiddleware(this[items][id])
    })

    // Add several resource items
    this[methods.addPlural] = action((resources) => {
      resources.forEach((resource, iterator) => {
        this[methods.add](resource.id, resource)
      })
    })

    // Rename resource item
    this[methods.rename] = action((oldId, newId) => {
      let item = this[items][oldId]
      this[items][newId] = item

      if (oldId !== newId) {
        delete this[items][oldId]
      }
    })
    // Constructor ends here
  }

  runMiddleware(item) {
    let result = item;
    this.middleware.map(fn => {
      result = fn(item)
    })

    return result
  }
}

class ReferencedResourceStore extends ResourceStore {
  @observable references = {}
  constructor(name, resource, middleware) {
    super(name, resource, middleware)

    let {methods, refMethods, fetchMethods} = getResourceMethods(name)

    let getReference = (reference) => {
      return this.references[reference] || reference
    }

    this[refMethods.setRef] = action((ref, id) => {
      this.references[ref] = id
    })

    this[refMethods.add] = action((ref, data) => {
      this[methods.add](getReference(ref), data)
      this[refMethods.setRef](ref, getReference(ref))
    })

    this[refMethods.rename] = action((ref, id) => {
      this[methods.rename](getReference(ref), id)
      this[refMethods.setRef](ref, id)
    })

    // *** Fetch *** //

    // Resource pending

    this[fetchMethods.pending] = action((ref) => {
      this[refMethods.add](ref)
    })

    // Resource fulfilled

    this[fetchMethods.fulfilled] = action((ref, data) => {
      this[refMethods.add](ref, data)
      this[refMethods.rename](ref, data.id)

      let item = this[methods.plural][data.id];
      item.fetched = true
      item.status = 'fulfilled'
    })

  }
}

export {ResourceItem, ResourceList, ResourceStore, ReferencedResourceStore}

现在我只是创建一个简单的用户存储:

class UserResource extends ResourceItem {
  constructor(data) {
    super(data)
  }

  @observable posts = new ResourceList('Posts')
  @observable comments = new ResourceList('Comment')
}

// Create store
class UserStore extends ReferencedResourceStore {}
let store = new UserStore('User', UserResource)

并且 mobx-react 可以很好地连接到商店,也可以阅读。但是,每当我对项目进行任何更改时(users 在这种情况下,属性 的名称是动态的)属性,没有任何反应。我还注意到,在 chrome 中,对象 属性 在树视图中没有 "invoke property getter":

我找到了一个 hacky 解决方案:

首先,使用 extendObservable 代替(这是正确的解决方案),然后使用对象的新版本并将其设置为 属性。

let items = methods.plural.toLowerCase()
    extendObservable(this, {
      [items]: {}
    })

    // Add resource item
    this[methods.add] = action((id, resource) => {
      let item = this[items][id], data;

      if (item && item.fetched) {
        data = item.data
      } else {
        data = resource || {}
      }

      this[items][id] = new resourceItem(data)
      this.runMiddleware(this[items][id])

      this[items] = {...this[items]}
    })

这个可行,不确定是否有更好的解决方案。

您的选择是使用 extendObservable 或使用 observable map。 有关参考,请参阅 observable 的文档,特别是:

To create dynamically keyed objects use the asMap modifier! Only initially existing properties on an object will be made observable, although new ones can be added using extendObservable.

没有阅读整个要点,但如果您想在现有对象上声明一个新的可观察对象 属性,请使用 extendObservableobservable 仅创建一个盒装可观察对象,所以你现在有一个可观察的,但还没有一个可观察的属性。换句话说:

this[items] = observable({}) // <--------------- THIS DOES NOT WORK!

应该是:

extendObservable(this, {
  [items] : {}
})

N.b。如果你不能使用上面的 ES6 语法,它会脱糖为:

const newProps = {}
newProps[items] = {}
extendObservable(this, newProps)

理解这个:https://mobxjs.github.io/mobx/best/react.html

编辑:糟糕,误读了,你已经这样做了,这不是 hacky,而是正确的解决方案,只需确保在 属性 之前完成扩展用过:)