Русский
Русский
English
Статистика
Реклама

Из песочницы vuex typescript vuexok. Велосипед, который поехал и обогнал всех

Доброго времени суток.

Как и многие разработчики, я в свободное от работы время пишу свойотносительнонебольшой проект. Раньше писал на react, а на работе используется vue. Ну и что бы прокачаться во vue начал пилить свой проект на нем. Сначала всё было хорошо, прямо-таки радужно, пока я не решил, что надо бы еще прокачаться и в typescript. Так в моем проекте появился typescript. И если с компонентами всё былонеплохо, то с vuex всё оказалось печально. Так мне пришлось пройти все 5 стадий принятия проблемы, ну почти все.

Отрицание


Основные требования для стора:

  1. В модулях должны работать типы typescript
  2. Модули должно быть легко использовать в компонентах, должны работать типы для стейта, экшенов, мутаций и геттеров
  3. Не придумывать новое api для vuex, надо сделать так,чтобы как-то типы typescript заработали с модулями vuex, чтобы не приходилось разом переписывать всё приложение
  4. Вызов мутаций и экшенов должен быть максимально простым и понятным
  5. Пакет должен быть как можно меньше
  6. Не хочу хранить константы с именами мутаций и экшенов
  7. Оно должно работать (А как же без этого)

Не может быть что у такого уже зрелого проекта как vuex не было нормальной поддержки typescript. Ну-с, открываемGoogleYandex и погнали. Я был уверен на 100500% что с typescript всё должно быть отлично (как же я ошибался). Есть куча разных попыток подружить vuex и typescript. Приведу несколько примеров, которые запомнились, без кода чтобы не раздувать статью. Всё есть в документации по ссылкам ниже.

vuex-smart-module

github.com/ktsn/vuex-smart-module
Добротно, даже очень. Всё при себе, но лично мне не понравилось то, что для экшенов, мутаций, стейта, геттеров надо создавать отдельные классы. Это, конечно, вкусовщина, но это я и мой проект) И в целом вопрос типизации решен не до конца (ветка комментариев с объяснением почему).

Vuex Typescript Support

Хорошая попытка, но что-то много переписывать, да и вообще не принялось сообществом.

vuex-module-decorators

Казалось, что это идеальный способ подружить vuex и typescript. Похоже наvue-property-decorator, который я использую в разработке, работать с модулем можно как с классом, в общем супер, но

Но наследования нет. Классы модулей не корректно наследуются и issue на эту проблему висят уже очень давно! А без наследования будет очень много дублирования кода. Блин

Гнев


Дальше было совсем уже не очень, ну или так же идеального решения нет. Это тот самый момент, когда говоришь себе: Ну зачем я начал писать проект на vue? Ну ты же знаешь react, ну писал бы на react, там бы таких проблем не было! На основной работе проект на vue и тебе надо в нем прокачаться зашибись аргумент. А оно стоит потраченных нервов и бессонных ночей? Сиди как все, пиши компонентики, нет, тебе больше всех надо! Бросай этот vue! Пиши на react, прокачивайся в нем, за него и платят больше!

В тот момент я был готов хейтить vue как никто другой, но это были эмоции, и интеллект всё же был выше этого. Vue имеет (на мой субъективный взгляд) много преимуществ над react, но совершенства не бывает, как и победителей на поле сражений. И vue, и react по-своему хороши, а так как уже значительная часть проекта написана на vue, то было бы максимально глупо сейчас переходить на react. Надо было решить, что же делать с vuex.

Торг


Ну что же, дела обстоят не очень хорошо. Может тогда vuex-smart-module? Этот пакет вроде хорош, да, надо создавать много классов, но работает отлично же. Или может попробовать прописывать типы для мутаций и экшенов руками в компонентах и использовать чистый vuex? Там и vue3 c vuex4 на подходе, может у них дела с typescript обстоят лучше. Так что давай попробуем чистый vuex. И вообще на работу проекта это не влияет, всё же работает, типов нет, но вы держитесь. И держимся же)

Сначала так и начал делать, но код получается монструозный

Депрессия


Надо было двигаться дальше. Но куда неизвестно. Это был совсем отчаянный шаг. Решил сделатьконтейнер состояния с нуля. Код был набросан за пару часов. И получилось даже хорошо. Типы работают, состояние реактивно и даже наследование есть. Но вскоре агония отчаяния стала отступать, а здравый смысл возвращаться. В общем, эта идея отправилась на свалку. По большому счету это был паттерн глобальной шины событий. А он хорош только для не больших приложений. И вообще писать свой vuex всё же совсем перебор (по крайней мерев моей ситуации). Тут я уже догадывался, что совсем загнался. Но отступать было уже поздно.

Но если кому интересно, то код тут: (Наверное зря добавил этот фрагмент, но путь будет)

Слабонервным не смотреть
const getModule = <T>(name:string, module:T) => {  const $$state = {}  const computed: Record<string, () => any> = {}  Object.keys(module).forEach(key => {    const descriptor = Object.getOwnPropertyDescriptor(      module,      key,    );    if (!descriptor) {      return    }    if (descriptor.get) {      const get = descriptor.get      computed[key] = () => {        return get.call(module)      }    } else if (typeof descriptor.value === 'function') {      // @ts-ignore      module[key] = module[key].bind(module)    } else {      // @ts-ignore      $$state[key] = module[key]    }  })  const _vm = new Vue({    data: {      $$state,    },    computed  })  Object.keys(computed).forEach((computedName) => {    var propDescription = Object.getOwnPropertyDescriptor(_vm, computedName);    if (!propDescription) {      throw new Error()    }    propDescription.enumerable = true    Object.defineProperty(module, computedName, {      get() { return _vm[computedName as keyof typeof _vm]},      // @ts-ignore      set(val) { _vm[computedName] = val}    })  })  Object.keys($$state).forEach(name => {    var propDescription = Object.getOwnPropertyDescriptor($$state,name);    if (!propDescription) {      throw new Error()    }    Object.defineProperty(module, name, propDescription)  })  return module}function createModule<  S extends {[key:string]: any},  M,  P extends Chain<M, S>>(state:S, name:string, payload:P) {  Object.getOwnPropertyNames(payload).forEach(function(prop) {    const descriptor = Object.getOwnPropertyDescriptor(payload, prop)    if (!descriptor) {      throw new Error()    }    Object.defineProperty(      state,      prop,      descriptor,    );  });  const module = state as S & P  return {    module,    getModule() {      return getModule(name, module)    },    extends<E>(payload:Chain<E, typeof module>) {      return createModule(module, name, payload)    }  }}export default function SimpleStore<T>(name:string, payload:T) {  return createModule({}, name, payload)}type NonUndefined<A> = A extends undefined ? never : A;type Chain<T extends {[key:string]: any}, THIS extends {[key:string]: any}> = {  [K in keyof T]: (    NonUndefined<T[K]> extends Function       ? (this:THIS & T, ...p:Parameters<T[K]>) => ReturnType<T[K]>      : T[K]  )}


ПринятиеРождение велосипеда который обогнал всех. vuexok


Для нетерпеливых код тут, краткая документация тут.

В конце концов, написал крохотную библиотечку, которая закрывает все хотелки и даже чуть-чуть больше чем от нее требовалось. Но обо всём по порядку.

Простейший модуль с vuexok выглядит так:

import { createModule } from 'vuexok'import store from '@/store'export const counterModule = createModule(store, 'counterModule', {  state: {    count: 0,  },  actions: {    async increment() {      counterModule.mutations.plus(1)    },  },  mutations: {    plus(state, payload:number) {      state.count += payload    },    setNumber(state, payload:number) {      state.count = payload    },  },  getters: {    x2(state) {      return state.count * 2    },  },})

Ну вроде почти как vuex, хотя что там на 10й строке?

counterModule.mutations.plus(1)

Воу! А это легально? Ну с vuexok да, легально) Метод createModule возвращает объект, который в точности повторяет структуру объекта модуля vuex, только без свойства namespaced, и мы можем использовать его для вызова мутаций и экшенов или для получения стейта и геттеров, причем все типы сохраняются. Причем из любого места, где его можно импортировать.

А что там с компонентами?

А с ними все отлично, так как фактически это vuex, то в принципе ничего не поменялось, commit, dispatch, mapState и т.д. работают как и раньше.

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

import Vue from 'vue'import { counterModule } from '@/store/modules/counterModule'import Component from 'vue-class-component'@Component({  template: '<div>{{ count }}</div>'})export default class MyComponent extends Vue {  private get count() {    return counterModule.state.count // type number  }}

Свойство state в модуле реактивно, как и в store.state, так что чтобы использовать состояние модуля в компонентах Vue достаточно просто вернуть часть состояния модуля в вычисляемом свойстве. Есть только одна оговорка. Я намеренно сделал стейт Readonly типом, не хорошо так стейт vuex изменять.

Вызов экшенов и мутаций прост до безобразия и тоже сохраняются типы входных параметров

 private async doSomething() {   counterModule.mutations.setNumber(10)   // Аналогично вызову this.$store.commit('counterModule/setNumber', 10)   await counterModule.actions.increment()   // Аналогично вызову await this.$store.dispatch('counterModule/increment') }

Вот такая красота получилась. Чуть позже понадобилось еще реагировать на изменение jwt, который тоже хранится в сторе. И тогда появился у модулей метод watch. Вотчеры модулей работают также как store.watch. Единственная разница заключается в том, что в качестве параметров функции-гетера передаются стейт и гетеры модуля

const unwatch = jwtModule.watch(  (state) => state.jwt,  (jwt) => console.log(`New token: ${jwt}`),  { immediate: true },)

Итак, что мы имеем:

  1. типизированный стор есть
  2. типы работают в компонентах есть
  3. апи как у vuex и всё что было до этого на чистом vuex не ломается есть
  4. декларативная работа со стором есть
  5. маленький размер пакета (~400 байт gzip) есть
  6. не иметь необходимости хранить в константах названия экшенов и мутаций есть
  7. оно должно работать есть

Вообще странно что такой прекрасной фичи нет во vuex из коробки, это же офигеть как удобно!
Что касается поддержки vuex4 и vue3 не проверял, но судя по докам должно быть совместимо.

Так же решены проблемы представленные в этих статьях:

Vuex решаем старый спор новыми методами
Vuex нарушает инкапсуляцию

Влажные мечты:


Было бы здорово сделать так что бы в контексте экшенов были доступны мутации и другие экшены.

Как это сделать в контексте типов typescript хер его знает. Но если бы можно было делать так:

{  actions: {    one(injectee) {       injectee.actions.two()    },    two() {      console.log('tada!')    }}

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

Вот такое приключение с vuex и typescript. Ну, вроде выговорился. Спасибо за внимание.
Источник: habr.com
К списку статей
Опубликовано: 07.11.2020 22:21:21
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Vuejs

Typescript

Frontend

Front-end

Front-end разработка

Frontend-разработка

Vue

Vue2

Vuex

Vuex-typescript

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru