Базовое хранилище Vue с зависимыми вызовами API во всем приложении - PullRequest
0 голосов
/ 07 октября 2018

Я пытался задать этот вопрос на форумах Vue без ответа, поэтому я попытаюсь повторить его здесь:

У меня есть приложение, в которое клиенты входят и могут управлять несколькими учетными записями (веб-сайтами).В заголовке приложения есть раскрывающийся список, в котором пользователь может выбрать активную учетную запись, и это повлияет на все компоненты в приложении, которые отображают любую информацию об учетной записи.

Поскольку эта информация учетной записи необходимав компонентах по всему приложению я пытался следовать приведенному здесь примеру магазина (в моей ситуации Vuex выглядел слишком излишним):

https://vuejs.org/v2/guide/state-management.html

В моем файле src / main.js я определяюэто:

Vue.prototype.$accountStore = { accounts: [], selectedAccountId: null, selectedAccountDomain: null }

И это мой компонент для загрузки / изменения учетных записей:

<template>
  <div v-if="hasMoreThanOneAccount">
    <select v-model="accountStore.selectedAccountId" v-on:change="updateSelectedAccountDomain">
      <option v-for="account in accountStore.accounts" v-bind:value="account.id" :key="account.id">
        {{ account.domain }}
      </option>
    </select>
  </div>
</template>

<script>

export default {
  name: 'AccountSelector',
  data: function () {
    return {
      accountStore: this.$accountStore,
      apiInstance: new this.$api.AccountsApi()
    }
  },
  methods: {
    updateSelectedAccountDomain: function () {
      this.accountStore.selectedAccountDomain = this.findSelectedAccountDomain()
    },
    findSelectedAccountDomain: function () {
      for (var i = 0; i < this.accountStore.accounts.length; i++) {
        var account = this.accountStore.accounts[i]
        if (account.id === this.accountStore.selectedAccountId) {
          return account.domain
        }
      }

      return 'invalid account id'
    },
    loadAccounts: function () {
      this.apiInstance.getAccounts(this.callbackWrapper(this.accountsLoaded))
    },
    accountsLoaded: function (error, data, response) {
      if (error) {
        console.error(error)
      } else {
        this.accountStore.accounts = data
        this.accountStore.selectedAccountId = this.accountStore.accounts[0].id
        this.updateSelectedAccountDomain()
      }
    }
  },
  computed: {
    hasMoreThanOneAccount: function () {
      return this.accountStore.accounts.length > 1
    }
  },
  mounted: function () {
    this.loadAccounts()
  }
}
</script>

<style scoped>
</style>

Мне не кажется, что это лучший способсделай это, но я действительно не уверен, что лучше.Одна проблема заключается в том, что после обратного вызова я устанавливаю учетные записи, затем selectedAccountId, а затем selectedAccountDomain вручную.Мне кажется, что selectedAccountId и selectedDomainId должны быть вычисляемыми свойствами, но я не уверен, как это сделать, если хранилище не является компонентом Vue.

Другая проблема, с которой я столкнулся, заключается в том, что selectedAccountId не загружается дляВ первый раз я не могу делать какие-либо вызовы API в других компонентах, потому что вызовы API должны знать идентификатор учетной записи.Однако я не уверен, что лучше всего выслушать это изменение и затем выполнить вызовы API, как в первый раз, так и позже, когда оно обновляется.

1 Ответ

0 голосов
/ 10 октября 2018

В данный момент вы, кажется, используете store, чтобы просто хранить значения.Но настоящая сила паттерна Flux / Store на самом деле осознается, когда вы также централизуете логику внутри магазина.Если вы рассредоточите логику, связанную с хранилищем, между компонентами по всему приложению, в конечном итоге ее будет все сложнее и сложнее поддерживать, потому что такую ​​логику нельзя использовать повторно, и вам придется обходить дерево компонентов, чтобы достичь логики при исправлении ошибок.

На вашем месте я создам хранилище с помощью

  • Определение «первичных данных», затем
  • Определение «производных данных», которые могут быть получены из первичныхданные и, наконец,
  • Определение «методов», которые вы можете использовать для взаимодействия с такими данными.

IMO, «первичными данными» являются user, accounts иselectedAccount.И «производными данными» являются isLoggedIn, isSelectedAccountAvailable и hasMoreThanOneAccount.Как компонент Vue вы можете определить его следующим образом:

import Vue from "vue";

export default new Vue({
  data() {
    return {
      user: null,
      accounts: [],
      selectedAccount: null
    };
  },
  computed: {
    isLoggedIn() {
      return this.user !== null;
    },
    isSelectedAccountAvailable() {
      return this.selectedAccount !== null;
    },
    hasMoreThanOneAccount() {
      return this.accounts.length > 0;
    }
  },
  methods: {
    login(username, password) {
      console.log("performing login");
      if (username === "johnsmith" && password === "password") {
        console.log("committing user object to store and load associated accounts");
        this.user = {
          name: "John Smith",
          username: "johnsmith",
          email: "john.smith@somewhere.com"
        };
        this.loadAccounts(username);
      }
    },
    loadAccounts(username) {
      console.log("load associated accounts from backend");
      if (username === "johnsmith") {
        // in real code, you can perform check the size of array here
        // if it's the size of one, you can set selectedAccount here
        // this.selectedAccount = array[0];

        console.log("committing accounts to store");
        this.accounts = [
          {
            id: "001234",
            domain: "domain001234"
          },
          {
            id: "001235",
            domain: "domain001235"
          }
        ];
      }
    },
    setSelectedAccount(account) {
      this.selectedAccount = account;
    }
  }
});

Затем вы можете легко import сохранить это хранилище в любом компоненте Vue и начать ссылаться на значения или вызывать методы из этого хранилища.

Например, предположим, что вы создаете компонент Login.vue, и этот компонент должен перенаправиться, когда объект user станет доступным в магазине, вы можете сделать это, выполнив следующие действия:

<template>
  <div>
    <input type="text" v-model="username"><br/>
    <input type="password" v-model="password"><br/>
    <button @click="submit">Log-in</button>
  </div>
</template>
<script>
import store from '../basic-store';

export default {
  data() {
    return {
      username: 'johnsmith',
      password: 'password'
    };
  },
  computed: {
    isLoggedIn() {
      return store.isLoggedIn;
    },
  },
  watch: {
    isLoggedIn(newVal) {
      if (newVal) { // if computed value from store evaluates to 'true'
        console.log("moving on to Home after successful login.");
        this.$router.push({ name: "home" });
      }
    }
  },
  methods: {
    submit() {
      store.login(this.username, this.password);
    }
  }
};
</script>

Кроме того, с помощью isSelectedAccountAvailable мы вычисляем, мы можем легко отключить / включить кнопку на экране, чтобы запретить пользователю выполнять вызовы API, пока не будет выбрана учетная запись:

<button :disabled="!isSelectedAccountAvailable" @click="performAction()">make api call</button>

Если выЕсли вы хотите увидеть весь проект, вы можете получить к нему доступ из этого запускаемого кода codesandbox .Обратите внимание на то, как basic-store.js определяется и используется в Login.vue и Home.vue.И, если хотите, вы также можете увидеть, как определяется магазин в vuex, взглянув на store.js.

Удачи!


Обновлено: О том, как вы должны организовывать зависимые / связанные вызовы API, ответ на самом деле прямо перед вами.Если вы присмотритесь к магазину поближе, вы заметите, что мой метод login() впоследствии вызывает this.loadAccounts(username) после успешного входа в систему.Таким образом, в принципе, у вас есть все возможности для цепочки / вложенных вызовов API в методах магазина для соответствия вашим бизнес-правилам.watch() существует просто потому, что пользовательский интерфейс должен выполнять навигацию на основе изменений, сделанных в store.Для самых простых изменений данных будет достаточно вычисленных свойств.

Кроме того, из того, как я его спроектировал, причина watch(), используемая в <Login> компоненте, двойственна:

  1. Разделение проблем: для меня, который годами работал над серверным кодом, я бы хотел, чтобы мой view связанный код был четко отделен от model.Ограничивая логику навигации внутри компонента, моему model в store вообще не нужно знать / заботиться о навигации.

  2. Однако, даже если я этого не делаюОтдельные проблемы, все еще будет довольно трудно импортировать vue-router в мой store.Это потому, что мой router.js уже импортирует basic-store.js для выполнения защиты навигации , предотвращающей доступ неаутентифицированных пользователей к <Home> компоненту:

router.beforeEach((to, from, next) => {
  if (!store.isLoggedIn && to.name !== "login") {
    console.log(`redirect to 'login' instead of '${to.name}'.`);
    next({ name: "login" });
  } else {
    console.log(`proceed to '${to.name}' normally.`);
    next();
  }
});

И, потомуjavascript пока не поддерживает циклическую зависимость (например, router import store и store import router), чтобы мой код оставался ациклическим, мой магазин не может выполнять навигацию по маршруту.

...