在 Vue 中使用 TypeScript(以及 RealWorld使用)

学习原因

  • 关于 TypeScript 相关资源和信息越来越多
  • 已经有越来越多的基于 JS 来写的项目,使用 TS 来编写。
    • 框架
    • npm 包
  • TS 未来可能会成为一个技能标配
    • 语法
    • 项目实战经验

创建支持 TypeScript 的 Vue 项目

  • 通过 Vue CLI 创建 支持 TS 的 Vue 项目
  • 在一个已有的 Vue 项目中配置 TypeScript 支持

通过 Vue CLI 创建

$ vue create vue-ts

? Please pick a preset: Manually select features

# 这一步把 TypeScript 勾选上即可,其它功能根据自己需要进行选择
# CSS Pre-processors - CSS 预处理器(如:SCSS LESS)
? Check the features needed for your project: Babel, TS, Router, Vuex, Linter

# 选择了 TS 后会询问,是否使用 class 的语法风格定义组件(React中的定义方式)
# 如果你喜欢使用 class 的方式定义组件,则输入 Yes,不喜欢的话就 No
? Use class-style component syntax? Yes

# 是否使用 Babel 编译 TS 语法
# TypeScript 本身就有编译功能,默认会把 JavaScript 代码转换为 ECMAScript 3 版本兼容的代码
# 如果你需要现代模式、自动检测 polyfill、转换 JSX 语法等功能,则建议开启这个选项
# 当选择以后,TypeScript 本身只会把代码转为 ESNext,也就是最新版的 ECMAScript 规范
# 然后由 Babel 把 ECMAScript 转换为低版本 JavaScript,例如 ECMAScript 5,以及自动检测 polyfill、转换 JSX 等功能
# 说白了就是让 TypeScript 转换和 Babel 转换相结合起来一起使用
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes

? Use history mode for router? (Requires proper server setup for index fallback in production) No

# 根据需要选择 校验风格
# TSLint(deprecated) 已经备注了不建议选择
? Pick a linter / formatter config: Standard

# 选择校验的时机(代码保存的时候,代码提交的时候)
# 建议都选上,更严谨
? Pick additional lint features: Lint on save, Lint and fix on commit

? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

? Save this as a preset for future projects? No
npm 包
{
  "dependencies": {
    // 这两个是支持用class编写组件的
    "vue-class-component": "^7.2.3",
    "vue-property-decorator": "^8.4.2",
  },
  "devDependencies": {
    // eslint 校验TS代码规范,不建议使用 tslint
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "@vue/eslint-config-typescript": "^5.0.2",
    // vue cli 帮助打包构建 .ts 文件 的插件
    "@vue/cli-plugin-typescript": "~4.5.0",
    // ts
    "typescript": "~3.9.3",
  },
}
TS 配置文件 tsconfig.json

通常情况下不需要对这些配置进行更改。

{
  // 配置选项
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  // include 指定需要被编译的文件
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  // exclude 排除不需要被编译的文件
  "exclude": [
    "node_modules"
  ]
}

ESLint 配置文件 .eslintrc.js

它默认用来校验 JS 代码规范。

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    // 在vue中校验standard风格代码规范
    '@vue/standard',
    // 在vue中校验typescript风格代码规范
    '@vue/typescript/recommended'
  ],
  parserOptions: {
    ecmaVersion: 2020
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}

src 源码
  • .js 文件都变为了 .ts 文件
  • src 根目录多了两个 .d.ts 文件(shims-tsx.d.ts、shims-vue.d.ts)
    • shims-tsx.d.ts
      • 主要是方便你使用在 ts 中使用 jsx 语法的,如果不使用 jsx 语法,可以无视
    • shims-vue.d.ts
      • 主要用于 TS 识别.vue 文件,TS 默认并不支持导入 vue 文件,这个文件告诉 TS 导入.vue 文件都按VueConstructor<Vue>(Vue 构造函数)处理
        • 这样配置可以实现快速跳转到引入的文件
      • 但是这样会衍生两个问题
        • 导入vue文件必须写 .vue 后缀
        • 就算导入的 .vue 文件路径错误,静态检测也不会检测到错误,鼠标放到错误路径上会看到全部指向*.vue,正确的就会显示真实路径。

错误路径:

在这里插入图片描述

正确路径:

在这里插入图片描述

  • components/HelloWorld.vue 单文件组件中,使用了 ts 语法 编写脚本
    • 并且使用了 class 的方式编写(创建项目时选择的)
<!-- lang="ts" 把script中的内容当作 TypeScript 对待 -->
<script lang="ts">
  // 通过这些成员,定义Vue组件
import { Component, Prop, Vue } from 'vue-property-decorator'

  // @ 是装饰器语法,不建议用
@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
</script>

已有的Vue项目中支持 TypeScript

使用 @Vue/cli 安装 TypeScript 插件

vue add @vue/typescript
? Use class-style component syntax? Yes

? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes

# 是否需要把所有 .js 文件转换为 .ts 资源
# 不建议使用,转换的过程会修改已有的 .js 资源代码,可能会带来问题,建议手动启用 TypeScript 之后手动把 .js 处理为 .ts
? Convert all .js files to .ts? No

# 是否允许编译 .js 文件模块,不建议开启,既然选择了 TypeScript,就最好全部更换为 TypeScript,而不要两者混搭,反而变得不伦不类
? Allow .js files to be compiled? No

安装后项目中增加的内容和 Vue CLI 创建项目中增加的内容差不多。

定义组件的方式

常规方式 直接导出选项对象

在不支持 TS 的环境中:

<script>
export default {
  name: 'Foo',
  data() {
    return {}
  },
  created: 100
}
</script>

这种方式,只有在运行期间才知道定义的选项格式是否正确。

例如 代码中的 created 应该是一个函数类型,此时随便定义一个数字,并不会提示错误。

指定使用 TS 脚本后,TS 和 Vetur 插件就会显式智能提示,created 类型错误

在这里插入图片描述

Options APIs 使用 Vue.extend / Vue.component 定义

在使用TS语法时,通过 Vue.extend / Vue.component (下面只以Vue.extend为例)创建组件。

  • 需要引入 Vue import Vue from 'vue'
  • 内部定义的方式和常规方式一样
<script lang="ts">
// Options APIs
import Vue from 'vue'
export default Vue.extend({
  name: 'Foo',
  data () {
    return {
      message: 'Hello world'
    }
  }
})
</script>
使用 Vue.extend 的原因
  • 常规方式是直接导出一个对象。
  • 此时 TypeScript 无法推断出对应的类型
  • 所以必须使用调用 Vue 方法的方式,声明选项对象的类型,确保 TypeScript 能够有正常的类型推断
    • 后面使用类的方式定义组件,通过继承 Vue 创建 class,也是为了向TS声明类的类型

官方说法:

import Vue from 'vue'
const Component = Vue.extend({
  // 类型推断已启用
})

const Component = {
  // 这里不会有类型推断,
  // 因为 TypeScript 不能确认这是 Vue 组件的选项
}
插件智能提示 & 编译提示
// Foo.vue 示例代码1
<script lang="ts">
export default {
  created: 123 // created 应该是函数类型
}
</script>

// Foo.vue 示例代码2
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  created: 123 // created 应该是函数类型
})
</script>

在 vscode 中如果安装了插件 vetur,就会在开发时,实时校验并智能提示类型错误。

.vue 后缀的文件中,设置了 ts 脚本类型(lang=“ts”),不使用 vue.extend 插件也会进行类型推断并提示,如 “常规方式”中的图示。

而官方讲的使用 Vue.extend 才可以正确推断类型,指的是编译阶段

禁用插件 vetur,就取消了编辑器的识别 .vue 文件的代码高亮和类型推断功能。

不论使用 示例1 还是 示例2 的代码,编辑器都不会提示 created 类型错误。

此时打包或编译项目,区别就体现出来了:

  • 示例代码1 会在 编译到 src/router/index.ts 文件时,校验到路由组件中使用了 Foo 组件,进而校验到 Foo 组件中的 created 类型错误。
    • 结果定位到的是父级组件,需要主动继续定位
    • 当前项目是 Vue CLI 创建的,所以最终还是会被校验出来。
    • 如果是在已有项目中增加支持TS的情况,可能编译阶段就会因为没有在 Foo.vue 文件中校验出错误,导致编译成功,这样就只能在浏览页面(也就是运行时)才会在控制台看到报错。

在这里插入图片描述

  • 示例代码2 会在编译到 Foo.vue 文件时,立即校验到错误。
    • 可以快速定位,在编译时发现错误。

在这里插入图片描述

以上是为了区分为什么要使用 Vue.extend 定义组件。

它的目的是可以在编译阶段正确推断类型,而不用等到运行阶段。

编辑器的vetur插件也会进行推断,容易混淆对这个目的的理解。

编辑器还有其他很多校验错误,但是不影响编译甚至运行的情况,这里只需要知道,为了使用TS的类型推断功能,要使用 Vue.extend 或 下面基于 class 的定义方式就可以了。

.ts 文件中使用类型推断

编写 Vue 组件不一定使用 .vue 格式的文件,也可以使用 .js 或 .ts,最终导出一个构造函数或组件选项对象。

这可能也是要求使用 Vue.extend 的另一个解释。

尝试使用 .ts 文件编写组件,摆脱 vetur 的影响:

import Vue from 'vue'

const Bar = {
  name: 'Bar',
  render (h: any) {
    return h('div', 'hello bar')
  },
  created: 123
}

// export default Bar // 编译到父组件时报错

export default Vue.extend(Bar) // 编译当前文件时报错

Class APIs 基于类的Vue组件定义

上面使用 Vue.extend 方法,组件内部采用原生的 vue 写法。

如果您在声明组件时更喜欢基于类的 API,则可以使用官方维护的 vue-class-component 装饰器

官方并没有表态哪种方式更高级,选择只取决于编码习惯(类似React)。

在 TypeScript 下,Vue 的组件可以使用一个继承自 Vue 类型的子类表示。

  • 需要引入 Vue
    • import Vue from 'vue'
  • 需要引入 Component
    • import Component from 'vue-class-component'
  • 导出的类继承自 Vue
  • 使用 @Component (类的装饰器)注册
    • 装饰器是一个函数,用于扩展类
      • 可以调用它,接收的参数就是组件的选项对象
      • 也可以不调用它,只用来声明
    • 如果不使用装饰器标识,在chrome浏览器Vue插件中看不到组件的选项信息
  • 可以在类的内部直接定义 data、 methods、 computed(使用getter/setter)、生命周期函数
  • 其他特性例如 components、props、filters、directives 等,需要使用装饰器参数传入。
  • 使用 class 风格的组件声明方式,并没有特别的好处,只是提供给开发者多种编码风格的选择性。

Vue Class Component 官方文档

// Class APIs
import Vue from 'vue'
import Component from 'vue-class-component' // 官方库

// props 使用,要先创建一个实例,然后让组件继承它
const FooProps = Vue.extend({
  props: {
    propMessage: String
  }
})

// 声明并导出一个继承自Vue的类
// 类名:组件的 name
// @Component 修饰符注明了此类为一个 Vue 组件

// 不调用方式
// @Component
// export default class Foo extends Vue {
//   // ...
// }

// 调用方式
@Component({
  // 所有的组件选项都可以放在这里
  // data、methods、computed、hooks 以外的选项必须在这里定义
  // props 要使用实例继承实现
  data () {
    return {
      count: 100
    }
  }
})
export default class Foo extends FooProps {
  // 在这里可以直接声明实例的data、methods、computed、hooks
  // 其他所有选项,需在装饰器函数Component(options)中定义:components、props等

  // data:初始化 data 的成员
  msg = 'world'

  // 使用 prop 初始化的 data 的成员(注意props的定义方式)
  helloMsg = 'Hello, ' + this.propMessage

  // hooks:钩子函数
  mounted () {
    this.say()
  }

  // computed:通过get 和 set 将计算的属性声明为类属性访问器
  get computedMsg () {
    return 'computed ' + this.msg
  }

  // methods:直接定义方法
  say () {
    console.log('Hello ' + this.msg)
  }
}

vue-class-component 在使用 props 时稍显麻烦,需要在外面创建定义了props的实例,然后让组件继承它。

可以使用 vue-property-decorator 的 @Prop 装饰器更方便的在类内部定义。

装饰器

装饰器 是 ES 草案中的一个新特性,提供一种更好的面向切面编程(AOP)的体验,不过这个草案最近(当前2020年)有可能发生重大调整,所以暂不推荐使用。

Class APIs + vue-property-decorator

vue-property-decorator 不是Vue 官方的库,完全依赖于vue-class-component

在 vue-class-component 的基础上扩展,并且提供了很多装饰器比如 @Prop@Watch等,可以更方便的使用 class 的方式定义组件。

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

@Component
export default class Property extends Vue {
  @Prop(String) readonly text: string | undefined

  msg = 'This is ' + this.text
}

总结

推荐使用 Options APIs 方式。

  • 上面几种方式实现的结果都是一样的。
  • Vue 本身不强制使用 class 方式定义组件选项,使用 Options APIs 更简单。
  • class 方式定义的实例成员都在类的内部直接定义,Options APIs 中定义选项,是分类放置,看起来更清晰更集中
    • 变量放在 data 中
    • 方法放在 methods 中
    • 计算属性放在 computed 中
  • class 的方式需要用到装饰器(@Component)
    • 装饰器语法目前还不稳定,目前还没有正式发版(当前2020年),草案变动也很大
    • 所以,建议正式发版后使用,当前不建议使用装饰器结合类的方式定义组件。
  • 其实 Vue.js 3.0 早期是想要放弃 Class APIs 的,不过无奈想要兼容,所以才保留下来了。

RealWorld

介绍

RealWorld 用于了解如何使用我们支持的任何前端技术(React,Vue等)和后端技术(Node,Go等)构建完全相同的案例(Medium.com,称为Conduit))。

它是个万能案例,提供了页面模板、免费的遵循相同的API规范的数据接口。

学习框架或技术后,通过实现 RealWorld 案例的实践,加深对学习内容的了解。

使用方法

  • 入门指南和规格

  • Fork our starter kit - Fork RealWorld 的入门模板

  • FRONTEND_INSTRUCTIONS - 前端使用说明

    • 数据接口
      • 接口基础路径: https://conduit.productionready.io/api
      • 接口文档
    • 路由
    • 样式
    • 布局 HTML 模板
      • Header 头部
      • Footer 尾部
    • 页面 HTML 模板
      • Home 首页
      • Login/Register 登录注册页面
      • Profile 用户页面
      • Settings 设置页面
      • Create/Edit Article 创建编辑文章页面
      • Article 文章详情页面
  • 然后手动创建这些HTML模板的文件,调用提供的接口,实现案例中的功能。

体验 TypeScript

使用 RealWorld 模板进行初始配置

先将 RealWorld 的模板复制到页面中:

  • /public/indexhtml 中复制 Header 中的样式引入 link
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    
    <!-- RealWorld -->
    <!-- Import Ionicon icons & Google Fonts our Bootstrap theme relies on -->
    <link href="//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css">
    <link href="//fonts.googleapis.com/css?family=Titillium+Web:700|Source+Serif+Pro:400,700|Merriweather+Sans:400,700|Source+Sans+Pro:400,300,600,700,300italic,400italic,600italic,700italic" rel="stylesheet" type="text/css">
    <!-- Import the custom Bootstrap 4 theme from our hosted CDN -->
    <link rel="stylesheet" href="//demo.productionready.io/main.css">
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

  • views 创建 layout/index.vue,内部定义布局组件
    • script 使用 ts 编写
    • 复制 Header 中的 nav 头部导航栏
    • 中间是子路由出口 router-view
    • 复制 Footer 模板内容
<template>
  <div>
    <!-- 头部导航栏 -->
    <nav class="navbar navbar-light">
      <div class="container">
        <a class="navbar-brand" href="index.html">conduit</a>
        <ul class="nav navbar-nav pull-xs-right">
          <li class="nav-item">
            <!-- Add "active" class when you're on that page" -->
            <a class="nav-link active" href="">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="">
              <i class="ion-compose"></i>&nbsp;New Post
            </a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="">
              <i class="ion-gear-a"></i>&nbsp;Settings
            </a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="">Sign up</a>
          </li>
        </ul>
      </div>
    </nav>

    <!-- 子路由出口 -->
    <router-view />

    <!-- 底部内容 -->
    <footer>
      <div class="container">
        <a href="/" class="logo-font">conduit</a>
        <span class="attribution">
          An interactive learning project from
          <a href="https://thinkster.io">Thinkster</a>. Code &amp; design
          licensed under MIT.
        </span>
      </div>
    </footer>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  name: 'layout'
})
</script>

<style></style>

创建首页、注册/登录页,同layout类似。

清空 App.vue:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<style></style>

修改路由配置:

import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
  {
    path: '/',
    component: () => import('@/views/layout/index.vue'),
    children: [
      {
        path: '', // 默认子路由
        name: 'home',
        component: () => import('@/views/home/index.vue')
      },
      {
        path: 'login',
        name: 'login',
        component: () => import('@/views/login/index.vue')
      }
    ]
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

现在可以运行查看效果:npm run serve

vscode 自定义代码段

vscode 提供代码段快速创建模板。

vetur 提供了vue的自定义代码段,但是当前使用TS,每次还要修改 script 标签及内容。

创建使用 TS 的 Vue 模板代码段:

  • F1 显示所有命令,搜索 snippets(首选项:配置用户代码片段)
  • 选择对应的语言(Vue),打开了vue.json
{
  // [key]:代码段的名称 - 每个代码段都在名称下定义
	// prefix:前缀 - 触发代码段的入口,使用时输入它会有智能感知提示
	// body:正文 - 代码段的主体,需要设置的代码放在这里
	//       字符串之间换行使用\r\n换行符隔开
	//       多行语句也可以使用字符串数组方式
	//       注意:如果值里包含特殊字符需要进行转义
	// $0:代码段插入后,光标的所在位置
	// descrition:代码段描述 - 使用智能感知时的描述
	
	".vue for ts": {
		"prefix": "<vue> with ts",
		"body": [
			"<template>",
			"  <div>",
			"    ${0}",
			"  </div>",
			"</template>",
			"<script lang=\"ts\">",
			"import Vue from 'vue'",
			"export default Vue.extend({",
			"\r",
			"})",
			"</script>",
			"\r",
			"<style></style>",
		],
		"description": "使用TS语法"
	}
}

在这里插入图片描述

使用 TS 实现案例的注册功能

编写请求方法

安装HTTP请求依赖 npm i axios

简单封装一下 axios:

// src/utils/request.ts
import axios from 'axios'

// 创建 axios 实例
const request = axios.create({
  // 接口基础路径
  baseURL: 'https://conduit.productionready.io/api'
})

export default request

封装用户认证相关接口请求接口的方法,这里开始使用 TS:

一般情况下,用户注册方法接口的参数可以是接口的请求体(data),但是这样就可以传任何类型,包括不是接口期望接收的结构和类型。

这也是前后端联调时经常发生的情况:接口传参传错。

TypeScript 可以通过interface(接口)的方式定义数据类型。

  • 接口定义数据类型,{}不是对象,而是接口的语法
  • 接口名首字母大写
  • 接口的分隔符可以使用逗号或分号,也可以不使用分隔符
    • @typescript-eslint/member-delimiter-style 校验风格要求必须使用分隔符
    • 可以选择在ESLint配置中关闭这条规则,或者编写分割符
// src/api/auth.ts
/**
 * 用户认证相关接口模块
 */
import request from '@/utils/request'

// 用户注册接口传参
export interface User {
  user: {
    username: string
    email: string
    password: string
  }
}

// 用户注册
export const register = (data: User) => {
  request({
    method: 'POST',
    url: '/users',
    data: data
  })
}

效果:

  • 格式错误会智能提示
  • 输入的时候会自动提示接口中定义的字段

在这里插入图片描述

在这里插入图片描述

建议:

在第一次使用的地方定义 interface,也可以集中在一个文件中定义。

提供接口的导出(export),方便其他地方使用

继续扩展 register 的返回值。

一般情况,会将请求的接口直接返回,这样开发的时候需要开发者自己了解返回的数据结构,频繁的看接口文档,或查看实际请求结果。

为返回值添加类型声明,这样在使用时,就会有智能提示,减少很多工作。

// src/api/auth.ts
/**
 * 用户认证相关接口模块
 */
import request from '@/utils/request'

// 用户注册接口传参
export interface User {
  user: {
    username: string
    email: string
    password: string
  }
}

// 用户注册接口返参
export interface RegisterData {
  user: {
    email: string
    token: string
    username: string
    bio: string
    image: string
  }
}

// 用户注册
export const register = async (data: User): Promise<RegisterData> => {
  const res = await request({
    method: 'POST',
    url: '/users',
    data: data
  })
  return res.data
}


注意:

  • request 调用的是 axios ,它是异步请求,所以要获取 axios 请求结果并返回,需要使用 async/await
  • 而 TypeScript 要求 async 异步函数的返回类型必须是 Promise,当直接指定返回类型时会报错
  • 需要使用泛型,指定 Promise 类型的类型声明

错误示例:

在这里插入图片描述

最终使用效果(智能提示):

在这里插入图片描述

编写表单事件
  • data 中定义表单数据
  • 绑定表单的 submit 事件为自定义方法 onSubmit
    • 注意使用 prevent 阻止表单默认行为
  • 绑定表单中的数据 v-model
  • 导入 register 方法
  • 编写 onSubmit 方法
<template>
  <div class="auth-page">
    <div class="container page">
      <div class="row">
        <div class="col-md-6 offset-md-3 col-xs-12">
          <h1 class="text-xs-center">Sign up</h1>
          <p class="text-xs-center">
            <a href="">Have an account?</a>
          </p>

          <ul class="error-messages">
            <li>That email is already taken</li>
          </ul>

          <form @submit.prevent="onSubmit">
            <fieldset class="form-group">
              <input
                v-model="user.username"
                class="form-control form-control-lg"
                type="text"
                placeholder="Your Name"
              />
            </fieldset>
            <fieldset class="form-group">
              <input
                v-model="user.email"
                class="form-control form-control-lg"
                type="text"
                placeholder="Email"
              />
            </fieldset>
            <fieldset class="form-group">
              <input
                v-model="user.password"
                class="form-control form-control-lg"
                type="password"
                placeholder="Password"
              />
            </fieldset>
            <button class="btn btn-lg btn-primary pull-xs-right">
              Sign up
            </button>
          </form>
        </div>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
import { register } from '@/api/auth.ts'
export default Vue.extend({
  name: 'login',
  data() {
    return {
      user: {
        username: 'aaasfgdfghfdaaaaa',
        email: 'aaasdsda@bfhrtgergb.com',
        password: '1234abcd'
      }
    }
  },
  methods: {
    async onSubmit() {
      // 获取表单数据
      // 表单验证
      // 提交表单
      const data = await register({
        user: this.user
      })
      console.log(data)
      // 根据返回结果处理后续操作
      this.$router.push({
        name: 'home'
      })
    }
  }
})
</script>

<style></style>

RealWorld 注册接口使用注意:

  • 接口会校验参数格式,校验失败会返回422。
  • 这是真实注册,注册过的账号不能重复使用,会报 has already been taken,也会返回422,可以用写登录多次尝试接口。
Logo

前往低代码交流专区

更多推荐