[Vuex] Заметки об исследовании Vuex

Vuex
[Vuex] Заметки об исследовании Vuex

1. Предисловие

Эта статья больше похожа на заметку для меня, чтобы изучить vuex.Ресурсы для обучения в основном взяты из официальных руководств по документации.Официальный учебникОн был подробно описан, и есть некоторые места, которые я не понимаю, поэтому я также проверил другие материалы, чтобы помочь своему пониманию.Это руководство добавило некоторые дополнительные материалы к официальному руководству, надеясь принести вам некоторые Спасибо, и спасибо другим крупным ребятам в Интернете, которые делятся знаниями, позвольте мне сделать несколько обходных путей! Если в статье что-то не так, пожалуйста, покритикуйте и поправьте меня!

Во-вторых, первоначальный опыт Vuex

1. Зачем использовать Vuex

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

2. Подготовка перед учебой

На этот раз все мое обучение проходит по проекту строительных лесов, предоставленному официальным лицом, на этот раз я не буду вдаваться в подробности об использовании строительных лесов, вы можете перейти наVue CLI, когда вы используете проект, сгенерированный Vue CLI, вам будет предложено выбрать магазин. После выбора на странице для вас будет сгенерирован store.js. Это исходный магазин. Структура выглядит следующим образом:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  }
})

3. Состояние

Ядром Vuex является хранилище хранилища. Этот экземпляр хранилища будет внедрен во все подкомпоненты. Свойство состояния внутри сохраняет наше состояние. Например, мы определяем количество состояний:

export default new Vuex.Store({
  state: {
    count: 10
  },
})

Таким образом, у нас есть централизованно управляемый счетчик состояний. Как другие компоненты получают этот счетчик? Вы можете рассчитать свойства, чтобы получить:

export default {
  data() {
    
  },
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
}

Поскольку параметр хранилища зарегистрирован в корневом экземпляре, экземпляр хранилища будет внедрен во все дочерние компоненты корневого компонента, и доступ к дочерним компонентам можно будет получить через this.$store. С помощью свойства вычисляемого мы можем использовать синтаксис шаблона для вызова счетчика в шаблоне следующим образом:

<template>
  <div>
    <p>{{ count }}</p>
  </div>
</template>

mapState

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

import { mapState } from 'vuex';

Примечание: после того, как здесь используется метод mapState, написание вычисляемого немного отличается.Например, по умолчанию ваше вычисляемое свойство записывается следующим образом:

data(){
  return{
    msg: 'hello '
  }
}
computed: {
  msg() {
    return this.msg + 'world!';
  }
}

Затем, после того, как вы используете mapState, вам нужно написать вычисленный таким образом и поместить msg() в mapState, иначе будет сообщено об ошибке.

data(){
  return{
    msg: 'hello ',
    localCount: 20
  }
}
computed: mapState({
  msg() {  // 最初的
    return this.msg + 'world!';
  },
  // 使用mapState从store中引入state
  count(state) {
    return state.count;
  },
  name(state) {
    return state.firstName + ' ' + state.lastName;
  },
  mixCount(state) { // 结合store和组件状态进行计算
    return state.count + this.localCount;
  },
})

Если вы используете оператор распространения..., то вычисляемое свойство не нужно изменять, оно записывается как обычно

computed: { // 使用展开的话可以按这种方式写,否则要使用另外一种方式,不然要报错
  msg() {
    return this.$store.state.msg;
  },
  // 这里返回一个状态count,
  // 返回多个你可以这样写...mapState(['count', 'firstName', 'lastName'])
  ...mapState(['count'])
},

4. Добытчик

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

export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8]
  },
});

Мы хотим отфильтровать четные числа в массиве и затем использовать их в компоненте, тогда фильтр можно сделать в геттере.

export default new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8]
  },
  getters: { //  这个主要是对状态的处理,相当于把状态处理的方法抽成公共部分来管理了
    modifyArr(state) { // 一般化getter
      return state.list.filter((item, index, arr) => {
        return item % 2 == 0;
      })
    },
    getLength(state, getter) { // 方法里面传getter,调用modifyArr来计算长度
      return getter.modifyArr.length;
    }
});

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

computed: {
    list() {
      return this.$store.getters.modifyArr;
    },
}

mapGetters

Вспомогательная функция mapGetters просто сопоставляет геттеры в хранилище с локальными вычисляемыми свойствами.Когда мы хотим ввести несколько геттеров в компонент, мы можем использовать mapGetter:

import {mapGetters} from 'vuex';

Например, как и modifyArr, getLength определено выше. Мы хотим импортировать эти два и получить их значения:

computed: {
  ...mapGetter(['modifyArr', 'getLength'])
}

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

computed: {
  mapGetter({
    arr: 'modifyArr',   // 把 `this.arr` 映射为 `this.$store.getters.modifyArr`,下面同理
    length: 'getLength'
  })
}

Если ваше вычисляемое свойство содержит другие методы вычисления, вы можете использовать только оператор распространения, который немного отличается от mapState.Если другие вычисляемые свойства записаны в mapGetter, будет сообщено об ошибке, говоря, что геттера нет, поэтому используйте следующее способ написания:

computed: {
  msg() {
    return this.num * 10;
  },
  ...mapGetters([
    'modifyArr',
    'getLength'
  ])
}

или укажите псевдоним

computed: { 
  msg() {
    return this.num * 10;
  },
  ...mapGetters({
    getList: 'modifyArr',
    length: 'getLength'
  })
}

Затем вызовите его в шаблоне:

<template>
  <div>
    <h2>mapGetters的使用演示</h2>
    <p>你的数字:{{ msg }}</p>
    <p>你的数组长度为:{{ length }}</p>
    <ul>
      <li v-for="(item, index) in getList" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

5. Мутация

Когда нам нужно изменить состояние в хранилище, мы не изменяем их непосредственно в компоненте, а изменяем их через методы в мутации, что способствует отслеживанию изменений состояния. Например, в состоянии есть переменная count, и мы нажимаем кнопки «плюс» и «минус», чтобы управлять ее значением:

mutations: {
  add(state) {
    state.count++;
  },
  reduce(state) {
    state.count--;
  }
},

В других компонентах мы инициируем изменения, определяя методы и время привязки.Здесь нам нужно использовать коммит:

methods: {
  add() {
    this.$store.commit('add');
  },
  reduce() {
    this.$store.commit('reduce');
  }
}

Отправить полезную нагрузку

Это для подачи дополнительных параметров при коммите, например, я передаю в счет дополнительное значение:

mutations: {
  loadAdd(state, payload) {  // 提交载荷,额外参数
    state.count += payload;
  }
},

Затем используйте его в компоненте:

methods: {
  loadAdd() {
    this.$store.commit('loadAdd', 100); // 传递额外参数
  }
}

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

this.$store.commit('loadAdd', {
  extraCount: 100
}); // 传递额外参数

Мы также можем записать все параметры в объект при вызове фиксации:

this.$store.commit( {
  type: 'addLoad'
  extraCount: 100
}); // 传递额外参数

Мутация должна следовать правилам ответа Vue.

В основном это означает, что когда вам нужно добавить дополнительные данные в состояние в процессе разработки, вам нужно следовать рекомендациям по ответу. Здесь я напрямую копирую инструкции из официальной документации: Мутации в Vuex также требуют тех же предостережений, что и в Vue:

  • Лучше всего заранее инициализировать все необходимые свойства в вашем магазине.

  • Когда вам нужно добавить новое свойство к объекту, вы должны использовать Vue.set(obj, 'newProp', 123) или заменить старый объект новым. Например, воспользуйтесь оператором распространения объектов этапа 3.

Мы можем написать это так:

state.obj = { ...state.obj, newProp: 123 }

Или подарить каштан: Я объявляю метод внутри мутации

addNewState(state, payload) { // 我打算再这儿添加新的属性到state
  // Vue.set(state, 'newProp', '添加一个新值!'); // 这是一种写法
  // 这种写法用新对象替换老对象
  // state= {...state, newProp: '添加一个新值!'} // 这个玩意儿不管用了,用下面的replaceState()方法
  this.replaceState({...state, newProp: '添加一个新值!'})
}

Затем вызовите его в компоненте и определите метод:

addNewProp() {
  this.$store.commit('addNewState', {});
}

После того, как этот метод будет выполнен таким образом, он будет обновлен до состояния во времени, а затем определен в вычисляемом свойстве компонента:

newMsg() {
  return this.$store.state.newProp || '还没添加新值';
}

Сразу отображается в шаблоне и не повлияет на другие состояния:

<p>添加的新值:{{ newMsg }}</p>
<div><button @click="addNewProp">添加新值</button></div>

Мутация должна быть синхронной функцией

Следует избегать следующего метода написания (прямой официальный пример благословения):

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

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')`
    })
  }
}

6. Действие

Действие похоже на мутацию, разница в следующем:

  • Действия вызывают мутации, а не изменения состояния напрямую.
  • Действие может содержать произвольные асинхронные операции. Как упоминалось ранее, мутация может содержать только синхронные транзакции, поэтому при работе с асинхронными транзакциями требуется действие, асинхронный процесс контролируется действием, а затем вызываются методы мутации для изменения состояния. Здесь я вставляю код напрямую, чтобы было понятно с первого взгляда, сначала я определяю продукт состояния:
state: {
  product: 'car'
}

Затем определите метод в мутации:

changeProduct(state, payload) {
  state.product = payload.change;
}

Определено в действии:

actions: {
  changeProduct(context, payload) { // 这个context是一个与 store 实例具有相同方法和属性的对象
    // 调用mutation里的changeProduct方法
    // context.commit('changeProduct', {change: 'ship'});
    // 改成异步方式
    // setTimeout(() => {
    //   context.commit('changeProduct', {change: 'ship'});
    // }, 1500)
    // 使用载荷
    let temp = 'ship+' + payload.extraInfo; 
    setTimeout(() => {
      context.commit('changeProduct', {change: temp});
    }, 1500)
  }
}

Определите отправку триггера события в методах компонента:

methods: {
  selectProduct() {
    // this.$store.dispatch('changeProduct')
    // 载荷方式分发
    // this.$store.dispatch('changeProduct', {
    //   extraInfo: 'sportcar'
    // })
    // 或者这种
    this.$store.dispatch({
      type: 'changeProduct',
      extraInfo: '->sportcar'
    })
  }
},

Такое простое действие завершено!

mapActions

Я не буду здесь вдаваться в подробности, по названию видно, что оно похоже на предыдущие вспомогательные функции, называемые map, которые используются для сопоставления методов в экшене, официальный код также напрямую размещен здесь:

import {mapActions} from 'vuex';
export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

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

Иногда мы хотим узнать статус асинхронного выполнения в экшене, а затем модифицировать другую информацию — это можно сделать с помощью Promise. Здесь мы объявляем состояние в состоянии:

state: {
  userInfo: { // 这个变量用来测试组合变量
    name: 'lee',
    age: 23
  }
}

Затем объявите мутацию:

mutations: {
    // 以下用来测试组合action
    changeInfo(state, payload) {
      state.userInfo.name = 'lee haha';
    }
}

Объявить действие:

actions: {
  changeInfo(context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        context.commit('changeInfo');
        resolve();
      }, 2000)
    })
  }
}

На этом этапе мы определяем метод в компоненте для отправки этого действия:

data() {
  return {
    status: '信息还没修改!'
  }
}
methods: {
  modifyInfo() {
    this.$store.dispatch('changeInfo').then(() => {
      this.status = '信息修改成功';
    });
  }
}

Отображение шаблона:

<template>
  <div>
    <h2>组合action</h2>
    <p>{{ status }}</p>
    <p>{{ info.name }}</p>
    <div><button @click="modifyInfo">修改信息</button></div>
  </div>
</template>

Когда мы нажимаем, чтобы изменить информацию, будет отправлено действие, и когда модификация будет успешной, отображаемая информация, упомянутая выше, будет изменена синхронно. Конечно, другие определенные методы действий также могут использоваться друг с другом.Официальный код размещен здесь:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

7. Модуль

Эта часть модуля соответствует его названию, когда все состояния будут сосредоточены в одном объекте, он станет довольно раздутым, в это время требуется метод управления модулем. Здесь я все еще использую код для иллюстрации, например, я определяю два модуля в магазине:

// 定义的模块A
const moduleA = {
  state: {
    name: 'lee',
    age: 23,
  },
  mutations: {

  },
  getters: {

  },
  actions: {

  }
};

// 定义模块B
const moduleB = {
  state: {
    name: 'wang',
    age: 22
  },
  mutations: {

  },
  getters: {

  },
  actions: {

  }
}

Затем объявите модуль в Vuex:

export default new Vuex.Store({
  modules: {
    ma: moduleA,
    mb: moduleB
  },
  state: {
    ........... // 其他状态
  }
});

Таким образом, если мы хотим получить доступ к состоянию других модулей в компоненте, мы можем сделать это Например, здесь я хочу вызвать состояние в модуле B:

computed: {
  msg() {
    return this.$store.mb; // 这里返回的是:{name: 'wang', age: 22}
  }
}

Что касается локального состояния внутри модуля, то оно мало чем отличается от обычного использования хранилища.Основное отличие заключается в следующих внешних состояниях.Например, для действия внутри модуля локальное состояние отображается через context.state, а состояние корневого узла — это context.rootState, перехватите здесь официальный код:

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

Для геттеров внутри модулей состояние корневого узла отображается как третий параметр:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

Таким образом, для методов в геттерах, мутациях и действиях мы можем называть их как базовое хранилище. Ограничений по области нет. Давайте опубликуем каштан кода. Ниже представлен модуль B, который я определил в store.js:

const moduleB = {
  state: {
    name: 'wang',
    age: 22,
    desc: 'nope'
  },
  mutations: {
    modifyDesc(state, payload) {
      state.desc = payload.newMsg;
    }
  },
  getters: {

  },
  actions: {

  }
}

Внутри компонента я определяю следующее:

<template>
  <div>
    <h2>7、module使用示例</h2>
    <div>
      <p>名字:{{ name }}</p>
      <p>描述:{{ desc }}</p>
      <button @click="handleClick">修改描述</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: this.$store.state.mb.name,
      // desc: this.$store.state.mb.desc 注意这个如果涉及到要在store里面会被改变的状态,一定要写在
      // computed属性里面,不然不能及时反馈到视图上
    }
  },
  computed: {
    desc() {
      return this.$store.state.mb.desc;
    }
  },
  methods: {
    handleClick() {
      this.$store.commit('modifyDesc', {newMsg: 'lao wang is beautiful!'});
    }
  },
}
</script>

Таким образом, вы можете вызывать методы в мутации, одинаковые для геттеров и действий.

09.09.2019 Дополнение к остальным

модуль пространства имен

По умолчанию мутации, действия и геттеры регистрируются глобально, и вы можете вызывать их напрямую.Если вы хотите, чтобы ваш модуль имел более высокую инкапсуляцию и возможность повторного использования, вы можете сделать это, добавив namespaced: true Станьте модулем с пространством имен. Когда модуль регистрируется, все его геттеры, действия и мутации автоматически именуются в соответствии с путем, зарегистрированным модулем. Сначала я создаю новый файл js для объявления модуля C:

/* 
* 这个文件用来声明模块C
*/

export const moduleC = {
  namespaced: true,
  state: {
    name: 'moduleC',
    desc: '这是模块C,用来测试命名空间的!',
    list: [1, 2, 3, 4]
  },
  getters: {
    filterList(state) {
      return state.list.filter((item, index, arrSelf) => {
        return item % 2 !== 0;
      });
    }
  },
  mutations: {
    modifyName(state, payload) {
      state.name = payload.newName;
    }
  },
  actions: {
    
  }
}

Затем импортируйте в store.js:

import { moduleC } from './module_c.js';

export default new Vuex.Store({
  modules: {
    mc: moduleC
  },
});

Чтобы сделать этот модуль модулем с пространством имен, объявите в нем свойстваnamespaced: trueВсе, тогда вызовы методов в мутациях, геттерах и действиях должны пройти еще один путь, например, я вызываю методы в мутациях в компоненте (то же самое верно для геттеров и действий):

methods: {
  modify() {
    // this.$store.commit('mc/modifyName', {
    //   newName: '命名空间模块C'
    // })
    this.$store.commit({
      type: 'mc/modifyName',
      newName: '命名空间模块C'
    })
  }
}

Конечно, также можно вкладывать модули внутрь модуля, должен ли путь пройти еще один слой, зависит от вас.namespaced: trueЗаявления нет, вот официальный код:

const store = new Vuex.Store({
  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']
          }
        }
      }
    }
  }
})

Доступ к глобальному содержимому в модуле с пространством имен

Если вы хотите получить доступ к глобальному содержимому в геттерах, мутациях и действиях внутри модуля, Vuex уже инкапсулирован здесь, вам нужно только передать еще несколько параметров. Волна официальных демок, простых и понятных:

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

Используйте вспомогательные функции mapState, mapGetters, mapMutations и mapActions в модулях

Из-за наличия пространств имен будут проблемы с описанным выше методом записи в компоненте.Если вы хотите использовать вспомогательную функцию для сопоставления вещей в модуле, вам нужно указать имя пространства, чтобы сообщить вспомогательной функции, куда найди это. Здесь я беру в качестве примера свой модуль C выше.Во-первых, функция mapSatate может быть воспроизведена так.Я объявляю mc в глобальных модулях, тогда мое имя пространства - mc:

computed: {
  ...mapState('mc', ['name', 'desc']) // 这里模块里面要使用辅助函数的话要多传一个参数才行
}

Затем напишите имя и описание в шаблоне, или вы можете сделать это:

computed: {
  ...mapState('mc', {
    name(state) {
      return state.name;
    },
    desc(state) {
      return state.desc;
    }
  })
},

Подобно действиям, мутациям и геттерам, главное указать имя пространства, например, для объявленных мутаций:

methods: {
  ...mapMutations('mc', ['modifyName'])
}

Если вы действительно не хотите писать имя пространства в каждой вспомогательной функции, Vuex также предоставляет другие методы, используйтеcreateNamespacedHelpersСоздайте вспомогательную функцию на основе пространства имен, которая возвращает объект с новыми вспомогательными функциями привязки компонентов, связанными с заданным значением пространства имен:

import { createNamespacedHelpers } from 'vuex';

const { mapState, mapMutations } = createNamespacedHelpers('mc');

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

8. Заключение

Эта статья эквивалентна основной вводной статье, но если вас интересует другой контент, вы можете просмотреть его на официальном сайте (#смешное спасение жизни). Я загрузил соответствующий код на github, если вам интересно, скачайте его и посмотрите!демо-код