Vue3 official website - tools TypeScript support (Vue CLI)

Keywords: Javascript TypeScript Vue.js

Vue3 official website - tools (XIX) TypeScript support (Vue CLI)

Summary:

  • Vue CLI
    • https://cli.vuejs.org/zh/guide/

1. TypeScript support

Vue CLI Provides built-in TypeScript tool support.

Official statement in NPM package

With the growth of applications, the static type system can help prevent many potential runtime errors, which is why Vue 3 is written with TypeScript. This means that using TypeScript in Vue does not require any other tools -- it has first-class citizen support.

Recommended configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // In this way, the data attributes on 'this' can be inferred more strictly
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}

Note that you must include strict: true (or at least noImplicitThis: true, which is part of the strict flag) to take advantage of this's type checking in component methods, otherwise it is always treated as any type.

See TypeScript compilation options document See more details.

Webpack configuration

If you use custom Webpack configuration, you need to configure 'ts loader' to parse the < script lang = "ts" > code block in the vue file:

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
      ...

development tool

Project creation

Vue CLI You can generate new projects that use TypeScript. Start:

# 1. Install Vue CLI, if not already installed
npm install --global @vue/cli@next

# 2. Create a new project and select the "Manually select features" option
vue create my-project-name

# 3. If you already have a Vue CLI project without TypeScript, add the appropriate Vue CLI plug-in:
vue add typescript

Ensure that the script part of the component has the language set to TypeScript:

<script lang="ts">
  ...
</script>

Or, if you want to combine TypeScript with JSX render function Combined:

<script lang="tsx">
  ...
</script>

Editor support

For developing Vue applications using TypeScript, we strongly recommend using Visual Studio Code , it provides good out of the box support for TypeScript. If you use Single file component (SFCs), then you can use great Volar extension , it provides TypeScript reasoning and many other excellent features in SFCs.

WebStorm It also provides out of the box support for TypeScript and Vue.

Defining Vue components

For TypeScript to correctly infer types in Vue component options, you need to define components using the defineComponent global method:

import { defineComponent } from 'vue'

const Component = defineComponent({
  // Type inference enabled
})

If you use Single file component , it is usually written as:

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // Type inference enabled
})
</script>

Use with Options API

TypeScript should be able to infer most types without explicitly defining them. For example, for a component with a count property of numeric type, if you try to call a method unique to the string, an error will appear:

const Component = defineComponent({
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    const result = this.count.split('') // => Property 'split' does not exist on type 'number'
  }
})

If you have a complex type or interface, you can use type assertion Specify:

interface Book {
  title: string
  author: string
  year: number
}

const Component = defineComponent({
  data() {
    return {
      book: {
        title: 'Vue 3 Guide',
        author: 'Vue Team',
        year: 2020
      } as Book
    }
  }
})

Extend type for globalProperties

Vue 3 provides a globalProperties object , which is used to add global properties that can be accessed by any component instance. For example, a plug-in unit You want to inject a shared global object or function.

// User defined
import axios from 'axios'
const app = Vue.createApp({})
app.config.globalProperties.$http = axios
// Plug in for validating data
export default {
  install(app, options) {
    app.config.globalProperties.$validate = (data: object, rule: object) => {
      // Check whether the object is compliant
    }
  }
}

To tell TypeScript these new properties, we can use Module augmentation.

In the above example, we can add the following type declarations:

import axios from 'axios'
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $http: typeof axios
    $validate: (data: object, rule: object) => boolean
  }
}

We can put these type declarations in the same file or a project level *. d.ts file (for example, in the Src / types folder that TypeScript will automatically load). For the library / plug-in author, this file should be defined in the types property of package.json.

Confirm that the declaration file is a TypeScript module

In order to make good use of the module expansion, you need to make sure that there is at least one top-level import or export in your file, even if it is just an export {}.

In TypeScript , any file that contains a top-level import or export is considered a "module". If the type declaration is outside the module, the declaration overrides rather than extends the original type.

For more information about the ComponentCustomProperties type, see its Definition in @ Vue / runtime core and Its TypeScript test case Learn more.

Annotation return type

Due to the circular nature of Vue declaration files, TypeScript may have difficulty inferring the type of computed. Therefore, you may need to annotate the return type of the calculated property.

import { defineComponent } from 'vue'

const Component = defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // Annotation required
    greeting(): string {
      return this.message + '!'
    },

    // When using setter s for calculation, you need to annotate getter s
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})

Annotation Props

Vue performs runtime validation on prop with type defined. To provide these types to TypeScript, we need to use PropType to indicate the constructor:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  author: string
  year: number
}

const Component = defineComponent({
  props: {
    name: String,
    id: [Number, String],
    success: { type: String },
    callback: {
      type: Function as PropType<() => void>
    },
    book: {
      type: Object as PropType<Book>,
      required: true
    },
    metadata: {
      type: null // The type of metadata is any
    }
  }
})

WARNING

Due to in TypeScript Design limitations , when it comes to type inference of function expressions, you must pay attention to the validator and default values of objects and arrays:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

const Component = defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // Be sure to use the arrow function
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    },
    bookB: {
      type: Object as PropType<Book>,
      // Or provide an explicit this parameter
      default(this: void) {
        return {
          title: 'Function Expression'
        }
      },
      validator(this: void, book: Book) {
        return !!book.title
      }
    }
  }
})

Annotation emit

We can annotate a payload for the triggered event. In addition, all undeclared trigger events will throw a type error when called.

const Component = defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // perform runtime validation
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // Wrong type!
      })
      this.$emit('non-declared-event') // Wrong type!
    }
  }
})

Use with composite API

In the setup() function, you do not need to pass the type to the props parameter because it infers the type from the props component options.

import { defineComponent } from 'vue'

const Component = defineComponent({
  props: {
    message: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const result = props.message.split('') // Correct, 'message' is declared as a string
    const filtered = props.message.filter(p => p.value) // An error will be thrown: Property 'filter' does not exist on type 'string'
  }
})

Type declaration refs

Refs infers the type from the initial value:

import { defineComponent, ref } from 'vue'

const Component = defineComponent({
  setup() {
    const year = ref(2020)

    const result = year.value.split('') // => Property 'split' does not exist on type 'number'
  }
})

Sometimes we may need to specify a complex type for the internal value of Ref. We can simply pass a generic parameter when calling ref to override the default reasoning:

const year = ref<string | number>('2020') // year's type: Ref<string | number>

year.value = 2020 // ok!

TIP

If the type of generic type is unknown, it is recommended to convert ref to ref < T >.

Define types for template references

Sometimes you may need to label a template reference for a child component to call its public methods. For example, we have a MyModal sub component, which has a method to open the mode:

import { defineComponent, ref } from 'vue'
const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)
    return {
      isContentShown,
      open
    }
  }
})

We want to call this method from a template reference of its parent component:

import { defineComponent, ref } from 'vue'
const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)
    return {
      isContentShown,
      open
    }
  }
})
const app = defineComponent({
  components: {
    MyModal
  },
  template: `
    <button @click="openModal">Open from parent</button>
    <my-modal ref="modal" />
  `,
  setup() {
    const modal = ref()
    const openModal = () => {
      modal.value.open()
    }
    return { modal, openModal }
  }
})

It works, but there is no type information about MyModal and its available methods. To solve this problem, you should use InstanceType when creating References:

setup() {
  const modal = ref<InstanceType<typeof MyModal>>()
  const openModal = () => {
    modal.value?.open()
  }
  return { modal, openModal }
}

Please note that you also need to use Optional chain operator Or other ways to confirm that modal.value is not undefined.

Type declaration reactive

When declaring the type reactive property, we can use the interface:

import { defineComponent, reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

export default defineComponent({
  name: 'HelloWorld',
  setup() {
    const book = reactive<Book>({ title: 'Vue 3 Guide' })
    // or
    const book: Book = reactive({ title: 'Vue 3 Guide' })
    // or
    const book = reactive({ title: 'Vue 3 Guide' }) as Book
  }
})

Type declaration computed

The calculated value will automatically infer the type based on the return value

import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  name: 'CounterButton',
  setup() {
    let count = ref(0)

    // read-only
    const doubleCount = computed(() => count.value * 2)

    const result = doubleCount.value.split('') // => Property 'split' does not exist on type 'number'
  }
})

Add type for event handler

When dealing with native DOM events, it may be useful to correctly add types to the parameters of the handler function. Let's look at this example:

<template>
  <input type="text" @change="handleChange" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    // `evt ` will be of type 'any'
    const handleChange = evt => {
      console.log(evt.target.value) // The TS will throw an exception here
    }
    return { handleChange }
  }
})
</script>

As you can see, TypeScript throws an exception when we try to get the value of the < input > element without correctly declaring the type for the evt parameter. The solution is to convert the target of the event to the correct type:

const handleChange = (evt: Event) => {
  console.log((evt.target as HTMLInputElement).value)
}

2. Production environment deployment

INFO

If you use Vue CLI , most of the following prompts are enabled by default. This section is relevant only if you use custom build settings.

Turn on production environment mode

During development, Vue provides many warnings to help you deal with common errors and hidden dangers. However, these warning strings become meaningless in a production environment and increase the burden on the application. In addition, there are some warning checks that also incur some run-time overhead Production mode This overhead can be avoided.

Do not use build tools

If you are using a full build, that is, introducing Vue directly through the script tag instead of using the build tool, make sure to use the compressed version in the production environment. This can be in Installation guide Found in.

Using the build tool

When using build tools such as Webpack or Browserify, the production environment pattern will be defined by process.env.node in Vue's source code_ Determined by Env, the default is development mode. Both build tools provide a way to override this variable to enable Vue's production mode, and warnings will be deleted by the compression tool during the build process. Vue CLI has preconfigured this for you, but it's better to understand how it works:

Webpack

In Webpack 4 +, you can use the mode option:

module.exports = {
  mode: 'production'
}

Browserify

  • Set the current environment variable NODE_ENV is set to "production" as the packaging command to run. It tells vueify to avoid introducing hot overloads and developing related code.

  • Set a global envify The transformation is applied to your package. This allows the compression tool to delete all warnings in the Vue source code wrapped in the environment variable condition block. For example:

    NODE_ENV=production browserify -g envify -e main.js | uglifyjs -c -m > build.js
    

    1

  • Alternatively, use Gulp envify:

    // Use the envify custom module to specify environment variables
    const envify = require('envify/custom')
    
    browserify(browserifyOptions)
      .transform(vueify)
      .transform(
        // Required in order to process node_modules files
        { global: true },
        envify({ NODE_ENV: 'production' })
      )
      .bundle()
    
  • Or, using Grunt and grunt-browserify use envify:

    // Use the envify custom module to specify environment variables
    const envify = require('envify/custom')
    
    browserify: {
      dist: {
        options: {
          // Function to deviate from grunt-browserify's default order
          configure: (b) =>
            b
              .transform('vueify')
              .transform(
                // Required in order to process node_modules files
                { global: true },
                envify({ NODE_ENV: 'production' })
              )
              .bundle()
        }
      }
    }
    

Rollup

use @rollup/plugin-replace:

const replace = require('@rollup/plugin-replace')

rollup({
  // ...
  plugins: [
    replace({
      'process.env.NODE_ENV': JSON.stringify( 'production' )
    })
  ]
}).then(...)

Precompiled template

When you use a template in DOM or a template string in JavaScript, the compilation from the template to the rendering function is performed dynamically. In most cases, this is fast enough, but if your application is performance sensitive, it's best to avoid doing so.

The easiest way to precompile a template is to use Single file component ——The relevant build settings automatically perform precompiling for you, so the build code contains the compiled rendering function instead of the original template string.

If you are using Webpack and prefer to separate JavaScript and template files, you can use vue-template-loader , it can also convert template files into JavaScript rendering functions in the build step.

Extract component CSS

When using a single file component, the CSS inside the component will be dynamically injected in the form of < style > tags through JavaScript. This has a small run-time cost. If you use server-side rendering, it will lead to "flash of styleless content". Extracting the CSS of all components into the same file can avoid these problems and better compress and cache CSS.

Refer to the respective build tool documentation to see how it works:

Trace runtime error

If a runtime error occurs during component rendering, it will be passed to the global app.config.errorHandler configuration function if it has been set. Associate this hook with an error tracking service such as Sentry It may be a good idea to use together, which provides Vue with An official integration.

Posted by kingssongs on Thu, 07 Oct 2021 13:11:10 -0700