Correct way of Creating multiple stores with mobx

2020-02-09 05:06发布

As suggested here in the Mobx documentation I have created multiple stores in the following manner:

class bankAccountStore {
  constructor(rootStore){
    this.rootStore = rootStore;
  }
...

class authStore {
  constructor(rootStore){
    this.rootStore = rootStore;
  }
...

And finally creating a root store in the following manner. Also I prefer to construct children stores within master's store constructor. Moreover, I found that sometimes my child store has to observe some data from parent store, so I pass this into child constructors

class RootStore {
  constructor() {
    this.bankAccountStore = new bankAccountStore(this);
    this.authStore = new authStore(this);
  }
}

Providing to the App in following manner:

<Provider rootStore={new RootStore()}>
  <App />
</Provider>

And injecting to the component in like this:

@inject('rootStore') 
@observer
class User extends React.Component{
  constructor(props) {
    super(props);
    //Accessing the individual store with the help of root store
    this.authStore = this.props.rootStore.authStore;
  }
}

Is it the correct and efficient way to inject the root store everytime to the component even if it needs a part of the root store? If not how to inject auth Store to the user component?

EDIT: I have made an answer concluding the github discussion. Link of the discussion provided in the answer

2条回答
太酷不给撩
2楼-- · 2020-02-09 05:48

This answer may be opinionated but it may help the community indirectly.
After a lot of research, I saw below approaches used in practice by many. General methods Have a root store that can act as a communication channel between stores.

Question 1: How to organise stores and inject them into the component?

Approach 1:

App.js

// Root Store Declaration
class RootStore {
    constructor() {
      this.userStore = new UserStore(this);
      this.authStore = new AuthStore(this);
    }
}    
const rootStore = new RootStore()

// Provide the store to the children
<Provider 
    rootStore={rootStore}
    userStore={rootStore.userStore}
    authStore={rootStore.authStore}
>
  <App />
</Provider>

Component.js

// Injecting into the component and using it as shown below
@inject('authStore', 'userStore')
@observer
class User extends React.Component {
    // only this.props.userStore.userVariable
}

Approach 2:

App.js

class RootStore {
    constructor() {
      this.userStore = new UserStore(this);
      this.authStore = new AuthStore(this);
    }
} 
const rootStore = new RootStore()

<Provider rootStore={rootStore}>
  <App />
</Provider>

Component.js

// Injecting into the component and using it as shown below
@inject(stores => ({
    userStore: stores.userStore,
    authStore: stores.authStore,
    })
)
@observer
class User extends React.Component {
    // no this.props.rootStore.userStore,userVariable here, 
    // only this.props.userStore.userVariable
}

Approach 1 and Approach 2 doesn't make any difference other than syntax difference. Okay! that is the injection part!

Question 2: How to have an inter-store communication? (Try to avoid it)

Now I know a good design keeps stores independent and less coupled. But somehow consider a scenario where I want the variable in UserStore to change if a certain variable in AuthStore is changed. Use Computed. This approach is common for both the above approaches

AuthStore.js

export class AuthStore {    
    constructor(rootStore) {
        this.rootStore = rootStore
        @computed get dependentVariable() {
          return this.rootStore.userStore.changeableUserVariable;                                      
        }
    }
}

I hope this helps the community. For more detailed discussion you can refer to the issue raised by me on Github

查看更多
你好瞎i
3楼-- · 2020-02-09 06:00

I would recommend you to have multiple stores, to avoid chaining of stores. As we do in our application:

class RootStore {
    @observable somePropUsedInOtherStores = 'hello';
}

class AuthStore {
    @observeble user = 'Viktor' ; 

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

    // this will reevaluate based on this.rootStore.somePropUsedInOtherStores cahnge
    @computed get greeting() {
        return `${this.rootStore.somePropUsedInOtherStores} ${this.user}`
    }
}

const rootStore = new RootStore();

const stores = {
    rootStore,
    bankAccountStore: new BankAccountStore(rootStore),
    authStore = new AuthStore(rootStore) 
}

<Provider {...stores}>
  <App />
</Provider>

In such a manner you can access exactly the store you need, as mostly one store covers one domain instance. Still, both sub-stores are able to communicate to rootStore. Set its properties or call methods on it.

If you do not need a cross store communication - you may not need a rootStore at all. Remove it and don't pass to other stores. Just keep 2 siblings stores

Answering your question on injecting not a whole store, you may benefit from mapperFunction (like mapStateToProps in redux) docs here

@inject(stores => ({
        someProp: stores.rootStore.someProp
    })
)
@observer
class User extends React.Component {
// no props.rootStore here, only props.someProp

}
查看更多
登录 后发表回答