Правильный способ создания нескольких магазинов с помощью mobx и внедрения его в компонент - ReactJs

Как было предложено здесь в документации Mobx, я создал несколько магазинов следующим образом:

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

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

И, наконец, создание корневого хранилища следующим образом. Также я предпочитаю для создания дочерних хранилищ в конструкторе главного хранилища. Более того, я обнаружил, что иногда моему дочернему хранилищу необходимо наблюдать некоторые данные из родительского хранилища, поэтому я передаю это дочерним конструкторам.

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

Предоставление приложению следующим образом:

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

И вводим в компонент вот так:

@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;
  }
}

Это правильный и эффективный способ каждый раз вставлять корневое хранилище в компонент, даже если ему нужна часть корневого хранилища? Если нет, то как внедрить Auth Store в пользовательский компонент?

РЕДАКТИРОВАТЬ: я ответил, завершая обсуждение github. Ссылка на обсуждение в ответе


person uneet7    schedule 27.01.2019    source источник


Ответы (3)


Этот ответ может быть самоуверенным, но он может косвенно помочь сообществу.
После долгих исследований я увидел ниже подходы, используемые на практике многими. Общие методы Имеют корневое хранилище, которое может выступать в качестве канала связи между хранилищами.

Вопрос 1. Как организовать магазины и встроить их в компонент?

Подход 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
}

Подход 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
}

Подход 1 и подход 2 не имеют никакого значения, кроме разницы в синтаксисе. Хорошо! это инъекционная часть!

Вопрос 2: Как наладить межмагазинную связь? (Постарайтесь избежать этого)

Теперь я знаю, что хороший дизайн делает магазины независимыми и менее связанными. Но как-нибудь рассмотрим сценарий, в котором я хочу, чтобы переменная в UserStore изменялась, если изменена определенная переменная в AuthStore. Используйте Computed. Этот подход является общим для обоих вышеперечисленных подходов.

AuthStore.js

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

Надеюсь, это поможет сообществу. Для более подробного обсуждения вы можете обратиться к проблеме, поднятой мной на Github

person uneet7    schedule 28.03.2019
comment
Поскольку этот ответ получает много положительных отзывов, обратите внимание, что эти подходы не работают с сериализатором, если вы хотите использовать mobx в проекте SSR. - person uneet7; 09.07.2020

Я бы порекомендовал вам иметь несколько магазинов, чтобы не связывать их друг с другом. Как мы это делаем в нашем приложении:

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>

Таким образом вы можете получить доступ именно к тому магазину, который вам нужен, поскольку в большинстве случаев один магазин охватывает один экземпляр домена. Тем не менее, оба субмагазина могут связываться с rootStore. Задайте для него его свойства или вызовите методы.

Если вам не нужен обмен данными между магазинами - возможно, вам вообще не понадобится rootStore. Снимите и не передавайте в другие магазины. Просто оставьте 2 магазина братьев и сестер

Отвечая на ваш вопрос о внедрении не всего магазина, вы можете воспользоваться mapperFunction (например, mapStateToProps в redux) документы здесь

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

}
person Shevchenko Viktor    schedule 28.01.2019
comment
У меня есть все, кроме одного. Какой смысл создавать пустой rootStore и затем передавать его ссылку в другие магазины? - person uneet7; 28.01.2019
comment
Очевидно, rootStore не пуст и содержит некоторые функции. Исходя из лучших практик разделения ответственности, лучше сообщить bankAccountStore и authStore о rootStore (что позволит им общаться) вместо того, чтобы сообщать им друг о друге. Таким образом, rootStore может играть роль шины, позволяющей другим магазинам общаться. - person Shevchenko Viktor; 28.01.2019
comment
Таким образом, new rootStore() является экземпляром того же класса rootStore, который объявлен в вопросе. Верный? - person uneet7; 28.01.2019
comment
Если нет, можете ли вы добавить в свой ответ объявление класса rootStore? - person uneet7; 28.01.2019
comment
Обновил мой ответ - person Shevchenko Viktor; 29.01.2019
comment
Но что, если я хочу получить доступ к данным в одном магазине из другого? Поскольку заранее неизвестно, какие данные будут совместно использоваться между хранилищами, и после того, как это будет реализовано, перемещение их в корневое хранилище - это накладные расходы, я полагаю. - person uneet7; 29.01.2019
comment
Основная цель, которую я хочу указать, - предпочитать взаимодействие между магазинами, а не третьим, а не прямым, так как это позволит вам лучше изолировать магазины друг от друга. Я не знаю особенностей вашего приложения, но это процесс разработки, вы видите, что что-то должно работать по-другому - ваш рефакторинг. Да, это накладные расходы - но если ваши болячки зависят друг от друга - ожидайте больших проблем, когда вам придется реорганизовать один или удалить. Когда это может быть недопустимо для приложения с 2 магазинами, но если у вас их 20 - это будет иметь значение. - person Shevchenko Viktor; 29.01.2019
comment
@ShevchenkoViktor Как вы справитесь, если у вас есть компонент, в который вводится bankAccountStore, как в вашем примере, и который отображается несколько раз. И если родительский компонент должен получить доступ ко всем значениям в bankAccountStore (который может иметь несколько реквизитов банковского счета). - person Achilles; 19.03.2019
comment
одно из чистых решений - person Hidayt Rahman; 27.06.2021

Инициализация вашего RootStore непосредственно в файле api.js перед его передачей провайдеру иногда не является тем, что вам нужно. Это может затруднить внедрение экземпляра класса основного магазина в другие файлы js:

Пример 1:

app.js - создает новый экземпляр перед его передачей поставщику:

//Root Store Declaration
class RootStore {
    constructor() {
      ...
    }
}    

const rootStore = new RootStore()

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

Пример 2:

RootStore.js - создает новый экземпляр прямо в классе RootStore:

// Root Store Declaration
class RootStore {
    constructor() {
      ...
    }
}    

export default new RootStore();

Example 1 по сравнению с Example 2, затрудняет доступ / внедрение хранилища в другую часть приложения, как в Api.js, описанном ниже.

Файл api.js представляет собой обертку axios (в моем случае он обрабатывает глобальный индикатор загрузки):

import rootStore from '../stores/RootStore'; //right RootStore is simply imported

const axios = require('axios');
const instance = axios.create({
 ...
});

// Loading indicator
instance.interceptors.request.use(
    (request) => {
        rootStore.loadingRequests++;
        return request;
    },
    (error) => {
        rootStore.loadingRequests--; 
        return Promise.reject(error);
    }
)

А с помощью React Hooks вы можете внедрить магазин таким образом:

import { observer, inject } from "mobx-react";
const YourComponent = ({yourStore}) => {
      return (
          ...
      )
}

export default inject('yourStore')(observer(YourComponent));
person kamil-kubicki    schedule 18.06.2020