Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等

news2024/11/23 17:21:56

简介

TypeScript 是 JavaScript 的超集,是 JavaScript(弱类型语言) 的强类型版本。

  • 拥有类型机制
  • 文件后缀 .ts
  • Typescript = type + ES6
  • TypeScript 和 JavaScript 的关系类似 less 和 css 的关系
  • TypeScript对 JavaScript 添加了一些扩展,如 class / interface / module 等,大大提升代码的可阅读性。
  • 不能在浏览器直接执行,而是编译成 JavaScript (去掉类型和特有语法)后才会运行:

与 JavaScript 相比的优势

  • 静态类型检查,可以在代码开发阶段就预知一些低级错误的发生。

类型声明文件

以为.d.ts 为后缀的文件,内容为 TS 语法编写的各种类型声明(定义数据,函数、接口或类的类型)

  • 通常安装第三方库后,若缺少类型声明文件,会有报错提示安装必要的类型声明文件
    在这里插入图片描述

  • 若没有提示,则可以在 https://www.npmjs.com/ 中搜索相应的类型声明文件

    搜索关键字为 @type/第三方库的名称

    在这里插入图片描述

  • 有的库没有类型声明文件,可以自己写,点我了解详细语法

  • 也可以用命令行生成类型文件,点我了解详情

学习资料

英文官网

https://www.typescriptlang.org/

中文文档
https://www.tslang.cn/docs/home.html

https://typescript.bootcss.com/

在线开发环境

https://www.typescriptlang.org/play/index.html

搭建本地开发环境

  1. 新建文件夹 TSdemo

  2. 初始化项目

npm init -y
  1. 安装必要的依赖
npm i -D typescript nodemon ts-node
  • typescript 用于将 TS 编译成 JS
  • nodemon 用于 node 进程的重启
  • ts-node 用于直接执行 TS 类型的文件
  1. 项目目录下创建文件 index.ts
let myNname: string = "朝阳";

console.log(myNname);
  1. 修改 package.json 中的 scripts 为
  "scripts": {
    "start": "nodemon --exec ts-node index.ts"
  },
  1. 初始化 ts 的配置
npx tsc --init

执行成功后,会生成文件 tsconfig.json

  1. 启动项目
    在这里插入图片描述
    在这里插入图片描述

声明类型

值类型

// 字符串
let myNname: string = "朝阳";

// 数字
let num: number = 10;

// 布尔类型
let ifLogin: boolean = true;
// 布尔类型支持赋值计算之后结果是布尔值的表达式
let bool: boolean = !!0

// null
let n: null = null;

// undefined
let u: undefined = undefined;

// symbol
let s: symbol = Symbol();

数组 []

// 空数组
let arr: [] = [];

// 元素只能是数字的数组(其他类型的写法类似)
let arr1: number[] = [1, 2];

// 元素只能是数字或字符串的数组(| 表示或的关系)
let arr2: (number | string)[] = [1, "朝阳"];

// 即是number类型也可能是string数组
const numbers1: number[] | string[] = ['123', '333'] // 正确
const numbers2: number[] | string[] = [123, '333'] // 错误

// 对象数组
let todoList: {
  id: number;
  label: string;
  done: boolean;
}[] = [
  {
    id: 1,
    label: "吃饭",
    done: false,
  },
  {
    id: 2,
    label: "编程",
    done: false,
  },
];

// 使用类型别名(type alias)
type User = { name: string; age: number }

// 存储对象类型的内容
const objectArr: User[] = [
  {
    name: 'zws',
    age: 18
  }
]

// 构造函数声明类型
let arr_1: Array<number> = [1, 2];
let arr_2: Array<number | string> = [1, "朝阳"];

对象 {}

// 空对象
let o: {} = {};
let o2: object = {};


// 必选属性的对象(赋值时,声明的属性必须有!)
let user: {
  name: string;
  age: number;
} = {
  name: "朝阳",
  age: 35,
};

// 可选属性的对象(可选属性需写在必选属性的后面!)
let user2: {
  name: string;
  age?: number;
} = {
  name: "晚霞",
};

Object 、 {} 、 object 的区别

  • 与Object类型相同的{}是最不具体的,可以将对象、数组和基元分配给它;

  • object是更具体的,类似于{ [key: string]: any };可以给它分配对象和数组,但不能分配原始类型的数据;

  • { [key: string]: string }是最具体的,它不允许任何原始类型、数组或具有非字符串值的对象被分配到它。

var o: object;
o = { prop: 0 }; // OK
o = []; // OK
o = 42; // Error
o = "string"; // Error
o = false; // Error
o = null; // Error
o = undefined; // Error

var p: {}; // or Object
p = { prop: 0 }; // OK
p = []; // OK
p = 42; // OK
p = "string"; // OK
p = false; // OK
p = null; // Error
p = undefined; // Error

var q: { [key: string]: any };
q = { prop: 0 }; // OK
q = []; // OK
q = 42; // Error
q = "string"; // Error
q = false; // Error
q = null; // Error
q = undefined; // Error

var r: { [key: string]: string };
r = { prop: 'string' }; // OK
r = { prop: 0 }; // Error
r = []; // Error
r = 42; // Error
r = "string"; // Error
r = false; // Error
r = null; // Error
r = undefined; // Error

类 class

class Person {}
const me: Person = new Person()

class Teacher {
  name: string
  age: number
}

const objectArr: Teacher[] = [
  new Teacher(),
  {
    name: 'zws',
    age: 18
  }
]

新增类型

以下类型为 TS 新增的,在 js 中不存在的数据类型

任意类型 any

当不确定变量的类型时(比如来自用户输入或第三方代码库的动态内容),可以使用,但尽量少用。

  • 任何类型的值都可以赋值给 any 类型的变量
let a: any = '你好'
a = 0

// 多数据类型的数组
let list: any[] = [1, true, "free"];

未知的类型 unknown

TypeScript3.0版本新增的类型

  • 任何类型的值都可以赋值给 unknown 类型的变量
  • any 与 unknown 的区别 : unknown 需要明确类型后执行操作,any 则不需要
let a: unknown = 1;

let b = a + 1; // 会报错 “a”的类型为“未知”

可通过 as 声明类型解决

let a: unknown = 1;

let b = (a as number) + 1;

永不存在的值 never

用于总会抛出异常或根本不会有返回值的函数表达式的返回值类型。
当变量被永不为真的类型保护所约束时,该变量也是 never 类型。

用途

  • 限制类型
  • 控制流程
  • 类型运算
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

无类型 Void

即什么类型都不是,通常用于描述函数没有返回值

function test(): void {
}

void类型的变量只能被赋予undefinednull

let test: void = undefined;

元组 Tuple

即固定长度和类型的数组

let x: [string, number];
x = ['hello', 10];

枚举 enum

一种全新的数据描述方式,用于描述一组数量有限的系列数据。

数值枚举(默认)

// 枚举三原色
enum Color {Red, Green, Blue}
// 按枚举内容的字符串取值,得到的是对应的下标(类似数组的下标,从0开始)
let c: Color = Color.Green;  // c的值为1 
// 枚举方向:上下左右
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 按下标取值,可得到枚举内容的字符串
console.log(Direction[0]) // "Up"

可以自定义下标的起点

// 将默认的下标 0  改为 下标 1,则后续下标会依次递增
enum Direction {
  Up = 1,
  Down,  // 2
  Left,  // 3
  Right  // 4
}

也可以自定义任意下标,未定义的在上一个的基础上递增

enum Direction {
  Up = 11,
  Down, // 12
  Left = 6, 
  Right, // 7
}

甚至下标可以相同

enum Direction {
  Up = 11,
  Down, // 12
  Left = 11,
  Right, // 12
}

console.log(Direction.Down); // 打印 12

console.log(Direction); // 打印 { '11': 'Left', '12': 'Right', Up: 11, Down: 12, Left: 11, Right: 12 }

如果下标使用了计算值或常量,那么该字段后面紧接着的字段必须设置初始值,不能默认递增值了!

const getValue = () => {
  return 0;
};
enum ErrorIndex {
  a = getValue(),
  b, // error 枚举成员必须具有初始化的值
  c
}
enum RightIndex {
  a = getValue(),
  b = 1,
  c
}
const Start = 1;
enum Index {
  a = Start,
  b, // error 枚举成员必须具有初始化的值
  c
}

字符串枚举

枚举成员为字符串时,其之后的成员也必须是字符串。

enum Direction {
  Up, // 未赋值,默认为0
  Down = '南',
  Left = '西',
  Right = '东'
}

类型断言 as

当你知道更确切的类型时,可以使用类型断言,类似类型转换,但不进行特殊的数据检查和解构。

它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设程序员已经进行了必须的检查。

let someValue: any = "this is a string";

方式一:as 【推荐】

let strLength: number = (someValue as string).length;

JSX中,只可用 as 语法断言

方式二:<>

let strLength: number = (<string>someValue).length;

函数

声明函数类型

  • 如果省略参数的类型,TypeScript 会默认这个参数是 any 类型;

  • 如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;

  • 如果函数有返回值,那么 TypeScript 会根据我们定义的逻辑推断出返回类型。

  • 函数体内使用的外部变量的类型,不会体现在函数类型定义中。

// 命名函数
function add(arg1: number, arg2: number): number {
  return x + y;
}

// 箭头函数
const add = (arg1: number, arg2: number): number => {
  return x + y;
};
// 定义变量 add 并声明为函数类型
let add: (x: number, y: number) => number;

add = (arg1: number, arg2: number): number => arg1 + arg2;

使用 Interface 声明函数类型

interface Add {
  (x: number, y: number): number;
}

使用 type 声明函数类型

type Add = (x: number, y: number) => number;

可选参数

可选参数需放置在必选参数之后,用 ? 标注

let add: Add = (arg1: number, arg2?: number): string => arg1 + arg2;

默认参数

  • = 号标注参数的默认值
  • 所有必须参数后面的带默认初始化的参数都是可选的
function add(x: number, y: number = 20): number {
    return x + y
}

剩余参数

const handleData = (arg1: number, ...args: number[]) => {
  //
};

函数重载

强类型语言中的函数重载:定义几个函数名相同,但参数个数或类型不同的函数,在调用时传入不同的参数,编译器会自动调用适合的函数。

TS 中的函数重载:通过为一个函数指定多个函数类型定义,从而对函数调用的返回值进行检查。

  • 只能用 function 来定义,不能使用接口、类型别名等。
// 这个是重载的一部分,指定当参数类型为string时,返回值为string类型的元素构成的数组
function handleData(x: string): string[];
// 这个也是重载的一部分,指定当参数类型为number时,返回值类型为string
function handleData(x: number): string; 

// 这个就是重载的内容了,这是实体函数,不算做重载的部分
function handleData(x: any): any { 
  if (typeof x === "string") {
    return x.split("");
  } else {
    return x
      .toString()
      .split("")
      .join("_");
  }
}

handleData("abc").join("_");
handleData(123).join("_"); // error 类型"string"上不存在属性"join"
handleData(false); // error 类型"boolean"的参数不能赋给类型"number"的参数。

接口 interface

用于自定义任意类型

interface Point {
  x: number;
  y: number;
}

interface Point {
  x: number,
  y: number
}
  • 每个属性间的间隔,可以说 ; 也可以是,
interface Person {
  name: string,
  age: number
}

// 使用范例
let user: Person = {
  name: '朝阳',
  age: 30
}

可选属性 ?

interface Point {
  x: number;
  y?: number;
}

可选属性的位置没有限制,无需像函数的可选参数一样,必须放在必传参数的后面。

只读属性 readonly

interface Point {
    readonly x: number;
    readonly y: number;
}

赋值后, xy再也不能被改变

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

属性的合并

interface Person {
  name: string;
}

interface Person {
  age: number;
}

let user: Person = {
  name: "朝阳",
  age: 35,
};

但已经指定过类型的属性,不能改为新类型

interface Person {
  name: string;
}

interface Person {
  name: number; // 报错 后续属性声明必须属于同一类型
}

若想定义为联合类型,也需在最初的地方定义!

interface Person {
  name: string | number;
}

定义函数类型

interface SumFunc {
  (a: number, b: number): number;
}

let c: SumFunc = (a, b) => a + b;

let d = c(1, 2);

定义索引类型

当定义了索引类型之后,数组的length方法,将不存在,包括Array原型链上的其他方法也不存

interface Dic {
  [id: number]: string;
}

const dic1: Dic = {
  0: "你",
  1: "好",
};

const dic2: Dic = ["你", "好"];

定义类类型(含接口的实现 implements )

用于给类添加约束,比如限定类必须含有某类型的属性/方法等。

// 定义接口 ClockInterface,有一个属性 currentTime,值类型为 Date
interface ClockInterface {
  currentTime: Date;
}

// 定义类 Clock 实现接口 ClockInterface
class Clock implements ClockInterface {
  // 因接口 ClockInterface 中有一个Date 类型的属性 currentTime,所以 类 Clock 也必须有这个属性
  currentTime: Date;
  constructor(arg: Date) {
    // 在构造方法中,需给接口 ClockInterface 限定的属性 currentTime 赋值
    this.currentTime = arg;
  }
}

可简写为

class Clock implements ClockInterface {
  constructor(public currentTime: Date) {}
}

绕开多余属性的类型检查

在这里插入图片描述

方式一:索引签名 【推荐】

interface myType {
  name: string;
  // 添加索引签名来兼容多余属性
  [prop: string]: any;
}

方式二:类型断言 【不推荐】

强行声明其为目标类型

let myInfo: myType = {
  name: "朝阳",
  age: 30,
} as myType;

方式三:类型兼容 【不推荐】

interface myType {
  name: string;
}

// 使用解构赋值,避开多余属性
let getName = ({ name }: myType) => {
  return name;
};

let myInfo = {
  name: "朝阳",
  age: 30,
};

console.log(getName(myInfo));

接口的继承 extends

与类的继承类似

// 基础类型 Person
interface Person {
  name: string;
}

// Student 类型在 Person类型的基础上,新增了学号 sid
interface Student extends Person {
  sid: number;
}

// Teacher 类型在 Person类型的基础上,新增了学科 class
interface Teacher extends Person {
  class: string;
}

let student1: Student = {
  name: "朝阳",
  sid: 1,
};

let teacher1: Teacher = {
  name: "朝阳",
  class: "前端开发",
};

同时继承多个接口

extends 后写多个接口即可,用 , 间隔

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

接口继承类

  • 接口继承了类之后,会继承成员(类型),但是不包括实现;
  • 接口还会继承 private 和 protected 修饰的成员,但是这个接口只可被这个类或它的子类实现
// 定义类 Person
class Person {
  name: string;
  constructor(arg: string) {
    this.name = arg;
  }
}

// 接口 I 继承类 Person
interface I extends Person {}

// 接口 I 只能被 类 Person 或 类 Person 的子类实现 implements
class Student extends Person implements I {}

泛型

在定义函数、接口或类的时候不预先指定数据类型,而在使用时再指定类型的特性。

泛型的作用

泛型可以提升应用的可重用性,如使用其创建组件,则可以使组件可以支持多种数据类型。

使用泛型

let printNum = (arg: number) => {
  console.log(arg);
};

printNum(123);
printNum("123"); // 会报错,因参数只能是数字
let printString = (arg: string) => {
  console.log(arg);
};

printString(123); // 会报错,因参数只能是字符串
printString("123");

怎样才能写一个通用的打印方法呢?
使用泛型!

let printAnyType = <T>(arg: T) => {
  console.log(arg);
};

printAnyType(123);
printAnyType("123");

可见泛型即将类型设定为一个变量,当代码执行时传入的类型是啥,它就是啥,从而大大拓展了代码的通用性。

泛型的语法

  • <> 包裹
  • 类型的变量通常用大写字母 T 表示,也可以是任意其他大写字母

多个泛型

// 定义函数 - 让元组中的两个元素互换位置
let exchange = <T, U>(tuple: [T, U]): [U, T] => {
  return [tuple[1], tuple[0]];
};

let tuple1: [number, string] = [1, "朝阳"];

let tuple2 = exchange(tuple1);

console.log(tuple2);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1876842.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《人人都是产品经理》:项目一图流

《人人都是产品经理》&#xff1a;项目一图流 项目一图流 项目一图流

[NSSCTF]-Reverse:[SWPUCTF 2021 新生赛]easyapp(安卓逆向,异或)

无壳 把后缀名改为zip&#xff0c;找到apk 查看jadx 这里调用了MainActivity的lambda$onCreate$0$MainActivity&#xff0c;然后又调用了Encoder进行异或。 exp&#xff1a; result棿棢棢棲棥棷棊棐棁棚棨棨棵棢棌 key987654321 flag for i in range(len(result)):flagchr(…

深入 SSH:解锁本地转发、远程转发和动态转发的潜力

文章目录 前言一、解锁内部服务&#xff1a;SSH 本地转发1.1 什么是 SSH 本地转发1.2 本地转发应用场景 二、打开外部访问大门&#xff1a;SSH 远程转发2.1 什么是 SSH 远程转发2.2 远程转发应用场景 三、动态转发&#xff1a;SSH 让你拥有自己的 VPN3.1 什么是 SSH 动态转发3.…

【工程实践】大模型推理指定GPU

前言 使用大模型进行推理&#xff0c;一般是在docker容器中&#xff0c;记录推理过程中遇到的问题。 问题描述 在使用docker容器时&#xff0c;在docker run时&#xff0c;如果使用的是--gpus all&#xff0c;这样在进入容器之后&#xff0c;会使用全部的GPU&#xff0c;如下图…

【算法专题--链表】两数相加 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐双指针 -- 模拟进位 (使用哨兵位头节点) &#x1f95d; 什么是哨兵位头节点&#xff1f; &#x1f347;思路解析 &#x1f34d;案例图解 四、总结与提炼 五、共勉 一、前言 两数相加 这道题&#xff0c;可以说是--…

如何将Hive表的分区字段插入PG表对应的时间戳字段?

文章目录 1、背景描述2、场景分析 1、背景描述 数据仓库的建设通常是为业务和决策服务的。在数仓开发的应用层阶段&#xff0c;BI可以直接从主题层/业务层取数&#xff0c;而前端需要根据具体的作图需求通过后端查询数据库 作图的指标需要根据主题层/业务层做查询计算&#xf…

基于C语言的Jacobi迭代和Gauss-Seidel迭代的方程组求解实现

文章目录 Jacobi迭代方法介绍Gauss-Seidel迭代方法介绍具体代码实现示例题目实现效果 Jacobi迭代方法介绍 Jacobi迭代法是一种简单的迭代求解方法&#xff0c;适用于严格对角占优矩阵。其基本思想是利用当前迭代步的已知解来更新下一个迭代步的解。在C语言实现中&#xff0c;我…

Textual Learning2 -- 使用时的小问题

1、出现的问题&#xff1a; 在vscode里面直接运行函数会显示报错&#xff1a; 我尝试在vscode中含textual库的环境下运行&#xff0c;但仍然报错 2、解决方案&#xff1a; 在命令行中运行&#xff1a; 首先按winR&#xff0c;输入cmd打开命令行 或在已经安装的conda环境&a…

【JVM-01】引言

【JVM-01】引言 1. 什么是JVM&#xff1f;2. JDK、JRE、JVM比较3.常用的JVM有那些4.学习路线 1. 什么是JVM&#xff1f; JVM即 Java Virtual Machine(Java虚拟机)&#xff0c;是Java程序运行的环境(Java 二进制字节码运行环境)。 好处&#xff1a; 一次编写&#xff0c;到处…

Java基础(五)——ArrayList

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

构建现代医疗:互联网医院系统源码与电子处方小程序开发教学

本篇文章&#xff0c;笔者将探讨互联网医院系统的源码结构和电子处方小程序的开发&#xff0c;帮助读者更好地理解和掌握这些前沿技术。 一、互联网医院系统源码结构 互联网医院系统通常由多个模块组成&#xff0c;每个模块负责不同的功能。以下是一个典型的互联网医院系统的主…

高精密机械设备中滚珠导轨的表面处理工艺有哪些?

滚珠导轨是机床传动和定位的传动元件&#xff0c;其表面处理方式对机床性能和使用寿命起着决定性的作用&#xff0c;不同的表面处理方法可以提高导轨的耐磨性、抗腐蚀性和整体性能。那么&#xff0c;滚珠导轨的表面处理方式有哪几种呢&#xff1f; 1、磨削法&#xff1a;磨削技…

myCrayon个人博客项目基于springBoot+Vue全栈开发

目录 项目介绍 简介 项目架构 项目模块组成 数据库设计 项目展示 首页 用户登录与注册 个人信息模块 商城展示 博客模块 博客浏览 博客发布与编辑 博客搜索 社区模块 新闻模块 后台管理系统 部署方式 结语 项目介绍 简介 项目类似于CSDN&#xff0c;支持所…

MyBatis Plus条件构造器使用

1Wrapper&#xff1a; 条件构造抽象类&#xff0c;最顶端父类 1.1 AbstractWrapper&#xff1a; 用于查询条件封装&#xff0c;生成 sql 的 where 条件 1.2 QueryWrapper&#xff1a; Entity 对象封装操作类&#xff0c;不是用lambda语法 1.3 UpdateWrapper&#xff1a; Update…

AVL树模拟

1.概念 虽然二叉搜索树可以缩短查找的效率&#xff0c;但如果数据有序或者接近有序时二叉搜索树树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。AVL 树是具有一下性质的二叉搜索树&#xff1a; 1.它的左右子树都是AVL树 2.左右子…

[Go 微服务] Kratos 使用的简单总结

文章目录 1.Kratos 简介2.传输协议3.日志4.错误处理5.配置管理6.wire 1.Kratos 简介 Kratos并不绑定于特定的基础设施&#xff0c;不限定于某种注册中心&#xff0c;或数据库ORM等&#xff0c;所以您可以十分轻松地将任意库集成进项目里&#xff0c;与Kratos共同运作。 API -&…

《mysql篇》--查询(进阶)

目录 将查询结果作为插入数据 聚合查询 聚合函数 count sum group by子句 having 联合查询 笛卡尔积 多表查询 join..on实现多表查询 内连接 外连接 自连接 子查询 合并查询 将查询结果作为插入数据 Insert into 表2 select * from 表1//将表1的查询数据插入…

【UE 网络】专用服务器和多个客户端加入游戏会话的过程,以及GameMode、PlayerController、Pawn的创建流程

目录 0 引言1 多人游戏会话1.1 Why&#xff1f;为什么要有这个1.2 How&#xff1f;怎么使用&#xff1f; 2 加入游戏会话的流程总结 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&#x1f4a5; 标题&#xff1a;【UE 网络】在网络…

爬坑之 [‘NODE_ENV‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。]

在package.json中配置如下&#xff1a; 执行npm run dev启动 报错&#xff1a; 实际上, NODE_ENVdevelopment webpack-dev-server 这条脚本会合并为两条命令执行&#xff0c; 分别为&#xff1a; NODE_EVNdevelopment webpack-dev-server 这种写法在cmd中是不被支持的 解决…

Bootstrap更改默认的“请在电子邮件地址中包含@”

摘要&#xff1a; 今天开发一个外贸系统&#xff0c;必须全部英文的&#xff0c;但是使用到bootatrp 4的input标签的type"email"输入没有含“”符号时会提示&#xff1a;“请在电子邮件地址中包含”中文提示&#xff01;一开始以为是中国下载的谷歌是浏览器自带的提示…