Как я сюда попал

Я впервые работал над проектом SPA в 2012 году. Это был относительно крупномасштабный проект, основанный на самой популярной библиотеке JavaScript года - Backbone. В то время интерфейсная среда разработки была не такой, как сегодня, и мы только начали слышать слухи о концепции SPA. Весь фронтенд-лагерь был заполнен фреймворками, порабощенными паттерном MVC. Пока люди спорили, соответствует ли AngularJS шаблону MVC или нет, на игровое поле вошли подвиды React. React, фреймворк, на который влияют реактивное программирование и функциональная парадигма, быстро рос. Пока я негодовал по поводу того факта, что гениальные умы, придумавшие это, были моложе меня, новый фреймворк под названием Vue также бросил свое имя в шляпу. На Vue повлияло множество различных фреймворков, и, хотя структура кода напоминала React, она процветала, когда дело доходило до состояний с помощью реактивного кода. В конце концов, Vue заработал репутацию, которая конкурирует с React по простоте использования. Хотя веб-разработчики, использующие React и Redux, часто сталкиваются с проблемами в реальных проблемах, даже если они полностью понимают неизменную основу React, количество веб-разработчиков, использующих Vue, растет с каждым днем.

Область фронтенд-разработки находится на грани выхода за рамки ограничений, налагаемых лингвистическими ограничениями JavaScript. CoffeeScript, Flow, Babel и TypeScript - такие примеры. Допустим, что Babel можно классифицировать иначе, потому что он только увеличивает совместимость существующих спецификаций, CoffeeScript, язык, который возглавлял революционное движение в прежние времена, столкнулся с золотой отставкой после того, как оставил заметное наследство ES6. Flow и TypeScript, надмножества JavaScript (ECMAScript), попытались применить более строгий указ о типе к исходному синтаксису и, пытаясь сделать это, представили фазу проверки во время компиляции для языка интерпретатора, который имел только время выполнения. TypeScript в конечном итоге вышел победителем из тайной войны между TypeScript и Flow, а TypeScript монополизировал транспайл-язык в JavaScript.

Лично у меня нет положительного взгляда на более строгие типы, но все же должен признать, что для протоколов для разнообразия и подписи модулей, указанные в стандартах интерфейса, весьма полезны. Хотя он разделяет фандом JavaScript на две части, и все улучшения делаются каждый день, JavaScript с каждым днем ​​становится все более стандартизированным языком. Однако, если вы начнете критиковать язык за отсутствие типов, я не думаю, что смогу вернуть JavaScript и объяснить вам причину, по которой JavaScript - лучший язык программирования! Единственное, что я могу предложить, это то, что язык JavaScript - это «ага». Я не могу с уверенностью сказать, что TypeScript будет «в», или если другой CoffeeScript займет его место, но я считаю, что если вы хотите изучить фронтенд-разработку, вам следует проявить интерес и обучить себя с помощью немного TypeScript. Это именно то, что я сделал, когда он был впервые выпущен, но до недавнего времени я был доволен тем, что просто знал об этом, и не имел никакого намерения использовать его на своем рабочем месте.

Прошли годы после того, как я впервые изучил TypeScript, и, когда TypeScript собирался уйти в прошлое, мой мозг жестоко обманул меня и снова сделал его интересным. Более того, TypeScript использовался в офисе не разработчиками интерфейса, а разработчиками в других областях; TypeScript расширил сферу применения. Разработчики, привыкшие иметь дело со строгими типами, использовали TypeScript в качестве буфера для начала использования JavaScript. Поскольку использование типов - это не только вопрос использования типов или нет, но и влияет на дизайн всего приложения, это было понятно. Даже без запроса компании мы поняли, что нам нужен виртуоз TypeScript. Поэтому мы решили использовать TypeScript для официального выпуска проекта, над которым в данный момент работали. Для этого конкретного проекта я начал использовать Vue по той же причине.

Vue и TypeScript приветливы до такой степени, что Vue переписал всю кодовую базу 3.0 на TypeScript. Даже когда я работал с версией Vue 2.x, было предсказуемо, что Vue также будет хорошо ладить с TypeScript. Первым вопросом, который мне пришлось рассмотреть, был выбор между компонентом на основе объектов и компонентом на основе классов. В конце концов я решил, что у меня недостаточно опыта, чтобы сделать звонок, и решил использовать объектно-ориентированные для самых низких базовых компонентов и основанные на классах компоненты для остальных. Я опасался, что из-за того, что количество базовых компонентов было меньше, чем у других, в конечном итоге он превратился в проект, основанный на классах. Когда я работал над проектом, я осознал, насколько большую роль играет Vuex, и эта статья написана специально для того, чтобы поделиться своим опытом, а также результатом.

Компонент JavaScript и Vue

Прошу прощения за долгое вступление. Честно говоря, я просто не могу поверить, как много изменилось и как далеко зашел мир развития. Коды, которыми я собираюсь поделиться с вами, находятся в файлах, непонятных браузерам SFC, а язык даже технически не является JavaScript. Коды, представленные вам в браузере при отладке, не являются кодами, которые используются, а всего лишь призрачными вымыслами реструктурированной исходной карты. Даже когда я это описываю, я испытываю ностальгию, поэтому без лишних слов продолжу статью.

Однофайловый компонент (SFC) - это файл, предназначенный исключительно для компонента Vue, рекомендованный Vue. Шаблон, JavaScript и даже CSS определены в одном файле. Когда разработчики работают с Vue, вместо определения классов для создания компонентов разработчики могут просто определить параметры для создания таких классов.

<template>
  <div>
    <input type="text" v-model="newTodo" @keyup.enter="onEnter">
    <ul>
      <li v-for="todo in todos">{{todo}}</li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      todos: ['TASK1'],
      newTodo: ''
    };
  },
  methods: {
    onEnter(ev) {
      this.addTodo(this.newTodo);
    },
    addTodo(title) {
      this.todos.push(title);
    }
  }
};
</script>

Выше вы видите простой пример компонента Todolist. Фрагмент данных, содержащий список Todo, представляет собой массив с именем todos, в котором элементы хранятся в виде строк. В шаблоне автоматически выполняется итерация массива todos, и Todolist изображается с помощью li-элемента. Если в поле ввода вводится новая задача, она обновляет массив todos. Это достаточно простой компонент, и он работает нормально. Теперь давайте изменим его с помощью TypeScript.

Vue.extend

Есть два способа реализовать TypeScript в компоненте Vue. Один из них использует Vue.extend для его объективизации, или вы можете просто создавать компоненты на основе классов. Например, чтобы полностью различать характеристики компонентов, я использовал оба метода, чтобы облегчить понимание. Давайте сначала рассмотрим использование метода Vue.extend. Когда компонент определен с использованием Vue.extend, он похож на версию компонента без TypeScript.

<template>
  <div>
    <input type="text" v-model="newTodo" @keyup.enter="onEnter">
    <ul>
      <li v-for="todo in todos">{{todo}}</li>
    </ul>
  </div>
</template>
   
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
  data() {
    return {
      todos: ['TASK1'],
      newTodo: ''
    };
  },
  methods: {
    onEnter(ev: UIEvent) {
      this.addTodo(this.newTodo);
    },
    addTodo(title: string) {
      this.todos.push(title);
    }
  }
});
</script>

Определите компонент Vue так же, как и с JavaScript, используя объект-параметр построения компонента. Однако с Vue.extend вы можете использовать типы. Если вы используете Visual Studio Code, вы можете проверить сводку типов, наведя курсор на расширение, и даже можете просматривать файлы и подписи с помощью Peek Definition, как показано на изображении ниже.

TypeScript можно интегрировать в проекты Vue для разработки компонентов, используя extend в объявлении типа, определенном в каталоге @type. Предупреждения о недопустимых параметрах компонентов, а также предупреждения о неправильном использовании API-интерфейсов или членов компонентов работают правильно, и типы данных предполагаются надлежащим образом. Когда вначале я проводил простые тесты, подобные этим, я был счастлив, как моллюск во время прилива, но мое счастье было недолгим.

interface Todo {
  title: string;
}

Здесь я объявил тип Todo для данных компонента. Я сделал это с намерением получить данные типа Todo от родительского компонента в качестве свойств. У меня не было причин сомневаться, что это не сработает, поэтому я заявил, что буду использовать тип массива Todo по умолчанию с Todo [].

export default Vue.extend({
  props: {
    todos: {
      type: Todo[],
      required: true,
      default: []
    }
  },
  ...

Однако вскоре этот код выдает ошибку.

В ошибке указано, что Todo используется как значение, тогда как его следует использовать только для ссылки на тип. Это означает, что когда я объявил type: Todo[], я не объявлял тип, а скорее присвоил значение Todo[] type. Подумав об этом, синтаксис обрел смысл; типы, определенные в TypeScript, полезны только на стадии разработки и компиляции, но не существуют в транспилированном коде JavaScript. Моя логика работала с JavaScript, потому что Number и Array были примитивными значениями, которые я действительно мог использовать. Есть статьи, предлагающие решения, но ни одна еще не сбила меня с ног.

Я до сих пор не нашел привлекательного решения проблемы, заключающейся в том, что в TypeScript при определении компонента в объектной форме типы TypeScript не могут использоваться в качестве типа свойств. В текущем проекте я использую объектную форму только для определения самых нижних компонентов, и по той же причине, которую я только что описал, я использую только базовые типы, предоставляемые JavaScript.

export default Vue.extend({
  props: {
    title: {
      type: String,
      required: true,
      default: []
    }
  },

Я столкнулся с другой проблемой, когда представил Vuex в своем проекте. Подобная проблема возникает и в других компонентах на основе классов. Функции Vuex Map-Helper удобно отображают функции, связанные с различными данными, определенными в хранилище, как члены компонента. Помощники по картам, основанные на JavaScript, постоянно использовались почти для всех функций магазина. Однако в TypeScript использование Map-Helpers изменяет методы и данные на String и другие параметры Map-Helper, что затрудняет для TypeScript определение наличия определенного члена в компоненте.

methods: {
  ...mapActions(['addTodo']),
  onEnter(ev: UIEvent) {
    this.addTodo(this.newTodo);
  }
}

Следовательно, Map-Helpers нельзя использовать. Мне пришлось бы вручную создать метод косвенного обращения или для действий вручную выполнить отправку, чтобы получить доступ к магазину.

methods: {
  addTodo(todo: string) {
    this.$store.dispatch(‘addTodo’, todo);
  },
  onEnter(ev: UIEvent) {
    this.addTodo(this.newTodo);
  }
}

В конце концов я пришел к выводу, что Vue.extend - не лучшая комбинация для TypeScript.

Компонент на основе классов

Поскольку при использовании Vue.extend и объектов были проблемы с TypeScript, я предположил, что при использовании TypeScript было бы лучше разрабатывать компоненты в классах. Я считал, что Vue.extend - это метод, который придает больший вес структуре Vue в целом, а компоненты на основе классов придают большее значение самому языку. Хотя вы можете использовать компоненты на основе классов в ES6, поскольку Vue построен на основе того, что компоненты создаются с использованием объектов параметров построения компонентов, я утверждаю, что эта форма объекта по-прежнему уместна в ES6. Хотя я понятия не имею, как все изменится в Vue 3, я хотел бы верить, что Vue и я разделяем общее мнение. Обсуждение компонента Vue, основанного на классах, еще не имеет конкретного ответа, но постоянно обсуждается. Возможно, без TypeScript такое обсуждение могло бы и не возникнуть.

<template>…</template>
<script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';
import {mapActions} from 'vuex';
@Component({
  methods: {
    ...mapActions(['addTodo'])
  }
})
export default class Todolist extends Vue {
  public newTodo: string = '';
  public addTodo!: (title: string) => void;
  @Prop({required: true})
  public todos!: Todo[];
  public onEnter(ev: UIEvent) {
    this.addTodo(this.newTodo);
  }
}
</script>

При работе с классами мы используем get и set для определения вычисляемых свойств, методы используются непосредственно как методы класса, а поле данных из класса может использоваться как в Vue. Другие концепции, такие как наблюдатели и реквизиты, можно использовать во Vue с помощью декораторов. Декораторы предоставляют параметры, доступные во Vue для компонентов на основе классов. Использование компонентов на основе классов позволяет нам легко работать с типами свойств компонентов.

@Prop({required: true})
public todos!: Todo[];

Здесь я использовал декоратор Prop, чтобы определить, что todos является опорой, и передал его в качестве входных данных. Кроме того, тип может быть объявлен как неизменный синтаксис TypeScript, а не просто как опция. Я думаю, что помимо того факта, что vue-property-decorator не поддерживается официальными разработчиками Vue, декоратор Prop является стабильным и понятным решением наших проблем. Теперь, когда мы используем Props в компонентах, мы можем использовать типы в наших интересах.

Однако Vuex Map-Helper стал еще одной отравой нашего кода. Я попытался использовать помощник mapAction с помощью декоратора компонентов.

@Component({
  methods: {
    ...mapActions(['addTodo'])
  }
})

Предположим, что мы уже четко определили сигнатуру действия addTodo магазина с использованием типа TypeScript. Даже если это так, поскольку addTodo был передан в качестве входных данных через String, компонент не может проверить, существует ли addTodo. Вот почему, когда мы пытаемся использовать компонент addTodo сразу после этого, сообщение об ошибке имеет наглость сказать, что мы пытаемся использовать метод, не определенный в компоненте. Хотя у нас не было возможности использовать Map-Helpers с методом Vue.extend, мы делаем это для компонентов на основе классов! Мы просто снова определяем подпись внутри класса.

public addTodo!: (title: string) => void;

Мы обманули, принудительно установив подпись метода избыточным образом. Стоимость моего повторения можно найти не только в логике моего кода, но и в определении типа. Мы должны сделать так, чтобы мы определяли тип только один раз и позволяли компиляции определять, что это за тип. Хотя я потратил много времени на поиски других возможностей, мне не повезло.

Заключение

В конце концов, я решил придерживаться принципа Do-not-Repeat-Yourself (DRY) и отказался от использования Map-Helpers. Отображение элементов магазина с помощью Map - Помощники, в зависимости от компонента, для начала создавали длинные коды, но теперь с повторением типов код стал излишне сложным. Если вы также считаете, что нужно менять типы в разных ситуациях, я был ошеломлен. Компоненты моего текущего проекта выглядят следующим образом.

<template>…</template>
<script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';
@Component
export default class Todolist extends Vue {
  public newTodo: string = '';
  @Prop({required: true})
  public todos!: Todo[]
  get schedule(): Schedule {
    return this.$store.state.schedule;
  }
  public onEnter(ev: UIEvent) {
    this.$store.dispatch('addTodo', this.newTodo);
  }
}
</script>

Вместо использования Map-Helpers я решил определить свое собственное соглашение о кодировании Vuex.

  • Если не принудительно в особых ситуациях, используйте методы отправки и фиксации для прямого вызова действия и мутации вместо использования косвенных методов. Если бы в качестве входных данных был передан неправильный тип, мы не смогли бы его использовать, но это позволило бы нам исключить повторение типов; кроме того, если выполняется неточное действие или мутация, фреймворк сообщит нам об этом, выдав ошибку.
  • Хотя state и getter могут быть определены как computed при определенных обстоятельствах и могут использоваться напрямую через $store, избегайте использования $store в самом шаблоне. Это только усложняет шаблон.

Следуя моему собственному соглашению, разработка компонентов Vue в среде TypeScript была легкой задачей. Видя, что у меня не было никаких серьезных проблем даже в финале проекта, я думаю, можно с уверенностью сказать, что шаблон, вероятно, будет продолжаться, по крайней мере, до тех пор, пока не будет выпущен Vue 3.

На данный момент я думаю, что TypeScript лучше подходит для React, чем для Vue. Помимо этой статьи, у меня есть важная информация, подтверждающая это утверждение. TypeScript поддерживает JSX под именем TSX, что позволяет TypeScript проверять компоненты JSX, но по-прежнему не поддерживает SFC или шаблоны Vue. Следовательно, независимо от того, насколько надежно вы разрабатываете тип компонента Props, он никогда не будет полностью соответствовать текущему состоянию Vue. Я лично прихожу к выводу, что фреймворку TypeScript и Vue нужно больше времени вместе, но Vue очень поддерживает TypeScript до такой степени, что вся кодовая база Vue 3 была разработана на TypeScript. Я думаю, мы можем надеяться, что Vue 3 представит более привлекательные варианты.
Не так давно в документе RFC, обсуждающем направление развития Vue, был добавлен Class API RFC. Хотя я согласен с тем, что это следует обсудить более подробно, я также считаю, что пока это выглядит хорошо. Я рекомендую всем, кому интересно, это проверить, и если у вас есть другие интересные альтернативы, я приветствую это с распростертыми объятиями.



Изначально опубликовано на Toast Meetup, автор: Сонхо Ким.