Практика Typescript в Vue

TypeScript Vue.js

1 Преимущества использования машинописного текста

Говоря о ts, возникает неизбежный вопрос: зачем использовать ts и есть ли какие-то преимущества перед js? Попробую ответить на этот вопрос с двух сторон:

1.1 Удобство при разработке проекта

  1. Избегайте низкоуровневых ошибок

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

image

  1. Редактор IntelliSense

Когда проект становится все больше и больше, также очень трудно запомнить, что описывает переменная. Это может потребовать от нас продолжать искать определение типа. На самом деле, ts может решить эту проблему очень хорошо, хотя определение типа в начало немного громоздко, но подсказки типов, завершение кода и т. д., которые он приносит, заставят нас почувствовать, что рабочая нагрузка того стоит.

1.2 Затраты на техническое обслуживание после завершения проекта

  1. Тип — аннотация

В нашей работе нам неизбежно потребуется обновить или изменить определенный модуль.Иногда нам нужно прочитать код, если нет комментариев или неполные комментарии.На самом деле, во многих случаях нас не волнует конкретная реализация внутри модуля, мы просто хотим знать Его вход и выход, в это время ts очень подходит для этой сцены:

type Pay = (orderId: string) => Promise<-1 | 0>

const pay: Pay = orderId => {
  // do something
}

Подобно приведенному выше коду, его цель может быть примерно выведена из определения типа.

  1. хорошо подходит для рефакторинга

Это может быть большим преимуществом системы типов. Раньше я ходил по тонкому льду при рефакторинге js-проекта. Я боялся, что весь проект рухнет после модификации определенного модуля. С системой типов я чувствую более непринужденно.Где редактор становится красным?Его можно модифицировать где угодно.

2 Пусть текст течет

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

// goodApi.ts
export type GoodInfo = {
  infoId: string,
  price: string,
  title: string,
  label: {
    labeltxt: string,
    labelImg: string
  }[]
}

const getGoodInfo = (): GoodInfo[] => {
  // do something
}

Теперь есть способprocessLabelпродукт, который необходимо ввестиlabelиди прямоlabelImgВыполните некоторую обработку следующим образом:

type LabelMsg = {
  labeltxt: string,
  labelImg: string
}[]

const processLabel = (labelArr: LabelMsg) => {
  // do something
}

очевидноlabelArrполученный изGoodInfoизlabel, поэтому лучше повторно использоватьGoodInfoВведите:

import { GoodInfo } from './goodApi.ts'

const processLabel = (labelArr: GoodInfo['label']) => {
  // do something
}

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

import { GoodInfo } from './goodApi.ts'
import { UserInfo } from './userApi.ts'

type Info = Pick<GoodInfo, 'label'> & Pick<UserInfo, 'tag | avatar'>
const processLabel = (info: Info) => {
  // do something
}

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

3 дюймаVue@2.xиспользовать ТС в

Хорошо, давайте перейдем к теме этой статьи, используя ts во Vue, обычный компонент во Vue может выглядеть так:

export default {
  template: '<button @click="onClick">Click!</button>',
  data() {
    return {
      preStr: 'Hello!'
    }
  },
  props: {
    message: {
      type: String,
      default: 'world'
    }
  },
  methods: {
    onClick () {
      window.alert(this.preStr + this.message)
    }
  }
}

Vue@2.xДля использования ts обычно требуется помощьvue-property-decoratorЭта библиотека, давайте в общих чертах посмотрим, как она используется:

import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator';

@Component({
  template: '<button @click="onClick">Click!</button>'
})
export default class Test extends Vue {
  preStr: string = 'Hello!'

  @Prop({ type: String, default: 'world' })
  message: string

  onClick (): void {
    window.alert(this.preStr + this.message)
  }
}

Видно, что необходимо преобразовать написание объектных литералов в метод определения компонента на основе классов, чтобы было легко добавлять типы в состояние, проп, метод и т.д. компонента, и в то же время время, он использует декоратор для экспорта стандартного компонента vue во время выполнения, давайте посмотрим наДекоратор компонентовВнутренняя работа:

export function componentFactory (
  // Component即定义的组件类
  Component: VueClass<Vue>,
  // options为传给Component装饰器的初始选项
  options: ComponentOptions<Vue> = {}
): VueClass<Vue> {
  options.name = options.name || (Component as any)._componentTag || (Component as any).name

  const proto = Component.prototype
  Object.getOwnPropertyNames(proto).forEach(function (key) {

    // 如果方法名与vue生命周期同名 直接放进options内
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)
    // 普通方法放进options的methods内
    if (descriptor.value !== void 0) {
      if (typeof descriptor.value === 'function') {
        (options.methods || (options.methods = {}))[key] = descriptor.value
      }
    // get set函数放进options的计算computed内
    } else if (descriptor.get || descriptor.set) {
      (options.computed || (options.computed = {}))[key] = {
        get: descriptor.get,
        set: descriptor.set
      }
    }
  })

  ;(options.mixins || (options.mixins = [])).push({
    data (this: Vue) {
      // 类实例上的属性当做options中data函数的返回值,即state
      return collectDataFromConstructor(this, Component)
    }
  })

  // Super就是Vue 根据计算得到的options选项extend一个常规的vue组件
  const Extended = Super.extend(options)

  return Extended
}

вышеComponentЧасть исходного кода декоратора заключается в том, чтобы грубо собрать методы на прототипе класса компонента и распределить его по разным условиям.optionsв методах жизненного цикла, обычных методах и вычисляемых свойствах, а затем собирайте свойства в экземпляре класса какoptionsсерединаdataВозвращаемое значение функции, наконец, согласноoptionsидтиextendстандартvueкомпоненты.

Просто посмотрите на следующееPropПринцип декоратора:

export function Prop(options: PropOptions | Constructor[] | Constructor = {}) {
  return (target: Vue, key: string) => {
    applyMetadata(options, target, key)
    createDecorator((componentOptions, k) => {
      // componentOptions即上文中的options对象  k即被修饰的键名
      ;(componentOptions.props || ((componentOptions.props = {}) as any))[
        k
      ] = options
    })(target, key)
  }
}

PropРабота несложная, т.PropИзмененное имя ключа и входящий параметр образуют пару ключ-значение и помещают ее вoptions.props, похожий наvue-property-decoratorТакже предоставляются многие другие декораторы (Model, Watch, Ref и т. д.), охватывающие распространенные сценарии использования.Основной принцип заключается в грубом изменении параметров параметров построения компонентов во время выполнения.

4 дюймаVue@3.xиспользовать ТС в

Vue@3.xВыпущена официальная версия с Composition API и улучшенной поддержкой машинописи. дляVue@3.x+ ts, вы можете точно определить тип в параметрах компонента, просто определив компонент с помощью defineComponent:

import { defineComponent } from 'vue'

const Component = defineComponent({
  data() {
    return {
      preStr: 'hello!'
    }
  },
  props: {
    message: {
      type: String,
      required: true
    }
  },
  methods: {
    onClick() {
      this.preStr = 'hi!' // ok    preStr被推断成 string
      this.preStr = 123   // error number类型不能分配给string类型
      this.message = 'zz' // error message是read-only属性
    }
  }
})

Также имеется высокая поддержка Composition Api:

import { defineComponent, ref, PropType } from 'vue';

export default defineComponent({
  props: {
    callback: {
      required: true,
      type: Function as PropType<(nums: number) => void>
    },
    message: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const nums = ref(0)

    props.callback(props.message)  // error callback的参数应该是number类型

    props.callback(nums.value)     // ok
  }
})

Я также попробовал это через демо.Vue@3.xМожно сказать, что использование ts in очень плавно по сравнению сVue@2.xМетод определения компонента на основе класса вVue@3.xМы можем легко получить доступ к ts, не изменяя наших привычек кода, а его вывод типов довольно эффективен.

Наконец, если вам нужно найти недостаток, метод написания шаблонов Vue все еще не совместим с ts.Хотя есть альтернативы tsx, использование tsx не очень Vue, но я полагаю, что будут отличные инструменты, чтобы компенсировать это в будущее недостаточно. В общем, поддержка Vue для ts достаточно сильна, давайте использовать ее быстро, это вкусно!

Раздел благосостояния

Статья также будет отправлена ​​на публичный аккаунт "Да Чжуань Чжуань ФЭ", а в паблик аккаунте будет проведена лотерея. Призом в этой статье будет памятная футболка Чжуань Чжуаня. Просим всех обратить внимание ✿✿ヽ(°▽°)ノ✿