[Aprendizado Vue3] Armazenamento de gerenciamento de estado Vuex

Vuex é um padrão + biblioteca de gerenciamento de estado desenvolvido especificamente para aplicativos Vue.js. Ele usa um armazenamento centralizado para gerenciar o estado de todos os componentes do aplicativo e usa regras correspondentes para garantir que o estado mude de maneira previsível.

Instalar

npm

npm install vuex@next --save

fio

npm install vuex@next --save

uso básico

1) Crie um arquivo src/store/index.js com o seguinte conteúdo:

import {
    
     createStore } from 'vuex'

// 创建一个新的 store 实例
export default createStore({
    
    
  state:  {
    
    
      count: 0
  },
  getters:{
    
    
    evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
  },
  mutations: {
    
    
    increment (state, n) {
    
    
      state.count+=n
    }
  },
  actions: {
    
    
    increment(context,n){
    
    
      context.commit('increment',n)
    }
  }
})

2) Introduzir store/index.js em main.js

import App from './App.vue'
// 引入store
import store from './store';

const app = createApp(App)
// 使用store //
app.use(store)

3) Uso em componentes

<template>
  
  <div>
    <h3>Count demo</h3>
    useStore:{
    
    {
    
     count }} 
    <button @click="increment()">increment</button>
    <div>evenOrOdd:{
    
    {
    
     evenOrOdd }}</div>
    <button @click="addByAction()">addByAction</button>
    <button @click="addPromise()">incrementPromise</button>
  </div>
  
</template>

<script setup>

import {
    
     computed } from 'vue'
import {
    
     useStore  } from 'vuex'
// 通过useStore得到store对象
const store = useStore()
// 通过计算属性得到state.count
const count = computed(()=> store.state.count)
function increment(){
    
    
  // 提交commit,调用Mutation中的increment方法
  store.commit('increment',1)
}
// 通过计算属性得到getters.evenOrOdd
const evenOrOdd = computed(()=>store.getters.evenOrOdd)
// 分发action,触发方法increment
function addByAction(){
    
    
  store.dispatch('increment',1)
}
</script>

4) Execute o projeto, a interface é a seguinte

insira a descrição da imagem aqui

ideia central

No coração de cada aplicativo Vuex está a loja. Uma "loja" é basicamente um contêiner que contém a maior parte do estado do seu aplicativo. O Vuex difere dos objetos globais simples de duas maneiras:

O armazenamento de estado do Vuex é reativo. Quando o componente Vue lê o estado da loja, se o estado na loja mudar, o componente correspondente será atualizado de forma eficiente de acordo.

Você não pode alterar diretamente o estado na loja. A única maneira de alterar o estado no armazenamento é confirmar explicitamente a mutação. Isso nos permite rastrear facilmente cada mudança de estado, permitindo-nos implementar algumas ferramentas para nos ajudar a entender melhor nossa aplicação.
imagem

1、Estado

árvore de estado único

O Vuex usa uma única árvore de estado - sim, um único objeto que contém todo o estado no nível do aplicativo. Neste ponto, existe como uma "Fonte Única da Verdade (SSOT)". Isso também significa que cada aplicativo conterá apenas uma instância de armazenamento.

função auxiliar mapState

Use a função auxiliar mapState para nos ajudar a gerar propriedades calculadas

// 在单独构建的版本中辅助函数为 Vuex.mapState
import {
    
     mapState } from 'vuex'

export default {
    
    
  // ...
  computed: mapState({
    
    
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
    
    
      return state.count + this.localCount
    }
  })
}

variedade

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

operador de propagação de objetos

computed: {
    
    
  localComputed () {
    
     /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    
    
    // ...
  })
}

<script setup>Observe que funções auxiliares, como mapState, não podem ser usadas em APIs combinadas

2、Obter

Vuex nos permite definir "getters" (que podem ser pensados ​​como propriedades computadas da loja) na loja.
O Getter aceita o estado como seu primeiro parâmetro:

const store = createStore({
    
    
  state: {
    
    
    todos: [
      {
    
     id: 1, text: '...', done: true },
      {
    
     id: 2, text: '...', done: false }
    ]
  },
  getters: {
    
    
    doneTodos (state) {
    
    
      return state.todos.filter(todo => todo.done)
    }
  }
})

acesso via atributos

Getters são expostos como objetos store.getters, e você pode acessar os valores como propriedades:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Os getters também podem aceitar outros getters como segundos argumentos:

getters: {
    
    
  // ...
  doneTodosCount (state, getters) {
    
    
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

Observe que os getters são armazenados em cache como parte do sistema de reatividade do Vue quando acessados ​​por meio de propriedades.

acesso via método

Você também pode passar parâmetros para um getter fazendo com que o getter retorne uma função. Útil quando você está fazendo consultas em arrays na loja.

getters: {
    
    
  // ...
  getTodoById: (state) => (id) => {
    
    
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

Observe que quando o getter for acessado por meio do método, ele será chamado todas as vezes e o resultado não será armazenado em cache.

função auxiliar mapGetters

A função auxiliar mapGetters simplesmente mapeia getters no armazenamento para propriedades computadas locais:

import {
    
     mapGetters } from 'vuex'

export default {
    
    
  // ...
  computed: {
    
    
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

Se você quiser nomear uma propriedade getter com outro nome, use o formulário de objeto:

import {
    
     mapGetters } from 'vuex'

export default {
    
    
  // ...
  computed: {
    
    
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

3、Mutação

A única maneira de alterar o estado na loja do Vuex é enviar uma mutação. As mutações no Vuex são muito semelhantes aos eventos: cada mutação tem um tipo de evento de string (tipo) e uma função de retorno de chamada (manipulador). Essa função de retorno de chamada é onde realmente fazemos a mudança de estado e aceita o estado como o primeiro argumento:

const store = createStore({
    
    
  state: {
    
    
    count: 1
  },
  mutations: {
    
    
    increment (state) {
    
    
      // 变更状态
      state.count++
    }
  }
})

cometer mutação

store.commit('increment')

Você pode passar parâmetros adicionais para store.commit, ou seja, a carga de mutação (payload)

// ...
mutations: {
    
    
  increment (state, n) {
    
    
    state.count += n
  }
}
store.commit('increment', 10)

Envio de estilo de objeto

Outra maneira de enviar uma mutação é usando diretamente o objeto que contém a propriedade type:

store.commit({
    
    
  type: 'increment',
  amount: 10
})

Ao usar o envio de estilo de objeto, o objeto inteiro é passado como carga útil para a função de mutação, portanto, o manipulador permanece inalterado:

mutations: {
    
    
  increment (state, payload) {
    
    
    state.count += payload.amount
  }
}

Misturar chamadas assíncronas em mutações pode dificultar a depuração do seu programa. Por exemplo, quando você chama duas mutações que contêm retornos de chamada assíncronos para alterar o estado, como você sabe quando chamar de volta e qual chamar de volta primeiro? É por isso que distinguimos entre esses dois conceitos. No Vuex, as mutações são todas as transações síncronas

função auxiliar mapMutations

import {
    
     mapMutations } from 'vuex'

export default {
    
    
  // ...
  methods: {
    
    
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
    
    
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

4、Ação

A ação é semelhante à mutação, a diferença é:

  • As ações enviam mutações em vez de alterar diretamente o estado.
  • A ação pode conter operações assíncronas arbitrárias.
const store = createStore({
    
    
  state: {
    
    
    count: 0
  },
  mutations: {
    
    
    increment (state) {
    
    
      state.count++
    }
  },
  actions: {
    
    
    increment (context) {
    
    
      context.commit('increment')
    }
  }
})

A função Action aceita um objeto de contexto com os mesmos métodos e propriedades da instância de armazenamento, portanto, você pode chamar context.commit para confirmar uma mutação ou obter estado e getters por meio de context.state e context.getters.

Na prática, costumamos usar a desestruturação de parâmetros do ES2015 para simplificar o código (principalmente quando precisamos chamar o commit várias vezes):

actions: {
    
    
  increment ({
     
      commit }) {
    
    
    commit('increment')
  }
}

Distribuir ações

A ação é acionada pelo método store.dispatch:

store.dispatch('increment')

À primeira vista parece supérfluo, não seria mais conveniente para nós distribuir a mutação diretamente? Na verdade não, lembra da restrição que as mutações têm para executar de forma síncrona? A ação é irrestrita! Podemos realizar operações assíncronas dentro de uma ação:

actions: {
    
    
  incrementAsync ({
     
      commit }) {
    
    
    setTimeout(() => {
    
    
      commit('increment')
    }, 1000)
  }
}

As ações suportam a mesma carga útil e distribuição de objetos:

// 以载荷形式分发
store.dispatch('incrementAsync', {
    
    
  amount: 10
})

// 以对象形式分发
store.dispatch({
    
    
  type: 'incrementAsync',
  amount: 10
})

Vejamos um exemplo de carrinho de compras mais realista, envolvendo a chamada de uma API assíncrona e o envio de várias mutações:

actions: {
    
    
  checkout ({
     
      commit, state }, products) {
    
    
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求
    // 然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

Observe que estamos fazendo uma série de operações assíncronas e registrando os efeitos colaterais (ou seja, mudanças de estado) das ações ao enviar mutações.

5, Módulo

Devido ao uso de uma única árvore de estado, todo o estado do aplicativo será concentrado em um objeto relativamente grande. Quando o aplicativo se torna muito complexo, o objeto de armazenamento pode ficar bastante inchado.

Para resolver os problemas acima, o Vuex nos permite dividir a loja em módulos. Cada módulo tem seu próprio estado, mutação, ação, getter e até mesmo submódulos aninhados—separados da mesma forma de cima para baixo:

const moduleA = {
    
    
  state: () => ({
    
     ... }),
  mutations: {
    
     ... },
  actions: {
    
     ... },
  getters: {
    
     ... }
}

const moduleB = {
    
    
  state: () => ({
    
     ... }),
  mutations: {
    
     ... },
  actions: {
    
     ... }
}

const store = createStore({
    
    
  modules: {
    
    
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

Namespaces

Por padrão, as ações e mutações dentro dos módulos ainda são registradas no namespace global - isso permite que vários módulos respondam à mesma ação ou mutação.

Se você deseja que seu módulo tenha um grau mais alto de encapsulamento e reutilização, você pode adicionar namespaced: true para torná-lo um módulo com um namespace. Quando um módulo é registrado, todos os seus getters, ações e mutações serão nomeados automaticamente de acordo com o caminho do registro do módulo. Por exemplo:

const store = createStore({
    
    
  modules: {
    
    
    account: {
    
    
      namespaced: true,
      // 模块内容(module assets)
      state: () => ({
    
     ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
    
    
        isAdmin () {
    
     ... } // -> getters['account/isAdmin']
      },
      actions: {
    
    
        login () {
    
     ... } // -> dispatch('account/login')
      },
      mutations: {
    
    
        login () {
    
     ... } // -> commit('account/login')
      },

      // 嵌套模块
      modules: {
    
    
        // 继承父模块的命名空间
        myPage: {
    
    
          state: () => ({
    
     ... }),
          getters: {
    
    
            profile () {
    
     ... } // -> getters['account/profile']
          }
        },

        // 进一步嵌套命名空间
        posts: {
    
    
          namespaced: true,
          state: () => ({
    
     ... }),
          getters: {
    
    
            popular () {
    
     ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

Funções vinculadas com namespaces

Ao usar funções como mapState, mapGetters, mapActions e mapMutations para vincular módulos com namespaces, pode ser complicado escrever:

computed: {
    
    
  ...mapState({
    
    
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  }),
  ...mapGetters([
    'some/nested/module/someGetter', // -> this['some/nested/module/someGetter']
    'some/nested/module/someOtherGetter', // -> this['some/nested/module/someOtherGetter']
  ])
},
methods: {
    
    
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

Para este caso, você pode passar a string de nome do namespace do módulo como o primeiro argumento para a função acima, e todas as ligações terão automaticamente esse módulo como o contexto. Portanto, o exemplo acima pode ser simplificado para:

computed: {
    
    
  ...mapState('some/nested/module', {
    
    
    a: state => state.a,
    b: state => state.b
  }),
  ...mapGetters('some/nested/module', [
    'someGetter', // -> this.someGetter
    'someOtherGetter', // -> this.someOtherGetter
  ])
},
methods: {
    
    
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

Além disso, você pode criar auxiliares com base em um determinado namespace usando createNamespacedHelpers. Ele retorna um objeto com novas funções auxiliares de ligação de componente vinculadas ao valor de namespace fornecido:

import {
    
     createNamespacedHelpers } from 'vuex'

const {
    
     mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
    
    
  computed: {
    
    
    // 在 `some/nested/module` 中查找
    ...mapState({
    
    
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    
    
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

Registro dinâmico do módulo

Após a criação da loja, você pode registrar os módulos com o método store.registerModule:

import {
    
     createStore } from 'vuex'

const store = createStore({
    
     /* 选项 */ })

// 注册模块 `myModule`
store.registerModule('myModule', {
    
    
  // ...
})

// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
    
    
  // ...
})

Você também pode usar store.unregisterModule(moduleName) para descarregar módulos dinamicamente. Observe que você não pode descarregar módulos estáticos (aqueles declarados quando a loja foi criada) usando este método.

Observe que você pode verificar se o módulo foi registrado na loja com o método store.hasModule(moduleName). A única coisa a lembrar é que os módulos aninhados devem ser passados ​​para registerModule e hasModule como arrays, não como strings de caminho para o módulo.

referência

  • https://vuex.vuejs.org/zh/

Acho que você gosta

Origin blog.csdn.net/wlddhj/article/details/131257203
Recomendado
Clasificación