Jednostránkové aplikace s Vue.js a Flask:Správa stavu s Vuex

Správa státu s Vuex

Děkuji, že jste se ke mně připojili u třetího příspěvku o používání Vue.js a Flask pro vývoj webu v plném rozsahu. Hlavním tématem tohoto příspěvku bude použití vuex ke správě stavu v naší aplikaci. Abych představil vuex, ukážu, jak refaktorovat komponenty Home a Survey z předchozího příspěvku pro využití vuex, a také vybuduji schopnost přidávat nové průzkumy pomocí vzoru vuex.

Kód pro tento příspěvek je v úložišti na mém účtu GitHub v pobočce Třetí příspěvek .

Obsah seriálu

  1. Nastavení a seznámení s VueJS
  2. Navigace Vue Router
  3. Správa státu s Vuex (jste zde)
  4. RESTful API s Flask
  5. Integrace AJAX s REST API
  6. Ověření JWT
  7. Nasazení na virtuální privátní server

Představujeme Vuex

Vuex je centralizovaná knihovna státní správy oficiálně podporovaná základním vývojovým týmem Vue.js. Vuex poskytuje jednosměrný datový tok podobný toku, vzor, ​​který se ukázal jako velmi účinný při podpoře středně velkých až velkých aplikací Vue.js.

Existují další implementace vzorů a knihoven řízení stavu podobných toku, ale vuex byl navržen tak, aby specificky pracoval s rychlým a jednoduchým systémem reaktivity Vue.js. Toho je dosaženo prostřednictvím dobře navrženého rozhraní API, které poskytuje jediný zdroj pravdy pro data aplikace jako jediný objekt. Kromě principu jednotného zdroje pravdy poskytuje vuex také explicitní a sledovatelné metody pro asynchronní operace (akce), pohodlné opakovaně použitelné přístupové objekty (gettery) a možnosti změny dat (mutace).

Chcete-li použít vuex, musím jej nejprve nainstalovat do stejného adresáře, který obsahuje soubor package.json takto:

$ npm install --save vuex

Dále přidám nový adresář do adresáře src/ projektu s názvem "store" a přidám soubor index.js. Výsledkem je struktura projektu survey-spa, která nyní vypadá takto (ignorujte adresáře node_modules, build a config):

├── index.html
├── package-lock.json
├── package.json
├── src
│   ├── App.vue
│   ├── api
│   │   └── index.js
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   ├── Header.vue
│   │   ├── Home.vue
│   │   └── Survey.vue
│   ├── main.js
│   ├── router
│   │   └── index.js
│   └── store
│       └── index.js
└── static
    └── .gitkeep

Uvnitř souboru store/index.js začnu přidáním nezbytných importů pro objekty Vue a Vuex a poté připojím Vuex k Vue pomocí Vue.use(Vuex) podobně jako to bylo provedeno s vue-routerem. Poté definuji čtyři vyřazené objekty JavaScriptu:state , actions , mutations a getters .

Na konci souboru definuji konečný objekt, který je instancí Vuex.Store({}) objekt, který stáhne všechny ostatní stub objekty dohromady a poté je exportován.

// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {
  // single source of data
}

const actions = {
  // asynchronous operations
}

const mutations = {
  // isolated data mutations
}

const getters = {
  // reusable data accessors
}

const store = new Vuex.Store({
  state,
  actions,
  mutations,
  getters
})

export default store

Dobře, dejte mi chvíli na vysvětlení významu state , actions , mutations a getters objektů.

state objekt bude sloužit jako jediný zdroj pravdy, kde jsou v úložišti obsažena všechna důležitá data na úrovni aplikace. Toto state objekt bude obsahovat data průzkumu, ke kterým budou mít přístup a mohou sledovat změny pro všechny komponenty, které o ně mají zájem, jako je například komponenta Home.

actions objekt je místo, kde budu definovat to, co je známé jako akce metody. Metody akcí se označují jako „odeslání“ a používají se ke zpracování asynchronních operací, jako jsou volání AJAX externí službě nebo rozhraní API.

mutations object poskytuje metody, které jsou označovány jako "committed" a slouží jako jediný způsob, jak změnit stav dat v state objekt. Když je mutace potvrzena, všechny komponenty, které odkazují na nyní reaktivní data v state objekt se aktualizují novými hodnotami, což způsobí, že uživatelské rozhraní aktualizuje a znovu vykresluje své prvky.

getters objekt také obsahuje metody, ale v tomto případě slouží pro přístup k state data využívající určitou logiku k vrácení informací. Gettery jsou užitečné pro snížení duplikace kódu a podporují opětovné použití v mnoha komponentách.

Poslední nezbytný krok k aktivaci obchodu se odehrává zpět v src/main.js, kam importuji store právě vytvořený modul. Potom dolů v objektu options, kde je nejvyšší úroveň Vue instance je vytvořena, přidám importovaný store jako majetek. Mělo by to vypadat následovně:

// src/main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

Migrace domácí komponenty na Vuex

Chtěl bych začít používat vuex v aplikaci Survey migrací způsobu, jakým se průzkumy načítají do komponenty Home, aby se používal vzor vuex. Pro začátek definuji a inicializuji prázdné pole průzkumů v state objekt v rámci store/index.js. Toto bude místo, kde budou uložena všechna data průzkumu na úrovni aplikace, jakmile budou načtena požadavkem AJAX.

const state = {
  // single source of data
  surveys: []
}

Nyní, když mají průzkumy místo k pobytu, musím vytvořit metodu akce, loadSurveys(...) , který lze odeslat z komponenty Home (nebo jakékoli jiné komponenty vyžadující data průzkumu) ke zpracování asynchronního požadavku na falešnou funkci AJAX fetchSurveys() . Chcete-li použít fetchSurveys() Nejprve jej musím importovat z api pak definujte loadSurveys(...) akční metoda pro zpracování požadavku.

Akce často fungují v tandemu s mutacemi ve vzoru provádění asynchronních požadavků AJAX na data na server s následnou explicitní aktualizací state obchodu. objekt s načtenými daty. Jakmile je mutace potvrzena, části aplikace používající průzkumy rozpoznají, že jsou aktualizované průzkumy prostřednictvím systému reaktivity Vue. Zde se mutace, kterou definuji, nazývá setSurveys(...) .

import Vue from 'vue'
import Vuex from 'vuex'

// imports of AJAX functions go here
import { fetchSurveys } from '@/api'

Vue.use(Vuex)

const state = {
  // single source of data
  surveys: []
}

const actions = {
  // asynchronous operations
  loadSurveys(context) {
    return fetchSurveys()
      .then((response) => context.commit('setSurveys', { surveys: response }))
  }
}

const mutations = {
  // isolated data mutations
  setSurveys(state, payload) {
    state.surveys = payload.surveys
  }
}

S obchodem, který nyní disponuje schopností načítat průzkumy, mohu aktualizovat komponentu Home a využívat obchod k načítání dat průzkumu. Zpět v src/components/Home.vue odstraním import souboru fetchSurveys funkce:

import { fetchSurveys } from '@/api'

a nahraďte jej importem do pomocné funkce vuex s názvem mapState .

import { mapState } from 'vuex'

Použiji mapState k mapování surveys pole, které se nachází v state objekt na vypočítanou vlastnost také nazývanou surveys . mapState je jednoduše funkce, která udržuje odkaz na konkrétní vlastnost state objekt (state.surveys v tomto případě) a pokud je tato vlastnost mutována komponentou pomocí mapState bude reagovat na tuto změnu a obnoví jakékoli uživatelské rozhraní, které je s těmito daty spojeno.

V komponentě Home jsem přidal nový surveys vypočítaný majetek. Navíc v beforeMount způsob I spouštím odeslání loadSurveys akci obchodu. Protože nyní existuje vypočítaná vlastnost nazvaná surveys Měl bych odstranit stávající surveys vlastnost z datové části objektu Vue komponenty. Ve skutečnosti, protože to byla jediná datová vlastnost, měl bych také odstranit celou datovou vlastnost, aby bylo vše v pořádku, jak je ukázáno níže.

<script>
import { mapState } from 'vuex'
export default {
  computed: mapState({
    surveys: state => state.surveys
  }),
  beforeMount() {
    this.$store.dispatch('loadSurveys')
  }
}
</script>

Všimněte si, že mám přístup k obchodu a odesílám metodu akce se syntaxí this.$store.dispatch(...) . Mělo by to vypadat podobně jako způsob, jakým jsem přistupoval k trase v předchozím článku pomocí this.$route . Je to proto, že jak vue-router, tak knihovna vuex vkládají tyto objekty do instance Vue jako komfortní vlastnosti. Mohl jsem také získat přístup k state.surveys obchodu pole z komponenty pomocí this.$store.state.surveys místo použití mapState a mohu také provést mutace pomocí this.$store.commit .

V tuto chvíli bych měl být schopen uložit svůj projekt a sledovat stejnou funkčnost v prohlížeči zadáním adresy URL localhost:8080 jak bylo vidět dříve.

Migrace komponenty průzkumu

Dalším úkolem je migrovat komponentu Průzkum tak, aby využila úložiště vuexu k načtení konkrétního průzkumu, který se má zúčastnit. Obecným postupem pro komponentu Survey bude přístup k :id podepření trasy a poté použijte metodu akce vuex k načtení průzkumu podle tohoto id . Místo přímého volání simulované funkce AJAX fetchSurvey jako dříve, chci to delegovat na jinou metodu akce úložiště, která pak může uložit (tj. provést mutaci) načtený průzkum do state vlastnost pojmenuji currentSurvey .

Počínaje modulem store/index.js změním tento řádek:

import { fetchSurveys } from '@/api'

do

import { fetchSurveys, fetchSurvey } from '@/api'

To mi dává přístup k fetchSurvey v modulu prodejny. Používám fetchSurvey v nové akční metodě s názvem loadSurvey což pak způsobí mutaci v další nové metodě v rámci mutations objekt s názvem setCurrentSurvey .

// src/store/index.js

const actions = {
  // asynchronous operations
  loadSurveys(context) {
    // omitted for brevity
  },
  loadSurvey(context, { id }) {
    return fetchSurvey(id)
      .then((response) => context.commit('setSurvey'. { survey: response }))
  }
}

Výše je implementace fetchSurvey metoda akce podobná předchozí fetchSurveys , kromě toho, že je mu přidělen další parametr objektu s vlastností id pro průzkum, který má být načten. Pro zjednodušení přístupu k id používám ES2015 object destructuring. Když je akce volána z komponenty, syntaxe bude vypadat takto this.$store.dispatch('loadSurvey', { id: 1 }) .

Dále přidám currentSurvey vlastnost na state objekt. Nakonec definuji mutaci nazvanou setSurvey v mutations objekt, který přidá choice pole pro každou otázku, chcete-li podržet vybranou volbu účastníka průzkumu, a nastavit hodnotu currentSurvey .

const state = {
  // single source of data
  surveys: [],
  currentSurvey: {}
}

const actions = { // omitted for brevity }

const mutations = {
  // isolated data mutations
  setSurveys(state, payload) {
    state.surveys = payload.surveys
  },
  setSurvey(state, payload) {
    const nQuestions = payload.survey.questions.length
    for (let i = 0; i < nQuestions; i++) {
      payload.survey.questions[i].choice = null
    }
    state.currentSurvey = payload.survey
  }
}

V souboru komponenty Survey.vue aktualizuji beforeMount způsob odeslání loadSurvey akce a mapa state.currentSurvey na vypočítanou vlastnost s názvem survey . Poté mohu odstranit stávající survey datová vlastnost.

<script>
import { saveSurveyResponse } from '@/api'

export default {
  data() {
    return {
      currentQuestion: 0
    }
  },
  beforeMount() {
    this.$store.dispatch('loadSurvey', { id: parseInt(this.$route.params.id) })
  },
  methods: {
    // omitted for brevity
  },
  computed: {
    surveyComplete() {
      // omitted for brevity
    },
    survey() {
      return this.$store.state.currentSurvey
    }
  }
}
</script>

Uložte soubory projektu a obnovte prohlížeč, abyste požádali o adresu URL localhost:8080/#/surveys/2 mi znovu nabízí stejné uživatelské rozhraní, jak je uvedeno níže.

Nicméně je tu ještě trochu problém. V kódu šablony, který zobrazuje možnosti každé otázky, používám v-model="question.choice" sledovat změny, když uživatel vybere volbu.

<div v-for="choice in question.choices" v-bind:key="choice.id">
  <label class="radio">
    <input type="radio" v-model="question.choice" :value="choice.id">
    {{ choice.text }}
  </label>
</div>

Výsledkem jsou změny v question.choice hodnoty, na které se odkazuje v obchodě state.currentQuestion vlastnictví. Toto je příklad nesprávné změny uložených dat mimo mutaci. Dokumentace vuex doporučuje, aby jakékoli změny údajů o stavu obchodu byly prováděny výhradně pomocí mutací. Možná se ptáte, jak potom mohu použít v-model v kombinaci se vstupním prvkem, který je řízen daty pocházejícími z obchodu vuex?

Odpovědí na to je použití mírně pokročilejší verze vypočítané vlastnosti, která obsahuje definovaný pár get a set metody v něm. To poskytuje v-model mechanismus pro využití 2-cestné datové vazby mezi uživatelským rozhraním a objektem Vue komponenty. Tímto způsobem má vypočítaná vlastnost explicitně kontrolu nad interakcemi s daty obchodu. V kódu šablony musím nahradit v-model="question.choice" s novou vypočítanou vlastností, jako je tato v-model="selectedChoice" . Níže je uvedena implementace vypočítané vlastnosti selectedChoice .

  computed: {
    surveyComplete() {
      // omitted for brevity
    },
    survey() {
      return this.$store.state.currentSurvey
    },
    selectedChoice: {
      get() {
        const question = this.survey.questions[this.currentQuestion]
        return question.choice
      },
      set(value) {
        const question = this.survey.questions[this.currentQuestion]
        this.$store.commit('setChoice', { questionId: question.id, choice: value })
      }
    }
  }

Všimněte si, že v této implementaci selectedChoice je ve skutečnosti vlastnost objektu místo funkce jako ostatní. get funkce funguje ve spojení s currentQuestion data, která vrátí hodnotu choice hodnotu aktuálně zobrazené otázky. set(value) část obdrží novou hodnotu, která je napájena z v-model 's 2-way data binding a odevzdá store mutaci nazvanou setChoice . setChoice mutaci je předán datový obsah objektu obsahující id otázky, která má být aktualizována spolu s novým value .

Přidávám setChoice mutace do modulu úložiště takto:

Zdarma e-kniha:Git Essentials

Prohlédněte si našeho praktického průvodce učením Git s osvědčenými postupy, průmyslově uznávanými standardy a přiloženým cheat sheetem. Přestaňte používat příkazy Google Git a skutečně se naučte to!

const mutations = {
  setSurveys(state, payload) {
    state.surveys = payload.surveys
  },
  setSurvey(state, payload) {
    // omitted for brevity
  },
  setChoice(state, payload) {
    const { questionId, choice } = payload
    const nQuestions = state.currentSurvey.questions.length
    for (let i = 0; i < nQuestions; i++) {
      if (state.currentSurvey.questions[i].id === questionId) {
        state.currentSurvey.questions[i].choice = choice
        break
      }
    }
  }
}

Poslední věcí, kterou je třeba v komponentě Průzkum migrovat, je uložení voleb odpovědí na průzkum. Pro začátek musím v Survey.vue odstranit import saveSurveyResponse funkce AJAX

import { saveSurveyResponse } from '@/api'

a přidejte jej jako import do modulu src/store/index.js takto:

import { fetchSurveys, fetchSurvey, saveSurveyResponse } from '@/api'

Nyní dole v actions metody modulu store/index.js Potřebuji přidat novou metodu nazvanou addSurveyResponse , který zavolá saveSurveyResponse funkci AJAX a případně ji uložit na server.

const actions = {
  loadSurveys(context) {
    // omitted for brevity
  },
  loadSurvey(context, { id }) {
    // omitted for brevity
  },
  addSurveyResponse(context) {
    return saveSurveyResponse(context.state.currentSurvey)
  }
}

Zpět v souboru komponenty Survey.vue musím aktualizovat handleSubmit metoda k odeslání této akční metody namísto přímého volání saveSurveyResponse takhle:

methods: {
    goToNextQuestion() {
      // omitted for brevity
    },
    goToPreviousQuestion() {
      // omitted for brevity
    },
    handleSubmit() {
      this.$store.dispatch('addSurveyResponse')
        .then(() => this.$router.push('/'))
    }
}

Přidání možnosti vytvářet nové průzkumy

Zbytek tohoto příspěvku bude věnován budování funkcí pro vytvoření nového průzkumu s jeho názvem, otázkami a možnostmi pro každou otázku.

Pro začátek budu muset přidat komponentní soubor s názvem NewSurvey.vue do adresáře komponent. Dále to budu chtít importovat a přidat novou trasu v modulu router/index.js takto:

// other import omitted for brevity
import NewSurvey from '@/components/NewSurvey'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }, {
      path: '/surveys/:id',
      name: 'Survey',
      component: Survey
    }, {
      path: '/surveys',
      name: 'NewSurvey',
      component: NewSurvey
    }
  ]
})

Do souboru Header.vue musím přidat navigační odkaz, abych mohl přejít do zobrazení vytvoření.

<template>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
  <div class="navbar-menu">
    <div class="navbar-start">
      <router-link to="/" class="navbar-item">
        Home
      </router-link>
      <router-link to="/surveys" class="navbar-item">
        Create Survey
      </router-link>
    </div>
  </div>
</nav>
</template>

Nyní v komponentě NewSurvey.vue vytvořím základní strukturu uživatelského rozhraní pro vytvoření průzkumu.

<template>
  <div>
    <section class="hero is-primary">
      <div class="hero-body">
        <div class="container has-text-centered">
          <h2 class="title">{{ name }}</h2>
        </div>
      </div>
    </section>

    <section class="section">
      <div class="container">
        <div class="tabs is-centered is-fullwidth is-large">
            <ul>
                <li :class="{'is-active': step == 'name'}" @click="step = 'name'">
                    <a>Name</a>
                </li>
                <li :class="{'is-active': step == 'questions'}" @click="step = 'questions'">
                    <a>Questions</a>
                </li>
                <li :class="{'is-active': step == 'review'}" @click="step = 'review'">
                    <a>Review</a>
                </li>
            </ul>
        </div>
        <div class="columns">
          <div class="column is-half is-offset-one-quarter">

            <div class="name" v-show="step === 'name'">
              <h2 class='is-large'>Add name</h2>
            </div>

            <div class="questions" v-show="step === 'questions'">
              <h2>Add Questions</h2>
            </div>

            <div class="review" v-show="step === 'review'">
              <h2>Review and Submit</h2>
            </div>

          </div>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
export default {
  data() {
    return {
      step: 'name'
    }
  }
}
</script>

<style></style>

Jak můžete vidět na snímku obrazovky výše, existují tři karty, které spustí zobrazení komponent uživatelského rozhraní pro přidání názvu, otázek a recenze před uložením.

Funkce, které řídí interaktivitu této stránky, jsou diktovány na základě hodnoty step vlastnost data, která určuje, která karta by měla být aktivní. step výchozí na kartu "name", ale aktualizuje se, když uživatel klikne na jednu z dalších karet. Nejen hodnota step určit, která karta by měla mít is-active třídy, ale také řídí zobrazování a skrývání divs které poskytují uživatelské rozhraní pro přidání jména, otázky a recenze před odesláním.

Začínám jménem uživatelského rozhraní div který jednoduše obsahuje textový vstup svázaný s name datová vlastnost přes v-model , asi takhle:

část šablony

<div class="name" v-show="step === 'name'">
  <div class="field">
    <label class="label" for="name">Survey name:</label>
    <div class="control">
      <input type="text" class="input is-large" id="name" v-model="name">
    </div>
  </div>
</div>

část skriptu

data() {
  return {
    step: 'name',
    name: ''
  }
}

Uživatelské rozhraní otázek a odpovědí bude o něco více zapojeno. Aby komponenta NewSurvey byla lépe organizovaná a snížila se složitost, přidám komponentu souboru NewQuestion.vue, která bude spravovat uživatelské rozhraní a chování nezbytné pro přidávání nových otázek spolu s proměnným počtem odpovědí.

Měl bych také poznamenat, že pro komponenty NewSurvey a NewQuestion budu využívat stav na úrovni komponent k izolaci obchodu od zprostředkovatelských nových dat průzkumu, dokud uživatel neodešle nový průzkum. Po odeslání se zapojím do obchodu vuex a souvisejícího vzoru odeslání akce k POST novému průzkumu na server a poté přesměrování na komponentu Home. Komponenta Home pak může načíst všechny průzkumy včetně nového.

V souboru NewQuestion.vue mám nyní následující kód:

<template>
<div>
    <div class="field">
        <label class="label is-large">Question</label>
        <div class="control">
            <input type="text" class="input is-large" v-model="question">
        </div>
    </div>

    <div class="field">
        <div class="control">
            <a class="button is-large is-info" @click="addChoice">
                <span class="icon is-small">
                <i class="fa fa-plus-square-o fa-align-left" aria-hidden="true"></i>
                </span>
                <span>Add choice</span>
            </a>
            <a class="button is-large is-primary @click="saveQuestion">
                <span class="icon is-small">
                    <i class="fa fa-check"></i>
                </span>
                <span>Save</span>
            </a>
        </div>
    </div>

    <h2 class="label is-large" v-show="choices.length > 0">Question Choices</h2>
    <div class="field has-addons" v-for="(choice, idx) in choices" v-bind:key="idx">
      <div class="control choice">
        <input type="text" class="input is-large" v-model="choices[idx]">
      </div>
      <div class="control">
        <a class="button is-large">
          <span class="icon is-small" @click.stop="removeChoice(choice)">
            <i class="fa fa-times" aria-hidden="true"></i>
          </span>
        </a>
      </div>
    </div>
</div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      choices: []
    }
  },
  methods: {
    removeChoice(choice) {
      const idx = this.choices.findIndex(c => c === choice)
      this.choices.splice(idx, 1)
    },
    saveQuestion() {
      this.$emit('questionComplete', {
        question: this.question,
        choices: this.choices.filter(c => !!c)
      })
      this.question = ''
      this.choices = []
    },
    addChoice() {
      this.choices.push('')
    }
  }
}
</script>

<style>
.choice {
  width: 90%;
}
</style>

Většina funkcí již byla probrána, takže je jen krátce zhodnotím. Pro začátek mám question vlastnost data, která je vázána na textový vstup přes v-model="question" poskytuje obousměrnou datovou vazbu mezi vlastností dat question a vstupní prvek uživatelského rozhraní.

Pod zadáním textu otázky jsou dvě tlačítka. Jedno z tlačítek je pro přidání volby a obsahuje posluchač události @click="addChoice" který vloží prázdný řetězec do choices pole. choices pole se používá k řízení zobrazení vybraných textových vstupů, z nichž každý je svázán s příslušným prvkem choices pole přes v-model="choices[idx]" . Každý textový vstup volby je spárován s tlačítkem, které uživateli umožňuje jej odstranit kvůli přítomnosti posluchače události kliknutí @click="removeChoice(choice)" .

Poslední částí uživatelského rozhraní v komponentě NewQuestion, o které je třeba diskutovat, je tlačítko pro uložení. Když uživatel přidá svou otázku a požadovaný počet možností, může kliknout na toto a otázku uložit. Toho lze dosáhnout prostřednictvím posluchače kliknutí @click="saveQuestion" .

Nicméně uvnitř saveQuestion metoda Zavedl jsem nové téma. Všimněte si, že používám jinou metodu připojenou k Vue komponenty instance. Toto je this.$emit(...) metoda emitoru událostí. Když to volám, vysílám do nadřazené komponenty, NewSurvey, událost nazvanou "questionComplete" a předávám spolu s ní objekt užitečného zatížení s question a choices .

Zpět v souboru NewSurvey.vue budu chtít importovat tuto komponentu NewQuestion a zaregistrovat ji do instance komponenty Vue takto:

<script>
import NewQuestion from '@/components/NewQuestion'

export default {
  components: { NewQuestion },
  data() {
    return {
      step: 'name',
      name: ''
    }
  }
}
</script>

Pak jej mohu zahrnout do šablony jako prvek komponenty takto:

<div class="questions" v-show="step === 'questions'">
  <new-question v-on:questionComplete="appendQuestion"/>
</div>

Všimněte si, že jsem použil v-on direktiva pro naslouchání události "questionComplete", která má být vygenerována z komponenty NewQuestion a registrována zpětné volání appendQuestion . Jedná se o stejný koncept, jaký jsme viděli u @click="someCallbackFunction" posluchač událostí, ale tentokrát jde o vlastní událost. Mimochodem, mohl jsem použít kratší @questionComplete="appendQuestion" syntaxe, ale myslel jsem, že vložím nějakou rozmanitost a je to také jasnější.

Další logickou věcí by bylo přidat appendQuestion metoda do komponenty NewSurvey spolu s questions datová vlastnost k udržování kolekce otázek a odpovědí generovaných v komponentě NewQuestion a odeslaných zpět do NewSurvey.

export default {
  components: { NewQuestion },
  data() {
    return {
      step: 'name',
      name: '',
      question: []
    }
  },
  methods: {
    appendQuestion(newQuestion) {
      this.questions.push(newQuestion)
    }
  }
}

Nyní mohu uložit a obnovit pomocí prohlížeče na URL localhost:8080/#/surveys poté klikněte na kartu Otázky, přidejte text otázky a několik možností, jak je uvedeno níže.

Poslední záložkou, kterou je třeba dokončit, je záložka Review. Tato stránka bude obsahovat seznam otázek a možností a nabídne uživateli možnost je odstranit. Pokud je uživatel spokojen, může odeslat průzkum a aplikace se přesměruje zpět na komponentu Home.

Šablonová část kódu pro uživatelské rozhraní recenze je následující:

<div class="review" v-show="step === 'review'">
  <ul>
    <li class="question" v-for="(question, qIdx) in questions" :key="`question-${qIdx}`">
      <div class="title">
        {{ question.question }}
        <span class="icon is-medium is-pulled-right delete-question"
          @click.stop="removeQuestion(question)">
          <i class="fa fa-times" aria-hidden="true"></i>
        </span>
      </div>
      <ul>
        <li v-for="(choice , cIdx) in question.choices" :key="`choice-${cIdx}`">
          {{ cIdx + 1 }}. {{ choice }}
        </li>
      </ul>
    </li>
  </ul>

  <div class="control">
    <a class="button is-large is-primary" @click="submitSurvey">Submit</a>
  </div>

</div>

Část skriptu nyní stačí aktualizovat přidáním removeQuestion a submitSurvey metody pro zpracování jejich příslušných posluchačů události kliknutí.

methods: {
  appendQuestion(newQuestion) {
    this.questions.push(newQuestion)
  },
  removeQuestion(question) {
    const idx = this.questions.findIndex(q => q.question === question.question)
    this.questions.splice(idx, 1)
  },
  submitSurvey() {
    this.$store.dispatch('submitNewSurvey', {
      name: this.name,
      questions: this.questions
    }).then(() => this.$router.push('/'))
  }
}

removeQuestion(question) metoda odstraní otázku z questions pole ve vlastnosti data, která reaktivně aktualizuje seznam otázek tvořících uživatelské rozhraní výše. submitSurvey metoda odešle brzy přidanou akční metodu submitNewSurvey a předá mu nový obsah průzkumu a poté použije this.$router.push(...) komponenty pro přesměrování aplikace na komponentu Home.

Nyní stačí vytvořit submitNewSurvey akční metoda a odpovídající falešná funkce AJAX pro falešné odesílání na server. V obchodě actions objekt přidám následující.

const actions = {
  // asynchronous operations
  loadSurveys(context) {
    return fetchSurveys()
      .then((response) => context.commit('setSurveys', { surveys: response }))
  },
  loadSurvey(context, { id }) {
    return fetchSurvey(id)
      .then((response) => context.commit('setSurvey', { survey: response }))
  },
  addSurveyResponse(context) {
    return saveSurveyResponse(context.state.currentSurvey)
  },
  submitNewSurvey(context, survey) {
    return postNewSurvey(survey)
  }
}

Nakonec do modulu api/index.js přidám postNewSurvey(survey) Funkce AJAX pro zesměšňování POST na server.

export function postNewSurvey(survey) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Saving survey ...', survey)
      resolve()
    }, 300)
  })
}

Uložím všechny soubory projektu a vyžádám si adresu URL localhost:8080/#/surveys . Po přidání jména, několika otázek s možnostmi a pozastavení na kartě recenze se zobrazí následující uživatelské rozhraní:

Zdroje

Chcete se dozvědět více o Vue.js a vytváření front-end webových aplikací? Zkuste se podívat na některé z následujících zdrojů, abyste se mohli hlouběji ponořit do tohoto rozhraní front-end:

  • Vue.js 2 – Kompletní průvodce
  • Vývojářský kurz Ultimate Vue JS 2

Závěr

Během tohoto příspěvku jsem se pokusil pokrýt to, co považuji za nejdůležitější aspekty poměrně velkého tématu, vuex. Vuex je velmi výkonný doplněk k projektu Vue.js, který vývojářům poskytuje intuitivní vzor, ​​který zlepšuje organizaci a robustnost středně velkých až velkých jednostránkových aplikací založených na datech.

Jako vždy děkuji za přečtení a nestyďte se níže komentovat nebo kritizovat.