首页 前端知识 前端学习笔记(Vue、TypeScript、JavaScript)

前端学习笔记(Vue、TypeScript、JavaScript)

2024-09-01 23:09:07 前端知识 前端哥 195 378 我要收藏

学习视频:

  • java开发所需的前端技术全教程

推荐资料:

  • JavaScript 教程 | 菜鸟教程 (runoob.com)

文章目录

  • HTML
    • 元素
      • 文本
        • 1、标题 Heading
        • 2、段落 Paragraph
        • 3、列表 List
        • 4、超链接 Anchor
      • 多媒体
        • 1、Image
        • 2、Video
        • 3、Audio
      • 表单
        • 常见的表单项
        • 数据格式小结
  • CSS
      • 1、选择器
      • 2、属性和值
      • 3、布局
  • JavaScript
    • 变量与数据类型
      • 声明变量
        • let
        • const
        • var
      • 基本类型
        • undefined 和 null
        • string
        • number 和 bigint
        • boolean
      • 对象类型
        • Function 函数
          • 定义函数
          • 默认参数
          • 匿名函数
          • 箭头函数
          • 函数是对象
          • 函数作用域
          • 闭包
        • Object
          • 语法
          • JSON
      • 动态类型
    • 运算符与表达式
      • 1) ===
      • 2) ||
      • 3) ?? 与 ?.
        • ??
        • ?.
      • 4) ...
      • 5) [] {}
        • []
        • {}
    • 控制语句
      • for in
      • for of
    • API 前端案例
        • 搭建前端服务器
        • 一、查找元素
  • Vue 3
    • TypeScript
      • 动态类型的问题
      • 使用入门
      • 类型
        • 标注位置
          • 标注变量
          • 标注参数
          • 标注返回值
        • 复杂类型
          • type
          • interface
          • 可选属性
          • 鸭子类型
        • 方法类型
        • 字面量类型
        • nullish 类型
        • 泛型
        • 基本语法
        • 方法 & 只读属性
        • get,set
        • 类与接口
        • 继承与接口
        • 方法重写
    • Vue3 基础
      • 环境准备
        • 创建项目
        • 安装 devtools
        • 修改端口
        • 配置代理
        • 项目结构
      • Vue 组件
        • main.ts
        • ref 与 reactive
        • 属性绑定
        • 事件绑定
        • 表单绑定
        • 计算属性
        • xhr
        • axios
          • 基本用法
          • 环境变量
          • baseURL
          • 拦截器
        • 条件与列表
        • 监听器
        • vueuse
        • useRequest
        • usePagination
        • 子组件
    • Vue3 进阶
      • Ant-design-vue
        • 表格
        • 分页
        • 搜索、删除、新增、修改
        • 全局消息
        • 表单校验
    • vue-router
        • 安装
        • 创建 router
        • 动态导入
        • 嵌套路由
        • 重定向
      • pinia
        • 安装
        • 定义Store


HTML

使用 ! + Tab 键可生成 HTML 模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    
</body>
</html>
  • <!DOCTYPE html> 声明为 HTML5 文档
  • <html> 元素是 HTML 页面的根元素
  • <head> 元素包含了文档的元(meta)数据,如 <meta charset="utf-8"> 定义网页编码格式为 utf-8
  • <title> 元素描述了文档的标题
  • <body> 元素包含了可见的页面内容
  • <h1> 元素定义一个大标题
  • <p> 元素定义一个段落

元素

文本

1、标题 Heading
<h1>1号标题</h1>
<h2>2号标题</h2>
<h3>3号标题</h3>
<h4>4号标题</h4>
<h5>5号标题</h5>
<h6>6号标题</h6>
2、段落 Paragraph
<p>段落</p>
3、列表 List

无序列表 unordered list

<ul>
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
</ul>

有序列表

<ol>
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
</ol>

多级列表

<ul>
    <li>
        深圳市
        <ul>
            <li>南山区</li>
            <li>福田区</li>
            <li>龙岗区</li>
        </ul>
    </li>
    <li>
        广州市
        <ul>
            <li>番禺区</li>
        </ul>
    </li>
</ul>
4、超链接 Anchor
<a href="resource/page.html">本地网页</a>

多媒体

1、Image
<img src="文件路径">
2、Video
<video src="文件路径"></video>
3、Audio
<audio src="文件路径"></audio>

表单

表单的作用:收集用户填入的数据,并将这些数据提交给服务器

<form action="服务器地址" method="请求方式" enctype="数据格式">
    <!-- 表单项 -->
    <input type="text" name="uesrname">
    
    <input type="submit" value="提交按钮">
</form>
  • method 请求方式有:
    • get (默认)提交时,数据跟在 URL 地址之后
    • post 提交时,数据在请求体内
  • enctype 在 post 请求时,指定请求体的数据格式
    • application/x-www-form-urlencoded(默认)
    • multipart/form-data
  • 其中表单项提供多种收集数据的方式
    • 有 name 属性的表单项数据,才会被发送给服务器
常见的表单项

文本框

<input type="text" name="uesrname">

密码框

<input type="password" name="password">

隐藏框

<input type="hidden" name="id">

日期框

<input type="date" name="birthday">

单选(name 要一致)

<input type="radio" name="sex" value="" checked>
<input type="radio" name="sex" value="">

多选(name 要一致,后端用 List 接收)

<input type="checkbox" name="fav" value="唱歌">
<input type="checkbox" name="fav" value="逛街">
<input type="checkbox" name="fav" value="游戏">

文件上传

<input type="file" name="avatar">
数据格式小结

客户端发送

  • 编码
    • application/x-www-form-urlencoded :url 编码
    • application/json:utf-8 编码
    • multipart/form-data:每部分编码可以不同
  • 表单只支持以 application/x-www-form-urlencoded 和 multipart/form-data 格式发送数据
  • 文件上传需要用 multipart/form-data 格式
  • js 代码可以支持任意格式发送数据

服务端接收

  • 对 application/x-www-form-urlencoded 和 multipart/form-data 格式的数据,Spring 接收方式是统一的,只需要用 java bean 的属性名对应请求参数名即可
  • 对于 applicaiton/json 格式的数据,Spring 接收需要使用 @RequestBody 注解 + java bean 的方式

CSS

CSS 即 Cascading Style Sheets 层叠样式表,它描述了网页的表现与展示效果。

1、选择器

  • type 选择器:根据标签名进行匹配(元素选择器)

  • class 选择器:根据元素的 class 属性进行匹配

  • id 选择器:根据元素的 id 属性进行匹配

2、属性和值

  • background-color : red;
  • display

3、布局

与布局相关的 html 元素

  • div
  • template
/* 元素type选择器 */
p {
    background-color:aqua;
}

/* 元素class选择器 */
.c1 {
    background-color:cornflowerblue;
}

/* id选择器 */
#p3 {
    background-color:red;
}

JavaScript

它是一种脚本语言,可以用来更改页面内容,控制多媒体,制作图像、动画等等。

  1. js 代码位置
<script>
	// js 代码
</script>
  1. 引入 js 脚本
<script src="js脚本路径"></script>
  • 注意,到了框架之后,引入方式会有不同

变量与数据类型

声明变量

let
let 变量名 =;

let 声明的变量可以被多次赋值,例如

let a = 100;  // 初始值是 100
a = 200;	  // ok, 被重新赋值为 200
const

const 修饰的叫常量,只能赋值一次

const b = 300; // 初始值是 300
b = 400;	   // error, 不能再次赋值

const 并不意味着它引用的内容不可修改,例如

const c = [1,2,3];
c[2] = 4; 	        // ok, 数组内容被修改成 [1,2,4]
c = [5,6];			// error, 不能再次赋值
var

var 声明的变量可以被多次赋值,例如

var f = 100;
f = 200;

letvar 的区别体现在作用域不同

如果函数外层引用的是 let 变量,那么外层普通的 {} 也会作为作用域边界,最外层的 let 也占一个 script 作用域

let x = 10; 
if(true) {
    let y = 20;
    function b() {
        console.log(x,y);
    }
    console.dir(b);
}

如果函数外层引用的是 var 变量,外层普通的 {} 不会视为边界

var x = 10; 
if(true) {
    var y = 20;
    function b() {
        console.log(x,y);
    }
    console.dir(b);
}

如果 var 变量出现了重名,则他俩会被视为同一作用域中的同一个变量

var e = 10; 
if(true) {
    var e = 20;	 // 相当于 e = 20
    console.log(e);	// 打印 20
}
console.log(e);		// 因为是同一个变量,还是打印 20

如果是 let,则视为两个作用域中的两个变量

let e = 10; 
if(true) {
    let e = 20;	
    console.log(e);	// 打印 20
}
console.log(e);		// 打印 10

要想里面的 e 和外面的 e 能区分开来,最简单的办法是改成 let,或者用函数来界定作用域范围

var e = 10; 
if(true) {
    function b() {
        var e = 20;
    	console.log(e);
    }
    b();
}
console.log(e);	

基本类型

undefined 和 null
  • 执行表达式或函数,没有返回结果,出现 undefined
  • 访问数组不存在的元素,访问对象不存在的属性,出现 undefined
  • 定义变量,没有初始化,出现 undefined
console.log(1);  	// 函数没有返回值, 结果是  undefined
let a = 10;		 	// 表达式没有返回值, 结果是 undefined
let b = [1,2,3];
console.log(b[10]); // 数组未定义元素是 undefined
let c = {"name":"张三"};
console.log(c.age); // 对象未定义属性是 undefined
let d;
console.log(d);		// 变量未初始化是 undefined

二者共同点

  • 都没有属性、方法
  • 二者合称 Nullish

二者区别

  • undefined 由 js 产生
  • null 由程序员提供
string

js 字符串三种写法

let a = "hello";  // 双引号
let b = "world";  // 单引号
let c = `hello`;  // 反引号

html 代码如下,用 java 和 js 中的字符串如何表示?

<a href="1.html">超链接</a>

java 显得比较繁琐

String s1 = "<a href=\"1.html\">超链接</a>";

String s2 = """
    <a href="1.html">超链接</a>""";

js 就比较灵活

let s1 = '<a href="1.html">超链接</a>';

let s2 = `<a href="1.html">超链接</a>`;

模板字符串(Template strings)

需求:拼接 URI 的请求参数,如

/test?name=zhang&age=18
/test?name=li&age=20

传统方法拼接

let name = ; // zhang li ...
let age = ; // 18 20 ...

let uri = "/test?name=" + name + "&age=" + age;

模板字符串方式

let name = ; // zhang li ...
let age = ; // 18 20 ...

let uri = `/test?name=${name}&age=${age}`;
number 和 bigint

number 类型标识的是双精度浮动小数,例如

10 / 3;   // 结果 3.3333333333333335

既然是浮点小数,那么可以除零

10 / 0;	  // 结果 Infinity 正无穷大
-10 / 0;  // 结果 -Infinity 负无穷大

浮点小数都有运算精度问题,例如

2.0 - 1.1; // 结果 0.8999999999999999

字符串转数字

parseInt("10"); 	// 结果是数字 10 
parseInt("10.5");	// 结果是数字 10, 去除了小数部分
parseInt("10") / 3; // 结果仍视为 number 浮点数, 因此结果为 3.3333333333333335

parseInt("abc");	// 转换失败,结果是特殊值 NaN (Not a Number)

要表示真正的整数,需要用 bigint,数字的结尾用 n 表示它是一个 bigint 类型

10n / 3n;			// 结果 3n, 按整数除法处理
boolean
  • Truthy
  • Falsy

在 js 中,并不是 boolean 才能用于条件判断,你可以在 if 语句中使用【数字】、【字符串】… 作为判断条件

let b = 1;

if(b) { // true
    console.log("进入了");
}

这时就有一个规则,当需要条件判断时,这个值被当作 true 还是 false,当作 true 的值归类为 truthy,当作 false 的值归类为 falsy

下面值都是 falsy

  • false
  • Nullish (null, undefined)
  • 0, 0n, NaN
  • "" '' `` 即长度为零的字符串

剩余的值绝大部分都是 truthy

有几个容易被当作 falsy 实际是 truthy 的

  • "false", "0" 即字符串的 false 和 字符串的零
  • [] 空数组
  • {} 空对象

对象类型

Function 函数
定义函数
function 函数名(参数) {
    // 函数体
    return 结果;
}

js 中的函数调用特点:对参数的类型个数都没有限制,例如

add('a', 'b');  // 返回 ab
add(4, 5, 6);   // 返回 9, 第三个参数没有被用到, 不会报错
add(1);			// 返回 NaN, 这时 b 没有定义是 undefined, undefined 做数学运算结果就是 NaN
默认参数

java 中(spring)要实现默认参数的效果得这么做:

@RestController 
public class MyController {
    @RequestMapping("/page")
    @ResponseBody
    public void page(
        @RequestParam(defaultValue="1") int page, 
        @RequestParam(defaultValue="10") int size){
        // ...
    }
}

js

function pagination(page = 1, size = 10) {
    console.log(page, size);
}
匿名函数
(function (参数) {
    // 函数体
    return 结果;
})

第一种场景:定义完毕后立刻调用

(function(a,b){
    return a + b;
})(1,2)

第二种场景:作为其它对象的方法,例如

页面有元素

<p id="p1">点我啊</p>

此元素有一个 onclick 方法,会在鼠标单击这个元素后被执行,onclick 方法刚开始是 null,需要赋值后才能使用

document.getElementById("p1").onclick = (function(){
    console.log("鼠标单击了...");
});
箭头函数
(参数) => {
    // 函数体
    return 结果;
}
  • 如果没有参数,() 还是要保留
  • 如果只有一个参数,() 可以省略
  • 如果函数体内只有一行代码,{} 可以省略
  • 如果这一行代码就是结果,return 可以省略
document.getElementById("p1").onclick = () =>  console.log("aa");
函数是对象
  1. 可以参与赋值,例,具名函数也能参与赋值
function abc() {
    console.log("bb");
}

document.getElementById("p1").onclick = abc;
  1. 可以作为方法参数
function aa() {
    console.log('aa')
}

function bb(fn) {          // fn 将来可以是一个函数对象
    console.log('bb')
    fn();                 // 调用函数对象
}

bb(aa);
  1. 可以作为方法返回值
function c() {
    console.log("c");
    function d() {
        console.log("d");
    }
    return d;
}

c()()
函数作用域

函数可以嵌套(js 代码中很常见,只是嵌套的形式更多是匿名函数,箭头函数)

function a() {
    function b() {        
    }
}
  • 以函数为分界线划定作用域,所有函数之外是全局作用域
  • 查找变量时,由内向外查找
    • 在内层作用域找到变量,就会停止查找,不会再找外层
    • 所有作用域都找不到变量,报错
  • 作用域本质上是函数对象的属性,可以通过 console.dir 来查看调试
var x = 10;
function a() {
    var y = 20;
    function b() {
        console.log(x, y);
    }
    b();
}
a();
闭包
var x = 10;
function a() {
    var y = 20;
    function b() {
        console.log(x,y);
    }
    return b;
}
a()();  // 在外面执行了 b
  • 函数定义时,它的作用域已经确定好了,因此无论函数将来去了哪,都能从它的作用域中找到当时那些变量
  • 闭包就是指函数能够访问自己的作用域中变量
Object
语法
let obj = {
    属性名:,
    方法名: 函数,
    get 属性名() {},
    set 属性名(新值) {}
}

例1

let stu1 = {
    name: "小明",
    age: 18,
    study: function() {
        console.log(this.name + "爱学习");
    }    
}

例2

let name = "小黑";
let age = 20;
let study = function() {
    console.log(this.name + "爱学习");
}

let stu2 = { name, age, study }

例3(重点)

let stu3 = {
    name: "小白",
    age: 18,
    study() {
        console.log(this.name + "爱学习");
    }    
}
  • 注意:对象方法这么写,仅限于对象内部

例4

let stu4 = {
    _name: null, /*类似于java中私有成员变量*/
    get name() {
        console.log("进入了get");
        return this._name;
    },
    set name(name) {
        console.log("进入了set");
        this._name = name;
    }
}

调用 get,set

stu4.name = "小白";	// set
console.log(stu4.name); // get
JSON

一个 json 对象可以长这样:

{
    "name":"张三",
    "age":18
}

一个 js 对象长这样:

{
    name:"张三",
    age:18
}
  1. 本质不同
    • json 对象本质上是个字符串,它的职责是作为客户端和服务器之间传递数据的一种格式
    • js 对象是切切实实的对象,可以有属性方法
  2. 语法细节不同
    • json 中只能有 null、true|false、数字、字符串(只有双引号)、对象、数组
    • json 中不能有除以上的其它 js 对象的特性,如方法等
    • json 中的属性必须用双引号引起来

json 字符串与 js 对象的转换

JSON.parse(json字符串);  // 返回js对象
JSON.stringify(js对象);  // 返回json字符串

动态类型

静态类型语言,如 Java,值有类型,变量也有类型、赋值给变量时,类型要相符

int a = 10;
String b = "abc";

int c = "abc";  // 错误

而 js 属于动态类型语言,值有类型,但变量没有类型,赋值给变量时,没要求

let a = 200;

let b = 100;
b = 'abc';
b = true;

动态类型看起来比较灵活,但变量没有类型,会给后期维护带来困难,例如

function test(obj) {
    // obj 的类型未知,必须根据不同类型做出相应的容错处理
}

于是后续出现了 typescript。

运算符与表达式

  • + - * / % **
  • += -= *= /= %= **=
  • ++ --
  • 位运算、移位运算
  • == != > >= < <=
  • === !==
  • && || !
  • ?? ?.
  • ...
  • 解构赋值

1) ===

严格相等运算符,用作逻辑判等

1 == 1    	// 返回 true 
1 == '1'	// 返回 true,会先将右侧的字符串转为数字,再做比较
1 === '1'	// 返回 false,类型不等,直接返回 false

typeof 查看某个值的类型

typeof 1	// 返回 'number'
typeof '1'	// 返回 'string'

2) ||

需求,如果参数 n 没有传递,给它一个【男】

推荐做法

function test(n = '男') {
    console.log(n);
}

你可能的做法

function test(n) {
    if(n === undefined) {
        n = '男';
    }
    console.log(n);
}

还可能是这样

function test(n) {
    n = (n === undefined) ? '男' : n;
    console.log(n);
}

一些老旧代码中可能的做法(不推荐)

function test(n) {
    n = n || '男';
    console.log(n);
}

它的语法是

1 ||2

如果值1 是 Truthy,返回值1,如果值1 是 Falsy 返回值 2

3) ?? 与 ?.

??

需求,如果参数 n 没有传递或是 null,给它一个【男】

如果用传统办法

function test(n) {
    if(n === undefined || n === null) {
        n = '男';
    }
    console.log(n);
}

用 ??

function test(n) {
    n = n ?? '男';
    console.log(n);
}

语法

值1 ?? 值2
  • 值1 是 nullish,返回值2
  • 值1 不是 nullish,返回值1
?.

需求,函数参数是一个对象,可能包含有子属性

例如,参数可能是

let stu1 = {
    name:"张三",
    address: {
        city: '北京'
    }
};

let stu2 = {
    name:"李四"
}

let stu3 = {
    name:"李四",
    address: null
}

现在要访问子属性(有问题)

function test(stu) {
    console.log(stu.address.city)
}

现在希望当某个属性是 nullish 时,短路并返回 undefined,可以用 ?.

function test(stu) {
    console.log(stu.address?.city)
}

用传统办法

function test(stu) {
    if(stu.address === undefined || stu.address === null) {
        console.log(undefined);
        return;
    }
    console.log(stu.address.city)
}

4) …

展开运算符

作用1:打散数组,把元素传递给多个参数

let arr = [1,2,3];

function test(a,b,c) {
    console.log(a,b,c);
}

需求,把数组元素依次传递给函数参数

传统写法

test(arr[0],arr[1],arr[2]);		// 输出 1,2,3

展开运算符写法

test(...arr);					// 输出 1,2,3
  • 打散可以理解为【去掉了】数组外侧的中括号,只剩下数组元素

作用2:复制数组或对象

数组

let arr1 = [1,2,3];
let arr2 = [...arr1];		// 复制数组

对象

let obj1 = {name:'张三', age: 18};

let obj2 = {...obj1};		// 复制对象

注意:展开运算符复制属于浅拷贝,例如

let o1 = {name:'张三', address: {city: '北京'} }

let o2 = {...o1};

作用3:合并数组或对象

合并数组

let a1 = [1,2];
let a2 = [3,4];

let b1 = [...a1,...a2];		// 结果 [1,2,3,4]
let b2 = [...a2,5,...a1]	// 结果 [3,4,5,1,2]

合并对象

let o1 = {name:'张三'};
let o2 = {age:18};
let o3 = {name:'李四'};

let n1 = {...o1, ...o2};	// 结果 {name:'张三',age:18}

let n2 = {...o3, ...o2, ...o1}; // 结果{name:'李四',age:18}
  • 复制对象时出现同名属性,后面的会覆盖前面的

5) [] {}

解构赋值

[]

用在声明变量时

let arr = [1,2,3];

let [a, b, c] = arr;	// 结果 a=1, b=2, c=3

用在声明参数时

let arr = [1,2,3];

function test([a,b,c]) {
    console.log(a,b,c) 	// 结果 a=1, b=2, c=3
}

test(arr);				
{}

用在声明变量时

let obj = {name:"张三", age:18};

let {name,age} = obj;	// 结果 name=张三, age=18

用在声明参数时

let obj = {name:"张三", age:18};

function test({name, age}) {
    console.log(name, age); // 结果 name=张三, age=18
}

test(obj)

控制语句

for in

主要用来遍历对象

let father = {name:'张三', age:18, study:function(){}};

for(const n in father) {
    console.log(n);
}
  • 其中 const n 代表遍历出来的属性名
  • 注意1:方法名也能被遍历出来(它其实也算一种特殊属性)
  • 注意2:遍历子对象时,父对象的属性会跟着遍历出来
let son = Object.create(father);
son.sex = "男";

for(const n in son) {
    console.log(n);
}
  • 注意3:在 for in 内获取属性值,要使用 [] 语法,而不能用 . 语法
for(const n in son) {
    console.log(n, son[n]);
}

for of

主要用来遍历数组,也可以是其它可迭代对象,如 Map,Set 等

let a1 = [1,2,3];

for(const i of a1) {
    console.log(i);
}

let a2 = [
    {name:'张三', age:18},
    {name:'李四', age:20},
    {name:'王五', age:22}
];

for(const obj of a2) {
    console.log(obj.name, obj.age);
}

for(const {name,age} of a2) {
    console.log(name, age);
}

API 前端案例

搭建前端服务器

新建一个保存项目的 client 文件夹,进入文件夹执行

npm install express --save-dev

修改 package.json 文件

{
  "type": "module",
  "devDependencies": {
    "express": "^4.18.1"
  }
}
  • 其中 devDependencies 是 npm install --save-dev 添加的

编写 main.js 代码

import express from 'express'
const app = express()

app.use(express.static('./')) //设置静态资源路径
app.listen(7070)

执行 js 代码(运行前端服务器)

node main.js
一、查找元素
  • document.getElementById:根据 id 值查找一个元素;
  • [document|元素].querySelector:根据选择器查找第一个匹配元素;
  • [document|元素].querySelectorAll:根据选择器查找所有匹配元素;
<div>
    <div class="title">学生列表</div>
    <div class="thead">
        <div class="row bold">
            <div class="col">编号</div>
            <div class="col">姓名</div>
            <div class="col">性别</div>
            <div class="col">年龄</div>
        </div>
    </div>
    <div class="tbody">
        <div class="row">
            <div class="col">1</div>
            <div class="col">张三</div>
            <div class="col"></div>
            <div class="col">18</div>
        </div>
    </div>
</div>

执行

document.querySelector('.title'); // 找到 <div class="title">学生列表</div>

执行

document.querySelector('.col'); // 找到 <div class="col">编号</div>

执行

document.querySelectorAll('.col');

/*
  找到的是一个集合
  <div class="col">编号</div>
  <div class="col">姓名</div>
  <div class="col">性别</div>
  <div class="col">年龄</div>
  <div class="col">1</div>
  <div class="col">张三</div>
  <div class="col">男</div>
  <div class="col">18</div>
*/

执行

const thead = document.querySelector('.thead');

// 只在 thead 元素范围内找
thead.querySelectorAll('.col');

/*
  找到的是一个集合
  <div class="col">编号</div>
  <div class="col">姓名</div>
  <div class="col">性别</div>
  <div class="col">年龄</div>
*/

根据 id 属性查找既可以用

document.getElementById("id值")

也可以用

document.querySelector("#id值")

Vue 3

TypeScript

动态类型的问题

前面我们讲过 js 属于动态类型语言,例如

function test(obj) {    
}

obj 可能只是个字符串

test('hello, world')

obj 也有可能是个函数

test(()=>console.log('hello, world'))

obj 类型不确定,就给后期使用者带来了麻烦,一旦参数传不对,代码就崩溃了

  • 动态类型意味着:运行代码时才知道发生什么
  • 静态类型意味着:在代码运行前,就对它的行为做出预测

下面的 typescript 代码,就在代码运行前对参数加入了约束限制

//限制了参数只能做string那些事
function test(msg : string) {
    console.log(msg.toUpperCase());
}
//限制了参数只能做函数那些事
function test(msg : Function) {
  	msg()
}

使用入门

安装 typescript 编译器

npm install -g typescript

编写 ts 代码

function hello(msg: string) {
  	console.log(msg)
}

hello('hello,world')

执行 tsc 编译命令之前,先创建一个 tsconfig.json 文件,避免 “函数实现重复” 报错。

tsc --init

执行 tsc 编译命令

tsc xxx.ts

编译生成 js 代码,编译后进行了类型擦除

function hello(msg) {
    console.log(msg);
}
hello('hello,world');

再来一个例子,用 interface 定义用户类型

interface User {
  name: string,
  age: number
}

function test(u: User): void {
  console.log(u.name)
  console.log(u.age)
}

test({ name: 'zhangs', age: 18 })

编译后

function test(u) {
    console.log(u.name);
    console.log(u.age);
}
test({ name: 'zhangs', age: 18 });

可见,typescript 属于编译时实施类型检查(静态类型)的技术

类型

类型备注
字符串类型string
数字类型number
布尔类型boolean
数组类型number[],string[], boolean[] 依此类推
任意类型any相当于又回到了没有类型的时代
复杂类型type 与 interface
函数类型() => void对函数的参数和返回值进行说明
字面量类型“a”|“b”|“c”限制变量或参数的取值
nullish类型null 与 undefined
泛型<T><T extends 父类型>
标注位置
标注变量
let message: string = 'hello,world'

一般可以省略,因为可以根据后面的字面量推断出前面变量类型

let message = 'hello,world'
标注参数
function greet(name: string) {
    
}
标注返回值

一般也可以省略,因为可以根据返回值做类型推断

function add(a: number, b: number) : number {
    return a + b
}
复杂类型
type
type Cat = {
  name: string,
  age: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 错误: 缺少 age 属性
const c3: Cat = { name: '小黑', age: 1, sex: '公' } // 错误: 多出 sex 属性
interface
interface Cat {
  name: string,
  age: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 错误: 缺少 age 属性
const c3: Cat = { name: '小黑', age: 1, sex: '公' } // 错误: 多出 sex 属性
可选属性

如果需要某个属性可选,可以用下面的语法

interface Cat {
  name: string,
  age?: number
}

const c1: Cat = { name: '小白', age: 1 }
const c2: Cat = { name: '小花' }					  // 正确: age 属性可选

可选属性要注意处理 undefined 值

鸭子类型
interface Cat {
  name: string
}

function test(cat: Cat) {
  console.log(cat.name)
}

const c1 = { name: '小白', age: 1 } 
test(c1)

const c1 并没有声明类型为 Cat,但它与 Cat 类型有一样的属性,也可以被当作是 Cat 类型

方法类型
interface Api {
  foo(): void,
  bar(str: string): string
}

function test(api: Api) {
  api.foo()
  console.log(api.bar('hello'))
}

test({
  foo() { console.log('ok') },
  bar(str: string) { return str.toUpperCase() }
})
字面量类型
function printText(s: string, alignment: "left" | "right" | "center") {
  console.log(s, alignment)
}

printText('hello', 'left')
printText('hello', 'aaa') // 错误: 取值只能是 left | right | center
nullish 类型
function test(x?: string | null) {
  console.log(x?.toUpperCase())
}

test('aaa')
test(null)
test()

x?: string | null 表示可能是 undefined 或者是 string 或者是 null

泛型
interface Ref<T> {
  value: T
}

const r1: Ref<string> = { value: 'hello' }
const r2: Ref<number> = { value: 123 }
const r3: Ref<boolean> = { value: true }
  • 泛型的要点就是 <类型参数>,把【类型】也当作一个变化的要素,像参数一样传递过来,这样就可以派生出结构相似的新类型

函数定义也支持泛型

function ref<T>(n: T): Ref<T> {
  return { value: n }
}

const v1 = ref("hello"); 	// Ref<string>
const v2 = ref(123.3333);	// Ref<number>

v1.value.toLocaleLowerCase()
v2.value.toFixed(2)

关于 TypeScript 与 JavaScript 中的类语法不是重点,class 相关语法只是起到辅助作用,更重要的是前面讲的 interface

基本语法
class User {
    name: string;
    constructor(name: string) {
        this.name = name
    }
}

const u = new User('张三')

其实会被编译成这个样子(默认 --target=es3)

var User = /** @class */ (function () {
    function User(name) {
        this.name = name;
    }
    return User;
}());
var u = new User('张三');

所以 js 中的 class,并不等价于 java 中的 class,它还是基于原型实现的,原理参考第二章(036、037)

方法 & 只读属性

readonly 是 typescript 特有的,表示该属性只读

class User {
  readonly name: string;
  constructor(name: string) {
      this.name = name
  }
  study() {
    console.log(`[${this.name}]正在学习`)
  }
}

const u = new User('张三')
u.study()
get,set
class User {
  _name: string;
  constructor(name: string) {
    this._name = name
  }
  get name() {
    return this._name
  }
  set name(name: string) {
    this._name = name
  }
}

const u = new User('张三')
console.log(u.name)
u.name = '李四'
console.log(u.name)
  • 注意,需要在编译时加上 tsc --target es6 .\xxx.ts 选项
  • es6 等价于 es2015,再此之上还有 es2016 … es2022
类与接口
interface User {
  name: string
  study(course: string): void
}

class UserImpl implements User {
  name: string;
  constructor(name: string) {
    this.name = name
  }
  study(course: string) {
    console.log(`[${this.name}]正在学习[${course}]`)
  }
  foo() { }
}

const user: User = new UserImpl('张三')
user.study('Typescript')
user.foo() // 错误,必须是接口中定义的方法
继承与接口
interface Flyable {
  fly(): void
}

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name
  }
}

class Bird extends Animal implements Flyable {
  fly() {
    console.log(`${this.name}在飞翔`)
  }
}

const b: Flyable & Animal = new Bird("小花")
b.fly()
  • Flyable & Animal 表示变量是 flyable 类型,同时也是 Animal 类型
方法重写
class Father {
  study(): void {
    console.log(`father study`)
  }
}

class Son extends Father {  
  study(): void {
    super.study()
    console.log(`son study`)
  }
}

const f: Father = new Son()
f.study()

Vue3 基础

技术选型

  • Vue
    • 选项式 API 还是 组合式 API✔️
    • HTML 还是 单文件组件✔️
  • 语法
    • javascript 还是 typescript✔️
  • 构建工具
    • @vue/cli 还是 vite✔️
  • 路由
    • vue-router✔️
  • 共享存储
    • vuex 还是 pinia✔️
  • 视图组件
    • ElementUI 还是 Antdv✔️

环境准备

创建项目

采用 vite 作为前端项目的打包,构建工具

npm init vite@latest

按提示操作

cd 项目目录
npm install
npm run dev

访问 localhost:5173

安装 devtools

devtools 插件网址:https://devtools.vuejs.org/guide/installation.html

修改端口

打开项目根目录下 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 7070
  }
})

文档地址:配置 Vite {#configuring-vite} | Vite中文网 (vitejs.cn)

配置代理

为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理,同样是修改项目根目录下 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 7070,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
})
项目结构
index.html
package.json
tsconfig.json
vite.config.ts
├─public
└─src
    ├─assets
    ├─components
    ├─model
    ├─router
    ├─store
    └─views
  • index.html 为主页面
  • package.json npm 配置文件
  • tsconfig.json typescript 配置文件
  • vite.config.ts vite 配置文件
  • public 静态资源
  • src/components 可重用组件
  • src/model 模型定义
  • src/router 路由
  • src/store 共享存储
  • src/views 视图组件

Vue 组件

Vue 的组件文件以 .vue 结尾,每个组件由三部分组成

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

<template></template>

<style scoped></style>
  • script 代码部分,控制模板的数据来源和行为
  • template 模板部分,由它生成 html 代码
  • style 样式部分,一般不咋关心

根组件是 src/App.vue,先来个 Hello,world 例子

<script setup lang="ts">
import { ref } from "vue";
let msg = ref("hello"); // 把数据变成响应式的

function change() {
  msg.value = "world";
  console.log(msg);
}
</script>
<template>
  <h1>{{ msg }}</h1>
  <input type="button" value="修改msg" @click="change" />
</template>
  • {{msg}} 用来把一个变量绑定到页面上某个位置
  • 绑定的变量必须用 ref 函数来封装
    • ref 返回的是【响应式】数据,即数据一旦变化,页面展示也跟着变化
main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')
  • createApp 是创建一个 Vue 应用程序,它接收的参数 App 即之前我们看到的根组件
  • mount 就是把根组件生成的 html 代码片段【挂载】到 index.html 中 id 为 app 的 html 元素上
ref 与 reactive

vue 提供了两个函数,都可以将数据变为【响应式】的

<script setup lang="ts">
import { ref, reactive } from 'vue'
const msg = ref('Hello, World')
const user = reactive({ name: '张三' })
</script>

<template>
  <h2>{{msg}}</h2>
  <h2>{{user.name}}</h2>
</template>
  • ref 能将任意类型的数据变为【响应式】的
  • reactive 只能将对象类型变为【响应式】,对基本类型无效(例如 string,number,boolean)

还有一点不同:

<script setup lang="ts">
import { ref, reactive } from 'vue'
const u1 = ref({ name: '张三' })
const u2 = reactive({ name: '张三' })
function test() {
  console.log(u1.value)
  console.log(u2)
}
test()
</script>
<template>
  <h2>{{u1.name}}</h2>
  <h2>{{u2.name}}</h2>
</template>
  • 在 template 模板中使用 ref 包装的数据,直接写【变量名】就可以了
  • 但在代码中要使用 ref 包装的数据,必须用【变量名.value】才能访问到
属性绑定
<script setup lang="ts">
import { ref } from 'vue'
const path = ref('/src/assets/vue.svg')
</script>

<template>
  <img :src="path" alt="">		<!-- 加个冒号: -->
</template>
  • :属性名】用来将标签属性与【响应式】变量绑定
事件绑定
<!-- 事件绑定 -->
<script setup lang="ts">
import {ref} from 'vue'
const count = ref(0)
function inc() {
    count.value++;
}
function dec() {
    count.value--;
}
</script>
<template>
    <input type="button" value="+" @click="inc">
    <h2>{{ count }}</h2>
    <input type="button" value="-" @click="dec">
</template>
表单绑定
<script setup lang="ts">
import { ref } from "vue";
const user = ref({
  name:'张三',
  age:18,
  sex:'男',
  fav:['游泳','打球']
})
function saveUser() {
  console.log(user.value)
}
</script>

<template>
  <div class="outer">
    <div>
      <label for="">请输入姓名</label>
      <input type="text" v-model="user.name"/>
    </div>
    <div>
      <label for="">请输入年龄</label>
      <input type="text" v-model="user.age"/>
    </div>
    <div>
      <label for="">请选择性别</label>
      男 <input type="radio" value="男" v-model="user.sex"/> 
      女 <input type="radio" value="女" v-model="user.sex"/>
    </div>
    <div>
      <label for="">请选择爱好</label>
      游泳 <input type="checkbox" value="游泳" v-model="user.fav"/> 
      打球 <input type="checkbox" value="打球" v-model="user.fav"/> 
      健身 <input type="checkbox" value="健身" v-model="user.fav"/>
    </div>
    <div>
      <input type="button" value="保存" @click="saveUser">
    </div>
  </div>
</template>

<style scoped>
  div {
    margin-bottom: 8px;
  }
  .outer {
    width: 100%;
    position: relative;
    padding-left: 80px;
  }
  label {
    text-align: left;
    width: 100px;
    display: inline-block;
    position: absolute;
    left :0;
  }
</style>
  • v-model 实现双向绑定
    • javascript 数据可以同步到表单标签
    • 反过来用户在表单标签输入的新值也会同步到 javascript 这边
  • 双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
  • 复选框这种标签,双向绑定的 javascript 数据类型一般用数组
计算属性

有时在数据展示时要做简单的计算

<script setup lang="ts">
import { ref } from 'vue'
const firstName = ref('三')
const lastName = ref('张')
</script>

<template>
  <h2>{{lastName + firstName}}</h2>
  <h3>{{lastName + firstName}}</h3>
  <h4>{{lastName + firstName}}</h4>
</template>

看起来较为繁琐,可以用计算属性改进

<script setup lang="ts">
import { ref, computed } from 'vue'
const firstName = ref('三')
const lastName = ref('张')
const fullName = computed(() => {
  console.log('enter')
  return lastName.value + firstName.value
})
</script>

<template>
  <h2>{{fullName}}</h2>
  <h3>{{fullName}}</h3>
  <h4>{{fullName}}</h4>
</template>
  • fullName 即为计算属性,它具备缓存功能,即 firstName 和 lastName 的值发生了变化,才会重新计算
  • 如果用函数实现相同功能,则没有缓存功能
<script setup lang="ts">
import { ref } from 'vue'
const firstName = ref('三')
const lastName = ref('张')
function fullName() {
  console.log('enter')
  return lastName.value + firstName.value
}
</script>
  
<template>
  <h2>{{fullName()}}</h2>
  <h3>{{fullName()}}</h3>
  <h4>{{fullName()}}</h4>
</template>
xhr

浏览器中有两套 API 可以和后端交互,发送请求、接收响应,fetch api 前面我们已经介绍过了,另一套 api 是 xhr,基本用法如下

const xhr = new XMLHttpRequest();

//当响应返回时,会触发 onload 事件
xhr.onload = function() {
    //2.接收响应
    console.log(xhr.response)
}

//1.发请求
xhr.open('GET', "http://localhost:8080/api/students")
xhr.responseType = 'json'
xhr.send()

但这套 api 虽然功能强大,但比较老,不直接支持 Promise,因此有必要对其进行改造

function get(url: string) {
  return new Promise((resolve, reject)=>{
    const xhr = new XMLHttpRequest()
    xhr.onload = function() {
      if(xhr.status === 200){
        resolve(xhr.response)
      } else if(xhr.status === 404) {
        reject(xhr.response)
      } // 其它情况也需考虑,这里简化处理
    }
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.send()
  })
}
  • Promise 对象适合用来封装异步操作,并可以配合 await 一齐使用
  • Promise 在构造时,需要一个箭头函数,箭头函数有两个参数 resolve 和 reject
    • resolve 是异步操作成功时被调用,把成功的结果传递给它,最后会作为 await 的结果返回
    • reject 在异步操作失败时被调用,把失败的结果传递给它,最后在 catch 块被捉住
  • await 会一直等到 Promise 内调用了 resolve 或 reject 才会继续向下运行
axios
基本用法

axios 就是对 xhr api 的封装,手法与前面例子类似

安装依赖

npm install axios

一个简单的例子

<script setup lang="ts">
import axios from 'axios'
import {ref, onMounted} from 'vue'

const count = ref(0)
async function getData() {
    const resp = await axios.get('http://localhost:8080/source/getIndexInfo/2')
    count.value = resp.data.data.moduleList.length
}

onMounted(() => {   //页面组件加载完成后执行
    getData()
})
</script>

<template>
    <h2>数量为 {{ count }}</h2>
</template>

onMounted 指 vue 组件生成的 html 代码片段,挂载完毕后被执行


再来看一个 post 例子

<script setup lang="ts">
import { ref } from "vue";
import axios from "axios";

const student = ref({
  name: '',
  sex: '男',
  age: 18
})

async function addStudent() {
  console.log(student.value)
  //第二个参数是请求体数据,数据格式默认是json,后端使用@RequestBody接收
  const resp = await axios.post('/api/students', student.value)
  console.log(resp.data.data)
}
</script>

<template>
  <div>
    <div>
      <input type="text" placeholder="请输入姓名" v-model="student.name"/>
    </div>
    <div>
      <label for="">请选择性别</label>
      男 <input type="radio" value="男" v-model="student.sex"/> 
      女 <input type="radio" value="女" v-model="student.sex"/>
    </div>
    <div>
      <input type="number" placeholder="请输入年龄" v-model="student.age"/>
    </div>
    <div>
      <input type="button" value="添加" @click="addStudent"/>
    </div>
  </div>
</template>
<style scoped>
div {
  font-size: 14px;
}
</style>
环境变量
  • 开发环境下,联调的后端服务器地址是 http://localhost:8080
  • 上线改为生产环境后,后端服务器地址为 http://itheima.com

这就要求我们区分开发环境和生产环境,这件事交给构建工具 vite 来做

默认情况下,vite 支持上面两种环境,分别对应根目录下两个配置文件

  • .env.development - 开发环境
  • .env.production - 生产环境

针对以上需求,分别在两个文件中加入

VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'		//.env.development

VITE_BACKEND_API_BASE_URL = 'http://itheima.com'		//.env.production

然后在代码中使用 vite 给我们提供的特殊对象 import.meta.env,就可以获取到 VITE_BACKEND_API_BASE_URL 在不同环境下的值

import.meta.env.VITE_BACKEND_API_BASE_URL

默认情况下,不能智能提示自定义的环境变量,做如下配置:新增文件 src/env.d.ts 并添加如下内容

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_BACKEND_API_BASE_URL: string
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}
  • 参考文档地址 环境变量和模式 | Vite 官方中文文档 (vitejs.dev)
baseURL

可以自己创建一个 axios 对象,方便添加默认设置,新建文件 /src/api/request.ts

// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})

export default _axios

然后在其它组件中引用这个 ts 文件,例如 /src/views/E8.vue,就不用自己拼接路径前缀了

<script setup lang="ts">
import axios from '../api/request'	//导入自己写的axios

// ...
await axios.post('/api/students', ...)  	//无需写地址前缀  
</script>
拦截器
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
    baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})

// 请求拦截器
_axios.interceptors.request.use(
    (config)=>{     // 请求成功时的处理,统一添加请求头(如JWT)
        config.headers = {
            Authorization: 'aaa.bbb.ccc'
        }
        return config
    },
    (error)=>{      // 请求出错时的处理
        return Promise.reject(error)
    }
)
// 响应拦截器
_axios.interceptors.response.use(
    (response)=>{   // 状态码 2xx
        // 这里的code是自定义的错误码
        if(response.data.code === 200) {
          return response
        }     
        else if(response.data.code === 401) {       
          // 情况1
          return Promise.resolve({})
        }
        // ... 
    },
    (error)=>{      // 状态码 4xx,5xx
        console.error(error)    //统一异常处理
        if(error.response.status === 400) {
          // 情况1
        } else if(error.response.status === 401) {
          // 情况2
        } 
        // ...
        // return Promise.reject(error)
        return Promise.resolve({})
    }
)

export default _axios

处理响应时,又分成两种情况

  1. 后端返回的是标准响应状态码,这时会走响应拦截器第二个箭头函数,用 error.response.status 做分支判断
  2. 后端返回的响应状态码总是200,用自定义错误码表示出错,这时会走响应拦截器第一个箭头函数,用 response.data.code 做分支判断

另外

  • Promise.reject(error) 类似于将异常继续向上抛出,异常由调用者(Vue组件)来配合 try … catch 来处理
  • Promise.resolve({}) 表示错误已解决,返回一个空对象,调用者中接到这个空对象时,需要配合 ?. 来避免访问不存在的属性
条件与列表

首先,新增模型数据 '/src/model/Model8080.ts'

export interface Student {
  id: number;
  name: string;
  sex: string;
  age: number;
}

// 如果 spring 错误,返回的对象格式
export interface SpringError {
  timestamp: string,
  status: number,
  error: string,
  message: string,
  path: string
}

// 如果 spring 成功,返回 list 情况
export interface SpringList<T> {
  data: T[],
  message?: string,
  code: number
}

// 如果 spring 成功,返回 page 情况
export interface SpringPage<T> {
  data: { list: T[], total: number },
  message?: string,
  code: number
}

// 如果 spring 成功,返回 string 情况
export interface SpringString {
  data: string,
  message?: string,
  code: number
}

import { AxiosResponse } from 'axios'
export interface AxiosRespError extends AxiosResponse<SpringError> { }
export interface AxiosRespList<T> extends AxiosResponse<SpringList<T>> { }
export interface AxiosRespPage<T> extends AxiosResponse<SpringPage<T>> { }
export interface AxiosRespString extends AxiosResponse<SpringString> { }
  • AxiosRespPage 代表分页时的响应类型
  • AxiosRespList 代表返回集合时的响应类型
  • AxiosRespString 代表返回字符串时的响应类型
  • AxiosRespError 代表 Spring 出错时时的响应类型
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import axios from "../api/request";
import { Student, SpringList } from "../model/Model8080";

// 说明 students 数组类型为 Student[]
const students = ref<Student[]>([]);

async function getStudents() {
  // 说明 resp.data 类型是 SpringList<Student>
  const resp = await axios.get<SpringList<Student>>("/api/students");  
  console.log(resp.data.data);
  students.value = resp.data.data;
}

onMounted(() => getStudents());
</script>
<template>
  <div class="outer">
    <div class="title">学生列表</div>
    <div class="thead">
      <div class="row bold">
        <div class="col">编号</div>
        <div class="col">姓名</div>
        <div class="col">性别</div>
        <div class="col">年龄</div>
      </div>
    </div>
    <div class="tbody">
      <div v-if="students.length === 0">暂无数据</div>
      <template v-else>
        <div class="row" v-for="s of students" :key="s.id">
          <div class="col">{{ s.id }}</div>
          <div class="col">{{ s.name }}</div>
          <div class="col">{{ s.sex }}</div>
          <div class="col">{{ s.age }}</div>
        </div>
      </template>
    </div>
  </div>
</template>
<style scoped>
	......
</style>
  • 加入泛型是为了更好的提示
  • v-if 与 v-else 不能和 v-for 处于同一标签
  • template 标签还有一个用途,就是用它少生成一层真正 html 代码
  • 可以看到将结果封装为响应式数据还是比较繁琐的,后面会使用 useRequest 改进
监听器

利用监听器,可以在【响应式】的基础上添加一些副作用,把更多的东西变成【响应式的】

  • 原本只是数据变化 => 页面更新

  • watch 可以在数据变化时 => 其它更新

<template>
  <input type="text" v-model="name" />
</template>

<script setup lang="ts">
import { ref, watch } from "vue";
function useStorage(name: string) {
  const data = ref(sessionStorage.getItem(name) ?? "");
  // 参数1:要监听的数据
  // 参数2:数据变化时要执行的箭头函数
  watch(data, (newValue) => {
    sessionStorage.setItem(name, newValue);
  });
  return data;
}
const name = useStorage("name");
</script>
  • 名称为 useXXXX 的函数,作用是返回带扩展功能的【响应式】数据
  • localStorage 即使浏览器关闭,数据还在
  • sessionStorage 数据工作在浏览器活动期间
vueuse

安装

npm install @vueuse/core

一些函数的用法

<template>
  <h3>X: {{x}}</h3>
  <h3>Y: {{y}}</h3>

  <h3>{{count}}</h3>
  <input type="button" @click="inc()" value="+">
  <input type="button" @click="dec()" value="-">

  <input type="text" v-model="name">
</template>

<script setup lang="ts">
import { useMouse, useCounter, useStorage } from '@vueuse/core'

const {x, y} = useMouse()	//获取鼠标坐标

const {count, inc, dec} = useCounter()	//使用计数器

const name = useStorage("name", "")		//使用存储
</script>
useRequest

响应式的 axios 封装,官网地址 一个 Vue 请求库 | VueRequest (attojs.org)

首先安装 vue-request

npm install vue-request@next

组件使用

<template>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
</template>

<script setup lang="ts">
import axios from "../api/request"
import { useRequest } from 'vue-request'
import { computed } from 'vue'
import { AxiosRespList, Student } from '../model/Model8080'

// data 代表就是 axios 的响应对象
const { data } = useRequest<AxiosRespList<Student>>(() => axios.get('/api/students'))

const students = computed(()=>{
  return data?.value?.data.data || []
})
</script>
<style scoped>
	......
</style>
  • data.value 的取值一开始是 undefined,随着响应返回变成 axios 的响应对象
  • 用 computed 进行适配
usePagination

在 src/model/Model8080.ts 中补充类型说明

export interface StudentQueryDto {
  name?: string,
  sex?: string,
  age?: string, // 18,20
  page: number,
  size: number
}
  • js 中类似于 18,20 这样以逗号分隔字符串,会在 get 传参时转换为 java 中的整数数组

编写组件

<template>
  <input type="text" placeholder="请输入姓名" v-model="dto.name">
  <select v-model="dto.sex">
    <option value="" selected>请选择性别</option>
    <option value="男">男</option>
    <option value="女">女</option>
  </select>
  <input type="text" placeholder="请输入年龄范围" v-model="dto.age">
  <br>
  <input type="text" placeholder="请输入页码" v-model="dto.page">
  <input type="text" placeholder="请输入页大小" v-model="dto.size">
  <input type="button" value="搜索" @click="search">
  <hr>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
  <hr>
  总记录数{{total}} 总页数{{totalPage}}
</template>
<script setup lang="ts">
import axios from "../api/request"
import { usePagination } from 'vue-request'
import { computed, ref } from 'vue'
import { AxiosRespPage, Student, StudentQueryDto } from '../model/Model8080'

const dto = ref<StudentQueryDto>({name:'', sex:'', age:'', page:1, size:5})

// data 代表就是 axios 的响应对象
// 泛型参数1: 响应类型
// 泛型参数2: 请求类型
const { data, total, totalPage, run } = usePagination<AxiosRespPage<Student>, StudentQueryDto[]>(
  (d) => axios.get('/api/students/q', {params: d}), // 箭头函数
  {
    defaultParams: [ dto.value ], // 默认参数, 会作为参数传递给上面的箭头函数
    pagination: {
      currentKey: 'page', // 指明当前页属性
      pageSizeKey: 'size', // 指明页大小属性
      totalKey: 'data.data.total' // 指明总记录数属性
    } 
  } // 选项
)

const students = computed(()=>{
  return data?.value?.data.data.list || []
})

function search() {
  run(dto.value) // 会作为参数传递给usePagination的箭头函数
}
</script>
<style scoped>
	......
</style>
  • usePagination 只需要定义一次,后续还想用它内部的 axios 发请求,只需调用 run 函数
子组件

子组件可以用于抽取公共组件。

定义子组件 Child1

<template>
  <div class="container">
    <div class="card">
      <div>
        <p class="name">{{name}}</p>
        <p class="location">{{country}}</p>
      </div>
      <img :src="avatar || '/src/assets/vue.svg'"/>
    </div>
  </div>
</template>
<script setup lang="ts">
// 定义属性,  编译宏
defineProps<{name:string,country:string,avatar?:string}>()
</script>
<style scoped>
	......
</style>

父组件引用

<template>
  <Child1 name="张三" country="中国" avatar="/src/assets/vue.svg"></Child1>
  <Child1 name="李四" country="印度" avatar="/vite.svg"></Child1>
  <Child1 name="王五" country="韩国" ></Child1>
</template>
<script lang="ts" setup>
import Child1 from '../components/Child1.vue';
</script>

通用型代码

<!-- 父组件 -->
<template>
  <Child1 v-for="u of users" 
    :name="u.name.first" 
    :country="u.location.country" 
    :avatar="u.picture.medium"
    :key="u.login.username"></Child1>
</template>
<script setup lang="ts">
import axios from "axios";
import { useRequest } from "vue-request";
import { computed } from "vue";
import { AxiosRespResults } from '../model/ModelRandomUser'
import Child1 from "../components/Child1.vue";

const { data } = useRequest<AxiosRespResults>(
  ()=>axios.get('https://randomuser.me/api/?results=3')
)

const users = computed(()=>{
  return data.value?.data.results || []
})
</script>

Vue3 进阶

组件总览 - Ant Design Vue (antdv.com)

Ant-design-vue

添加必要插件

npm install ant-design-vue
  • ant-design-vue 组件库插件

引入 antdv 功能,修改 main.ts(全局导入)

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

import antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'

createApp(App).use(antd).mount('#app')
表格
<template>
    <a-table :columns="colums" :dataSource="studens" rowKey="id"></a-table>
</template>

<script setup lang="ts">
import axios from "../api/request";
import { ref, computed } from "vue";
import { useRequest } from "vue-request";
import { AxiosRespList, Student } from "../model/Model8080";

const {data} = useRequest<AxiosRespList<Student>>(
    () => {
        return axios.get('/api/students')
    }
)

const studens = computed(()=>{
    return data.value?.data.data || []
})

const colums = ref([
    {
        title: "编号",
        dataIndex: "id"
    },
    {
        title: "姓名",
        dataIndex: "name"
    },
    {
        title: "性别",
        dataIndex: "sex"
    },
    {
        title: "年龄",
        dataIndex: "age"
    }
])
</script>
分页
<template>
    <a-table :columns="colums" :dataSource="studens" rowKey="id" 
    :pagination="pagination" @change="tableChange"></a-table>
</template>

<script setup lang="ts">
import axios from "../api/request";
import { ref, computed } from "vue";
import { usePagination } from "vue-request";
import { AxiosRespPage, Student, StudentQueryDto } from "../model/Model8080";
import { PaginationProps } from "ant-design-vue";

const dto = ref({page: 1, size: 5})

const {data, total, run} = usePagination<AxiosRespPage<Student>, StudentQueryDto[]>(
    (d) => axios.get('/api/students/query', {params:d}),
    {
        defaultParams: [dto.value],
        pagination: {
            currentKey: "page",
            pageSizeKey: "size",
            totalKey: "data.data.total"
        }
    }
)

//在页号或页大小改变时调用
function tableChange(paginationProps: PaginationProps) {
    console.log(paginationProps)
    dto.value.page = paginationProps.current ?? 1
    dto.value.size = paginationProps.pageSize ?? 5
    run(dto.value)
}

//分页选项
const pagination = computed<PaginationProps>(()=>{
    return {
        current: dto.value.page,    //当前页
        pageSize: dto.value.size,   //页大小
        total: total.value,         //总记录数
        showSizeChanger: true,      //展示页大小设置框
        pageSizeOptions: ["3", "5", "10", "20", "50", "100"]    //自定义页大小
    }
})

const studens = computed(()=>{
    return data.value?.data.data.list || []
})

const colums = ref([
    {
        title: "编号",
        dataIndex: "id"
    },
    {
        title: "姓名",
        dataIndex: "name"
    },
    {
        title: "性别",
        dataIndex: "sex"
    },
    {
        title: "年龄",
        dataIndex: "age"
    }
])
</script>
搜索、删除、新增、修改
<template>
    <a-row>
        <a-col :span="2">
            <a-button type="primary" size="small" @click="onInsert">新增</a-button>
        </a-col>
        <a-col :span="2">
            <a-popconfirm title="确认要删除选中学生吗?"
                ok-text="确定" cancel-text="取消" @confirm="onDeleteIds"
                @visibleChange="onVisibleChange" :visible="visible">
                <a-button type="primary" size="small">删除选中</a-button>
            </a-popconfirm>
        </a-col>
        <a-col :span="6"></a-col>
        <a-col :span="4">
            <!-- 内容跟普通的HTML标签绑定,直接用v-model -->
            <!-- 内容跟vue组件绑定,要使用v-model:value -->
            <a-input placeholder="输姓名" size="small" v-model:value="dto.name"></a-input>
        </a-col>
        <a-col :span="4">
            <a-select placeholder="选性别" :allowClear="true" size="small" v-model:value="dto.sex">
                <a-select-option value="男">男</a-select-option>
                <a-select-option value="女">女</a-select-option>
            </a-select>
        </a-col>
        <a-col :span="4">
            <a-select placeholder="选年龄" :allowClear="true" size="small" v-model:value="dto.age">
                <a-select-option value="0,20">20以下</a-select-option>
                <a-select-option value="21,30">21~30</a-select-option>
                <a-select-option value="31,40">31~40</a-select-option>
                <a-select-option value="40,120">40以上</a-select-option>
            </a-select>
        </a-col>
        <a-col :span="2">
            <a-button @click="tableChange" type="primary" size="small">搜索</a-button>
        </a-col>
    </a-row>
    <hr/>
    <a-table :columns="colums" :dataSource="studens" rowKey="id" 
        :pagination="pagination" @change="tableChange"
        :row-selection="{selectedRowKeys:ids, onChange:onSelectChange}">
        <template #bodyCell="{column, record}">
            <template v-if="column.dataIndex === 'name'">
                {{ record.name + (record.sex === '男' ? '(大侠)' : '(女侠)') }}
            </template>
            <template v-else-if="column.dataIndex === 'operation'">
                <a @click="onUpdate(record)">修改</a>
                <a-divider type="vertical"></a-divider>
                <a-popconfirm title="确认要删除该学生吗?" ok-text="确定" cancel-text="取消" 
                    @confirm="onDelete(record.id)">
                    <a>删除</a>
                </a-popconfirm>
            </template>
        </template>
    </a-table>

    <!-- <A4Save :id="id" :dto="saveDto" :visible="saveVisible"></A4Save> -->
    <a-modal :visible="saveVisible" :title="title" @ok="onOk" @cancel="onCancel">
        <a-form>
        <a-form-item label="编号" v-if="id">
            <a-input readonly v-bind:value="id"></a-input>
        </a-form-item>
        <a-form-item label="姓名">
            <a-input v-model:value="saveDto.name"></a-input>
        </a-form-item>
        <a-form-item label="性别">
            <a-radio-group v-model:value="saveDto.sex">
            <a-radio-button value="男">男</a-radio-button>
            <a-radio-button value="女">女</a-radio-button>
            </a-radio-group>
        </a-form-item>
        <a-form-item label="年龄">
            <a-input-number v-model:value="saveDto.age"></a-input-number>
        </a-form-item>
        </a-form>
    </a-modal>
</template>

<script setup lang="ts">
import axios from "../api/request";
import { ref, computed } from "vue";
import { usePagination, useRequest } from "vue-request";
import { AxiosRespPage, Student, StudentQueryDto, AxiosRespString, StudentSaveDto } from "../model/Model8080";
import { PaginationProps } from "ant-design-vue";
import A4Save from "./A4Save.vue";

const dto = ref({page: 1, size: 5, name: "", sex: null, age: null})

//====================新增修改功能======================
const id = ref(0)
const saveDto = ref({name:'', sex:'男', age:20})
const saveVisible = ref(false)
const title = ref('新增学生')

const {runAsync:insert} = useRequest<AxiosRespString, StudentSaveDto[]>(
    (insertDto) => axios.post('/api/students', insertDto), 
    {
        manual: true
    }
)

const {runAsync:update} = useRequest<AxiosRespString, StudentSaveDto[]>(
    (updateDto) => axios.put(`/api/students/${id.value}`, updateDto), 
    {
        manual: true
    }
)

function onInsert() {
    //恢复初始值
    id.value = 0
    saveDto.value = {name:'', sex:'男', age:20}
    title.value = '新增学生'
    saveVisible.value = true
}

function onUpdate(record: Student) {
    saveVisible.value = true
    id.value = record.id
    // saveDto.value.name = record.name
    // saveDto.value.sex = record.sex
    // saveDto.value.age = record.age
    Object.assign(saveDto.value, record)
    title.value = '修改学生'
}
async function onOk() {
    saveVisible.value = false
    if (id.value === 0) {
        await insert(saveDto.value)
    } else {
        await update(saveDto.value)
    }
    search(dto.value)
}
function onCancel() {
    saveVisible.value = false
}

//==================搜索功能============================
//默认组件加载时就会执行一次
const {data, total, run:search} = usePagination<AxiosRespPage<Student>, StudentQueryDto[]>(
    (d) => axios.get('/api/students/query', {params:d}),
    {
        defaultParams: [dto.value],
        pagination: {
            currentKey: "page",
            pageSizeKey: "size",
            totalKey: "data.data.total"
        }
    }
)

//在页号或页大小改变时调用
function tableChange(paginationProps: PaginationProps) {
    console.log(paginationProps)
    dto.value.page = paginationProps.current ?? 1
    dto.value.size = paginationProps.pageSize ?? 5
    search(dto.value)
}

//分页选项
const pagination = computed<PaginationProps>(()=>{
    return {
        current: dto.value.page,    //当前页
        pageSize: dto.value.size,   //页大小
        total: total.value,         //总记录数
        showSizeChanger: true,      //展示页大小设置框
        pageSizeOptions: ["3", "5", "10", "20", "50", "100"]    //自定义页大小
    }
})

const studens = computed(()=>{
    return data.value?.data.data.list || []
})

//==========================删除功能=========================
async function onDelete(id:number) {
    await deleteById(id)        //使用await使得deleteById方法响应之后再执行search方法
    search(dto.value)
}
const {runAsync:deleteById} = useRequest<AxiosRespString, number[]>(
    (id) => axios.delete(`/api/students/${id}`),
    {
        manual: true, //表示手动调用请求,而不是组件加载就执行一次
    }
)

//======================删除选中功能===========================
const ids = ref<number[]>([])

function onSelectChange(keys:number[]) {
    console.log(keys)
    ids.value = keys
}

async function onDeleteIds() {
    await deleteByIds(ids.value)
    ids.value = []
    search(dto.value)
}

const {runAsync:deleteByIds} = useRequest<AxiosRespString, number[][]>(
    (ids) => axios.delete('/api/students', {data:ids}),  //data对应着请求体中的数据
    {
        manual: true
    }
)

const visible = ref(false)

function onVisibleChange(v:boolean) {
  if(!v) { // 希望隐藏
    visible.value = false
  } else { // 希望显示
    visible.value = ids.value.length > 0
  }
}
//==============================================================
const colums = ref([
    {
        title: "编号",
        dataIndex: "id"
    },
    {
        title: "姓名",
        dataIndex: "name"
    },
    {
        title: "性别",
        dataIndex: "sex"
    },
    {
        title: "年龄",
        dataIndex: "age"
    },
    {
        title: "操作",
        dataIndex: "operation"
    }
])
</script>
全局消息

在 request.ts 中对响应消息统一处理

import { message } from 'ant-design-vue'

// ...
// 响应拦截器
_axios.interceptors.response.use(
  (response)=>{ // 状态码  2xx
    if(response.data.message) {
      message.success(response.data.message, 3)
    }
    // ... 
  },
  (error)=>{ // 状态码 > 2xx, 400,401,403,404,500
    // ...
  }
)
表单校验
<template>
  <a-modal :visible="visible" :title="title" @ok="onOk" @cancel="onCancel">
    <a-form>
      <a-form-item label="编号" v-if="id">
        <a-input readonly v-model:value="id"></a-input>
      </a-form-item>
      <a-form-item label="姓名" v-bind="validateInfos.name">	<!-- 绑定错误校验信息 -->
        <a-input v-model:value="dto.name"></a-input>
      </a-form-item>
      <a-form-item label="性别" v-bind="validateInfos.sex">
        <a-radio-group v-model:value="dto.sex">
          <a-radio-button value="男">男</a-radio-button>
          <a-radio-button value="女">女</a-radio-button>
        </a-radio-group>
      </a-form-item>
      <a-form-item label="年龄" v-bind="validateInfos.age">
        <a-input-number v-model:value="dto.age"></a-input-number>
      </a-form-item>
    </a-form>
  </a-modal>
</template>
<script setup lang="ts">
import axios from "../api/request";
import { ref, computed } from "vue";
import { useRequest } from "vue-request";
import { StudentSaveDto, AxiosRespString } from "../model/Model8080";
import { Form } from 'ant-design-vue'

//......

async function onOk() {
    try {
        //先进行数据校验
        await validate()

        saveVisible.value = false
        if (id.value === 0) {
            await insert(saveDto.value)
        } else {
            await update(saveDto.value)
        }
        search(dto.value)
    } catch(e) {
        console.error(e)
    }    
}

//======================数据校验============================
const rules = ref({
    name: [
        {required: true, message: "姓名不能为空"},
        {min: 2, message: "字符数至少为2"}
    ],
    sex: [
        {required: true, message: "性别不能为空"}
    ],
    age: [
        {required: true, message: "年龄不能为空"},
        {min: 10, message: "年龄最小为10岁", type: "number"},
        {max: 120, message: "年龄最大为120岁", type: "number"}
    ]
})
// 参数1: 待校验的数据
// 参数2: 校验规则
const { validateInfos, validate } = Form.useForm(saveDto, rules)
</script>

vue-router

安装
npm install vue-router@4
创建 router

首先创建一个 /src/router/a5router.ts 文件,在其中定义路由

import {createRouter, createWebHashHistory} from 'vue-router'
import A51 from '../views/A51.vue'
import A52 from '../views/A52.vue'
// 路由 => 路径和组件之间的对应关系
const routes = [
  {
    path: '/a1',
    component: A51
  },
  {
    path: '/a2', 
    component: A52
  }
]

const router = createRouter({ 
  history: createWebHashHistory(), // 路径格式
  routes: routes // 路由数组
})

export default router
  • createWebHashHistory 是用 # 符号作为【单页面】跳转技术,上面两个路由访问时路径格式为

    • http://localhost:7070/#/a1
    • http://localhost:7070/#/a2
  • 每个路由都有两个必须属性

    • path:路径

    • component:组件

  • createRouter 用来创建 router 对象,作为默认导出

需要在 main.ts 中导入 router 对象:

// ...
import A5 from './views/A5.vue'  // vue-router
import router from './router/a5router'
createApp(A5).use(antdv).use(router).mount('#app')

A5 是根组件,不必在 router 中定义,但需要在其中定义 router-view,用来控制路由跳转后,A51、A52 这些组件的显示位置,内容如下

<template>
  <div class="a5">
    <router-view></router-view>
  </div>
</template>
动态导入
import {createRouter, createWebHashHistory} from 'vue-router'
import A51 from '../views/A51.vue'
import A52 from '../views/A52.vue'	//静态导入
const routes = [
  // ...
  {
    path: '/a3',
    component: () => import('../views/A53.vue')		//动态导入
  }
]
  • 用 import 关键字导入,效果是打包时会将组件的 js 代码都打包成一个大的 js 文件,如果组件非常多,会影响页面加载速度
  • 而 import 函数导入(动态导入),则是按需加载,即
    • 当路由跳转到 /a3 路径时,才会去加载 A53 组件对应的 js 代码
    • vue-router 官方推荐采用动态导入
嵌套路由

如果希望再嵌套更深层次的路由跳转,例如:希望在 A53 组件内再进行路由跳转

首先,修改 A53.vue

<template>
  <div class="a53">
    <router-view></router-view>
  </div>
</template>

其次,再修改 /src/router/a5router.ts 文件 内容

import {createRouter, createWebHashHistory} from 'vue-router'
import A51 from '../views/A51.vue'
import A52 from '../views/A52.vue'
const routes = [
  // ...
  {
    path: '/a3',
    component: () => import('../views/A53.vue'),
    children: [		//子组件
      {
        path: 'student',
        component: () => import('../views/A531.vue')
      },
      {
        path: 'teacher',
        component: () => import('../views/A532.vue')
      }
    ]
  }
]
重定向
  • 用法1
import {createRouter, createWebHashHistory} from 'vue-router'
import A51 from '../views/A51.vue'
import A52 from '../views/A52.vue'
const routes = [
  // ...
  {
    path: '/a3',
    component: () => import('../views/A53.vue'),
    redirect: '/a3/student', // 重定向到另外路径
    children: [
      {
        path: 'student',
        component: () => import('../views/A531.vue')
      },
      {
        path: 'teacher',
        component: () => import('../views/A532.vue')
      }
    ]
  }
]
// ...

效果是,页面输入 /a3,紧接着会重定向跳转到 /a3/student

  • 用法2
import {createRouter, createWebHashHistory} from 'vue-router'
import A51 from '../views/A51.vue'
import A52 from '../views/A52.vue'
const routes = [
  {
    path: '/a1',
    component: A51
  },
  {
    path: '/a2', 
    component: A52
  },
  // ...
  {
    path: '/:pathMatcher(.*)*', // 可以匹配剩余的路径
    redirect: '/a2'
  }
]
// ...

效果是,当页面输入一个不存在路径 /aaa 时,会被 path: '/:pathMatcher(.*)*' 匹配到,然后重定向跳转到 A52 组件(404页面)去。

pinia

作用:组件间共享数据。

需求:在组件 p1 里更新了数据,主页组件也同步更新显示

  • storage 虽然可以实现多个组件的数据共享,但是需要【主动访问】才能获取更新后的数据
  • 本例中由于没有涉及主页组件的 mounted 操作,因此并不会【主动】获取 storage 的数据
安装
npm install pinia

在 main.ts 中引入

import { createPinia } from 'pinia'

// ...
createApp(A6).use(antdv).use(router).use(createPinia()).mount('#app')
定义Store

再新建 store 目录来管理共享数据,下面是 /src/store/UserInfo.ts

import axios from '../api/request'
import { defineStore } from "pinia"
import { UserInfoDto } from '../model/Model8080'

export const useUserInfo = defineStore('userInfo', {
  state: () => {
    return { username: '', name: '', sex: '' }
  },
  actions: {
    async get(username: string) {
      const resp = await axios.get(`/api/info/${username}`)
      Object.assign(this, resp.data.data)
    },
    async update(dto: UserInfoDto) {
      await axios.post('/api/info', dto)
      Object.assign(this, dto)
    }
  }
})
  • 定义了 useUserInfo 函数,用来获取共享数据,它可能用于多个组件

    • 命名习惯上,函数变量以 use 打头
  • state 定义数据格式

  • actions 定义操作数据的方法

    • get 方法用来获取用户信息

    • update 方法用来修改用户信息

  • 由于 useRequest 必须放在 setup 函数内,这里简化起见,直接使用了 axios

获取用户信息

<template>
  <div class="a6main">
    <a-layout>
      <a-layout-header>
        <span>{{serverUsername}} 【{{userInfo.name}} - {{userInfo.sex}}】</span>
      </a-layout-header>
      <a-layout>
        <!-- ... -->
      </a-layout>
    </a-layout>
  </div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import AIcon from '../components/AIcon3' // jsx icon 组件
import { serverMenus, serverUsername } from '../router/a6router'
import { useUserInfo } from '../store/UserInfo'
const userInfo = useUserInfo()

onMounted(()=>{
  userInfo.get(serverUsername.value)
})
</script>

修改用户信息

<template>
  <div class="a6p1">
    <h3>修改用户信息</h3>
    <hr>
    <a-form>
      <a-form-item label="用户名">
        <a-input readonly v-model:value="dto.username"></a-input>
      </a-form-item>
      <a-form-item label="姓名" v-bind="validateInfos.name">
        <a-input v-model:value="dto.name"></a-input>
      </a-form-item>
      <a-form-item label="性别">
        <a-radio-group v-model:value="dto.sex">
          <a-radio-button value="男">男</a-radio-button>
          <a-radio-button value="女">女</a-radio-button>
        </a-radio-group>
      </a-form-item>
    </a-form>
    <a-button type="primary" @click="onClick">确定</a-button>
  </div>
</template>
<script setup lang="ts">
import { Form } from 'ant-design-vue'
import { onMounted, ref } from 'vue'
import { UserInfoDto } from '../model/Model8080'
import { useUserInfo } from '../store/UserInfo';
const dto = ref<UserInfoDto>({ username: '', name: '', sex: '' })
const userInfo = useUserInfo()
onMounted(()=>{
  Object.assign(dto.value, userInfo)
})
const rules = ref({
  name: [
    {required: true, message:'姓名必填'}
  ]
})
const { validateInfos, validate } = Form.useForm(dto, rules)
async function onClick() {
  try {
    await validate()
    await userInfo.update(dto.value)
  } catch (e) {
    console.error(e)
  }
}
</script>
  • 不能直接把 userInfo 绑定到表单,需要 dto 中转一下
  • userInfo.update 和 useInfo.get 返回的都是 Promise 对象,可以配合 await 一起用
转载请注明出处或者链接地址:https://www.qianduange.cn//article/17456.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!