5-3 基础类型&引用类型
环境配置与调试
初始化项目配置
1. 添加调试脚本
在 package.json
中添加调试脚本,使用 nodemon
和 ts-node
实现实时调试:
"scripts": {
"start": "nodemon --exec ts-node index.ts",
"build": "tsc", // 编译 TypeScript 文件
"test": "echo \"Error: no test specified\" && exit 1" // 预留测试脚本
}
json
nodemon
:监控文件变化并自动重启服务。ts-node
:直接运行 TypeScript 文件,无需手动编译。build
:用于生产环境编译 TypeScript 代码为 JavaScript。
💡 提示:
- 确保已全局安装
nodemon
和ts-node
:npm install -g nodemon ts-node
bash - 如果未全局安装,可以在项目中局部安装:
npm install --save-dev nodemon ts-node
bash
2. 生成 TypeScript 配置文件
运行以下命令生成默认的 tsconfig.json
:
npx tsc --init
bash
生成的配置文件包含大量默认选项,以下是一些关键配置说明:
{
"compilerOptions": {
"target": "ES6", // 编译目标 JavaScript 版本
"module": "commonjs", // 模块系统(CommonJS/ES6 等)
"strict": true, // 启用严格类型检查
"esModuleInterop": true, // 支持 CommonJS 和 ES6 模块互操作
"outDir": "./dist" // 编译输出目录
},
"include": ["src/**/*"], // 需要编译的文件范围
"exclude": ["node_modules"] // 排除的文件
}
json
💡 提示:
- 修改
outDir
可以指定编译后的文件输出路径(如./dist
)。 include
和exclude
用于控制哪些文件需要编译。
3. 创建入口文件
在项目根目录下创建 index.ts
文件,作为程序的入口:
// index.ts
const message: string = "Hello TS";
console.log(message);
typescript
- 文件结构:
project-root/ ├── node_modules/ ├── src/ │ └── index.ts ├── package.json └── tsconfig.json
text
💡 提示:
- 推荐将源代码放在
src
目录下,编译后的文件输出到dist
目录。 - 可以通过
tsconfig.json
的include
字段指定src/**/*
来匹配所有源文件。
实时调试流程
启动调试
运行以下命令启动实时调试:
npm run start
bash
- 效果:
- 修改
index.ts
文件并保存后,nodemon
会自动重启服务。 - 控制台会实时输出最新的日志。
- 修改
示例调试场景
- 修改
index.ts
:const message: string = "Hello TypeScript!"; console.log(message);
typescript - 保存文件后,控制台会立即输出:
Hello TypeScript!
text
💡 提示:
- 如果遇到错误(如语法错误),
nodemon
会显示错误信息并停止重启,直到问题修复。 - 可以通过
nodemon
的配置(如nodemon.json
)自定义监控的文件类型和忽略的目录。
常见问题与解决方案
问题 1:ts-node
报错 “Cannot find module”
- 原因:未安装依赖或路径错误。
- 解决:
npm install --save-dev typescript @types/node
bash
问题 2:nodemon
不监听文件变化
- 原因:可能是文件系统权限问题或配置错误。
- 解决:
- 确保
nodemon
的监控范围正确:// nodemon.json { "watch": ["src"], "ext": "ts,json" }
json
- 确保
问题 3:编译后文件未生成
- 原因:
tsconfig.json
的outDir
未正确配置。 - 解决:
{ "compilerOptions": { "outDir": "./dist" } }
json
扩展学习资源
- TypeScript 官方文档:
nodemon
高级配置:ts-node
使用技巧:
通过以上配置和调试流程,你可以高效地开发和调试 TypeScript 项目! 🚀
基础类型详解
类型注解语法深入
变量与常量类型声明
// 常量声明(不可重新赋值)
const PI: number = 3.1415926;
const APP_NAME: string = "MyTSApp";
// 变量声明(可重新赋值但必须同类型)
let age: number = 25;
let username: string = "Alice";
age = 26; // ✅ 合法
// age = "twenty-six"; // ❌ 类型错误
typescript
类型推断机制
TypeScript 具有强大的类型推断能力:
let score = 100; // 自动推断为number类型
// score = "满分"; // ❌ 后续赋值仍受类型约束
const isAdmin = true; // 自动推断为boolean
// isAdmin = false; // ❌ 常量不可修改
typescript
💡 即使省略显式类型注解,TS仍会进行类型检查
基础类型扩展说明
1. 布尔类型(boolean)
let isLogged: boolean = false;
let hasPermission: boolean = true;
// 常见应用场景
function checkAccess(hasLicense: boolean): void {
if (hasLicense) {
console.log("Access granted");
}
}
typescript
🚨 注意:不同于JavaScript,其他类型不能隐式转换为boolean
// let flag: boolean = 1; // ❌ 数字不能赋值给布尔
typescript
2. 数字类型(number)
支持多种数值表示:
let decimal: number = 42; // 十进制
let hex: number = 0x2A; // 十六进制
let binary: number = 0b101010; // 二进制
let octal: number = 0o52; // 八进制
let bigNum: number = 1_000_000; // 数字分隔符(ES2021)
typescript
3. 字符串类型(string)
支持多种字符串语法:
let name: string = 'Alice';
let sentence: string = `Hello, ${name}!`; // 模板字符串
let multiline: string = `
Line 1
Line 2
`; // 多行字符串
typescript
4. Symbol类型
创建唯一标识符:
const sym1: symbol = Symbol();
const sym2: symbol = Symbol("description");
console.log(sym1 === sym2); // false,每个Symbol都是唯一的
typescript
💡 主要用途:创建对象唯一属性键、实现私有成员
5. Null和Undefined
let empty: null = null;
let notDefined: undefined = undefined;
// 严格模式下需显式声明
let maybeString: string | null = null;
let maybeNumber: number | undefined;
typescript
类型安全实践案例
案例1:用户输入验证
function validateUserInput(input: string | number): boolean {
if (typeof input === "string") {
return input.length > 0;
}
return input > 0;
}
console.log(validateUserInput("hello")); // true
console.log(validateUserInput(0)); // false
typescript
案例2:配置对象类型
interface AppConfig {
debug: boolean;
port: number;
apiUrl: string;
}
const config: AppConfig = {
debug: true,
port: 8080,
apiUrl: "https://api.example.com"
};
typescript
最新特性:BigInt
ES2020新增类型(需target设置为ES2020):
const bigNumber: bigint = 9007199254740991n;
const hugeNumber = BigInt("9007199254740991");
typescript
类型系统对比表
特性 | JavaScript | TypeScript |
---|---|---|
动态类型 | ✅ 变量可任意改变类型 | ❌ 变量类型固定 |
类型注解 | ❌ 不支持 | ✅ 显式声明类型 |
编译时检查 | ❌ 运行时才发现错误 | ✅ 编译阶段发现错误 |
智能推断 | ❌ 无 | ✅ 自动推断类型 |
常见问题解答
❓ 问题1:为什么使用类型注解?
- 提高代码可读性
- 在编译阶段捕获类型错误
- 提供更好的IDE支持
❓ 问题2:何时需要显式类型声明?
- 函数参数和返回值
- 可能为多种类型的变量
- 复杂对象结构
❓ 问题3:如何处理第三方库无类型定义?
// 1. 安装类型声明文件
npm install @types/library-name
// 2. 自定义声明
declare module "library-name";
typescript
延伸学习建议
- 实践TypeScript Playground中的类型体操
- 阅读《Effective TypeScript》Item1-10
- 尝试将现有JS项目迁移到TS
通过系统学习这些基础类型,你已经迈出了成为TypeScript开发者的重要一步! 🎯
对象类型深度解析
对象定义方式详解
1. 字面量声明(推荐方式)
// 内联类型注解
let person: {
name: string;
age: number;
address?: string; // 可选属性
} = {
name: "Alice",
age: 28
};
// 添加新属性
person.address = "Shanghai"; // ✅ 可选属性可后期添加
// 错误示例
// person.age = "28"; // ❌ 类型不匹配
// person.phone = "123"; // ❌ 未声明属性
typescript
2. 构造函数声明
// 不推荐:过于宽泛的类型
let data: Object = new Object();
data = "string"; // ✅ 允许但失去类型安全
data = 123; // ✅ 但完全失去类型约束
// 推荐:使用具体类型
let user: { name: string } = new Object();
user.name = "Bob"; // ✅
// user.age = 30; // ❌ 未声明属性
typescript
接口(Interface)最佳实践
基础接口定义
interface Employee {
readonly id: number; // 只读属性
name: string;
department: string;
salary?: number; // 可选属性
}
const emp1: Employee = {
id: 1001,
name: "Charlie",
department: "IT"
};
// emp1.id = 1002; // ❌ 只读属性不可修改
emp1.salary = 15000; // ✅ 可选属性可后期添加
typescript
接口继承
interface Manager extends Employee {
teamSize: number;
approveRequest(): boolean;
}
const mgr: Manager = {
id: 2001,
name: "David",
department: "Management",
teamSize: 8,
approveRequest: () => true
};
typescript
可选属性的高级用法
1. 动态属性访问
interface Config {
apiUrl: string;
timeout?: number;
[key: string]: any; // 索引签名
}
const config: Config = {
apiUrl: "/api",
timeout: 5000
};
config.retryTimes = 3; // ✅ 通过索引签名允许动态属性
typescript
2. 严格可选属性检查
interface StrictUser {
name: string;
age?: number | undefined; // 显式包含undefined
}
function printUser(user: StrictUser) {
console.log(user.name);
if (user.age !== undefined) {
console.log(user.age.toFixed()); // 安全访问
}
}
typescript
类型别名(Type Aliases)
与接口的比较
// 类型别名方式
type Point = {
x: number;
y: number;
z?: number;
};
// 接口方式
interface IPoint {
x: number;
y: number;
z?: number;
}
// 使用区别
let p1: Point = { x: 1, y: 2 };
let p2: IPoint = p1; // ✅ 类型兼容
typescript
复杂对象类型示例
嵌套对象
interface Company {
name: string;
CEO: {
name: string;
age: number;
};
departments: Array<{
name: string;
budget: number;
}>;
}
const myCompany: Company = {
name: "TechCorp",
CEO: {
name: "Emma",
age: 45
},
departments: [
{ name: "R&D", budget: 1000000 },
{ name: "HR", budget: 500000 }
]
};
typescript
常见问题解决方案
问题1:如何定义可选但非undefined的属性?
type StrictOptional<T> = T extends undefined ? never : T;
interface Product {
name: string;
price: StrictOptional<number>; // 不能是undefined
}
typescript
问题2:如何合并多个对象的类型?
type User = { name: string };
type Admin = { privileges: string[] };
type SuperUser = User & Admin; // 交叉类型
const su: SuperUser = {
name: "Admin",
privileges: ["create", "delete"]
};
typescript
性能优化技巧
- 避免过度嵌套:超过3层嵌套应考虑拆分接口
- 使用只读数组:对于不会修改的数组属性
interface Config { readonly whitelist: ReadonlyArray<string>; }
typescript - 类型提取:将重复的类型定义提取为单独类型
最新特性:模板字面量类型
TypeScript 4.1+ 支持:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiPath = `/api/${string}`;
const path: ApiPath = "/api/users"; // ✅
// const invalid: ApiPath = "/users"; // ❌
typescript
学习路线图
通过系统学习这些对象类型技术,你将能够构建更健壮的类型系统! 🏗️
数组类型深度解析
数组定义方式详解
1. 字面量语法(推荐)
// 基础类型数组
const primes: number[] = [2, 3, 5, 7];
const flags: boolean[] = [true, false, true];
// 对象类型数组
interface Product {
id: number;
name: string;
}
const inventory: Product[] = [
{ id: 1, name: "Laptop" },
{ id: 2, name: "Mouse" }
];
typescript
2. 泛型语法(Array<T>
)
// 等效的字面量写法
const temperatures: Array<number> = [23.5, 19.2, 31.8];
// 复杂类型示例
type Coordinate = [number, number];
const polygon: Array<Coordinate> = [
[0, 0],
[10, 0],
[10, 10]
];
typescript
💡 在React JSX环境中,Array<T>
语法可能和标签冲突,此时推荐使用T[]
语法
联合类型数组高级用法
1. 类型守卫处理
const data: (string | number)[] = ["Apple", 100, "Orange", 200];
data.forEach(item => {
if (typeof item === "string") {
console.log(item.toUpperCase()); // ✅ 识别为string
} else {
console.log(item.toFixed(2)); // ✅ 识别为number
}
});
typescript
2. 类型推断优化
// 自动推断为 (string | number)[]
const mixed = ["text", 100];
// 显式声明特定顺序
const ordered: [string, number, string] = ["ID", 1001, "Name"];
typescript
只读数组类型
1. ReadonlyArray<T>
const colors: ReadonlyArray<string> = ["Red", "Green", "Blue"];
// colors.push("Yellow"); // ❌ 编译错误
typescript
2. readonly 修饰符
const matrix: readonly number[][] = [
[1, 2],
[3, 4]
];
// matrix[0].push(3); // ❌ 子数组也是只读的
typescript
元组(Tuple)类型
固定长度数组
type HttpResponse = [number, string];
const response: HttpResponse = [200, "OK"];
// 解构赋值
const [status, message] = response;
typescript
可选元组元素
type OptionalTuple = [string, number?];
const data1: OptionalTuple = ["Hello"];
const data2: OptionalTuple = ["World", 42];
typescript
数组类型工具
1. 类型查询
const users = [{ name: "Alice" }, { name: "Bob" }];
type UserType = typeof users[0]; // { name: string }
typescript
2. 映射类型
type Stringify<T> = { [P in keyof T]: string };
const stringProps: Stringify<{ age: number }> = { age: "25" };
typescript
常见问题解决方案
问题1:如何定义二维数组?
type Matrix = number[][]; // 或 Array<Array<number>>
const mat: Matrix = [
[1, 2],
[3, 4]
];
typescript
问题2:如何合并不同类型的数组?
type MixedArray = Array<string | number | boolean>;
const arr: MixedArray = ["text", 100, true];
typescript
性能优化技巧
- 避免any:会失去所有类型检查优势
- 使用const断言:保护数组不被修改
const LANGUAGES = ["TS", "JS"] as const; // LANGUAGES.push("Python"); // ❌
typescript - 优先使用for-of:比传统for循环有更好的类型推断
最新特性:可变元组类型(TS 4.0+)
type Bar<T extends any[]> = [boolean, ...T, number];
const bar: Bar<[string]> = [true, "hello", 100];
typescript
类型系统对比图
实战案例:购物车系统
type Currency = "USD" | "EUR";
type CartItem = {
id: number;
name: string;
price: number;
};
class ShoppingCart {
private items: CartItem[] = [];
private currency: Currency = "USD";
addItem(item: CartItem): void {
this.items.push(item);
}
get total(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
typescript
延伸学习建议
- 练习TypeScript内置数组工具类型(如
Partial<T[]>
) - 研究
lib.es5.d.ts
中的数组类型定义 - 尝试实现自定义类型安全的数组方法
通过深入理解这些数组类型技术,你将能够构建更健壮的数据结构! 📊
类型区分深度解析(Object vs object vs {})
核心类型对比增强版
类型 | 可接受值 | 不可接受值 | 典型用途 | 类型检查严格度 |
---|---|---|---|---|
Object | 任何值(包括原始类型) | 无 | 兼容老代码/任意值处理 | ★☆☆☆☆ |
object | 非原始类型(对象、数组、函数等) | string/number/boolean等原始类型 | 确保对象类型安全 | ★★★☆☆ |
{} | 任何值(类似Object) | 无 | 避免使用(过于宽泛) | ★☆☆☆☆ |
{ key: type } | 完全匹配结构的对象 | 缺失属性/类型不匹配/非对象值 | 精确类型约束 | ★★★★★ |
深入类型特性
1. Object 类型陷阱
const obj: Object = "字符串"; // ✅ 但失去类型安全
obj.toUpperCase(); // ❌ 运行时错误:Object没有toUpperCase方法
// 更安全的替代方案
const safeObj: String = new String("字符串"); // 包装对象
typescript
2. object 类型最佳实践
function processObject(obj: object) {
if (Array.isArray(obj)) {
console.log("处理数组:", obj.length);
} else if (typeof obj === "function") {
obj();
} else {
console.log("普通对象:", Object.keys(obj));
}
}
typescript
3. 精确结构类型 ({})
type StrictUser = {
id: number;
name: string;
};
// 属性检查
const user: StrictUser = {
id: 1,
name: "Alice",
// age: 25 // ❌ 多余属性
};
// 方法约束
interface Logger {
log: (msg: string) => void;
}
const consoleLogger: Logger = {
log: console.log // ✅ 方法签名匹配
};
typescript
类型层次关系图解
实际应用场景
场景1:API响应处理
type ApiResponse<T extends object> = {
data: T;
status: number;
};
function handleResponse(response: ApiResponse<{ user: string }>) {
console.log(response.data.user); // 安全访问
}
typescript
场景2:配置对象验证
interface Config {
env: "dev" | "prod";
readonly apiKey: string;
}
function init(config: Config) {
// config.apiKey = "new"; // ❌ 只读属性
}
typescript
高级类型技巧
1. 排除原始类型
type NonPrimitive<T> = T extends object ? T : never;
const val: NonPrimitive<string | { a: number }> = { a: 1 }; // string被排除
typescript
2. 属性存在性检查
type HasKey<T, K extends string> = T extends { [P in K]: any } ? true : false;
type Test = HasKey<{ id: number }, "id">; // true
typescript
常见问题解决方案
问题1:如何允许额外属性?
interface Flexible {
name: string;
[key: string]: any; // 索引签名
}
typescript
问题2:如何实现递归类型?
type Json =
| string
| number
| boolean
| null
| { [key: string]: Json }
| Json[];
typescript
性能考量
- Object/{} 类型:编译时检查成本最低,但运行时风险最大
- 精确类型:编译时检查更严格,可能增加编译时间
- 最佳实践:生产环境推荐使用精确类型,开发环境可放宽检查
最新特性:模板字面量类型(TS 4.1+)
type ObjectKey<T> = T extends object ? keyof T : never;
type UserKeys = ObjectKey<{ name: string; age: number }>; // "name" | "age"
typescript
类型安全演进路线
通过深入理解这些类型区别,您将能构建更健壮的类型系统! 🛡️
联合类型深度解析
基本应用扩展
1. 变量多类型声明
// 联合类型变量
let identifier: string | number | boolean = "USER_123";
identifier = 100; // ✅
identifier = true; // ✅
// identifier = {}; // ❌ 不在允许的类型范围内
// 初始化推断
const initialValue = Math.random() > 0.5 ? "text" : 100; // 自动推断为 string | number
typescript
2. 函数返回值类型
function parseInput(input: string): string | number {
const parsed = parseInt(input);
return isNaN(parsed) ? input : parsed;
}
const result = parseInput("42"); // number
const result2 = parseInput("abc"); // string
typescript
类型守卫进阶技巧
1. typeof
类型守卫
function formatValue(value: string | number | boolean) {
if (typeof value === "string") {
return value.padStart(5, " "); // 字符串操作
} else if (typeof value === "number") {
return value.toFixed(2); // 数字操作
}
return !value; // 布尔操作
}
typescript
2. instanceof
类型守卫
class Dog { bark() {} }
class Cat { meow() {} }
function handlePet(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark(); // ✅ 类型收窄到Dog
} else {
pet.meow(); // ✅ 类型收窄到Cat
}
}
typescript
3. 自定义类型谓词
interface Fish { swim(): void }
interface Bird { fly(): void }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // ✅ 类型收窄到Fish
} else {
pet.fly(); // ✅ 类型收窄到Bird
}
}
typescript
联合类型与数组
1. 混合类型数组
const mixedArray: (string | number)[] = ["a", 1, "b", 2];
mixedArray.push("c"); // ✅
mixedArray.push(3); // ✅
// mixedArray.push(true); // ❌
typescript
2. 类型安全的数组操作
function sumNumbers(values: (string | number)[]): number {
return values.reduce((sum, val) => {
return sum + (typeof val === "number" ? val : (parseInt(val) || 0));
}, 0);
}
typescript
复杂联合类型
1. 对象联合类型
type Square = { kind: "square"; size: number };
type Circle = { kind: "circle"; radius: number };
function area(shape: Square | Circle): number {
switch (shape.kind) {
case "square": return shape.size ** 2;
case "circle": return Math.PI * shape.radius ** 2;
}
}
typescript
2. 可辨识联合(Discriminated Unions)
type NetworkState =
| { state: "loading"; progress: number }
| { state: "success"; data: string }
| { state: "error"; code: number };
function handleState(state: NetworkState) {
switch (state.state) {
case "loading":
console.log(`Progress: ${state.progress}%`);
break;
case "success":
console.log(`Data: ${state.data}`);
break;
case "error":
console.error(`Error ${state.code}`);
break;
}
}
typescript
常见问题解决方案
问题1:如何处理未知的联合类型?
function safeToString(value: unknown): string {
if (value === null || value === undefined) return "nullish";
return value.toString(); // 需要类型守卫
}
typescript
问题2:如何合并多个联合类型?
type A = string | number;
type B = boolean | number;
type Combined = A | B; // string | number | boolean
typescript
性能优化建议
- 优先使用基本类型联合:
string | number
比object
联合更高效 - 避免深层嵌套联合:超过3层的联合类型应考虑重构
- 使用类型别名:提高复杂联合类型的可读性
type ID = string | number;
typescript
最新特性:模板字面量联合类型(TS 4.1+)
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = `/api/${HttpMethod}`;
const route: ApiRoute = "/api/POST"; // ✅
// const invalid: ApiRoute = "/api/INVALID"; // ❌
typescript
类型系统关系图
实战案例:表单验证
type ValidationResult =
| { valid: true; value: string }
| { valid: false; error: string };
function validateEmail(email: string): ValidationResult {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email)
? { valid: true, value: email }
: { valid: false, error: "Invalid email" };
}
typescript
延伸学习建议
- 练习TypeScript官方文档中的Union Types章节
- 尝试实现一个类型安全的Redux reducer
- 研究
never
类型在联合类型中的特殊作用
通过掌握这些联合类型技术,你将能够处理更复杂的类型场景! 🧩
TypeScript 类型系统核心要点总结 🚀
1. 基础类型系统
- 显式类型注解:区别于JavaScript的动态特性
let count: number = 42; // 必须为数字 let message: string = "Hello";
typescript - 特殊原始类型:
symbol
:唯一标识符bigint
:大整数(ES2020+)null
/undefined
:需显式处理
2. 对象类型精要
- 属性控制:
interface User { name: string; // 必选 age?: number; // 可选 readonly id: string; // 只读 }
typescript - 结构匹配:
- 鸭子类型(Duck Typing):只要结构匹配即视为合法
- 严格模式:启用
strictNullChecks
后更安全
3. 数组类型进阶
- 混合类型数组:
const data: (string | number)[] = ["a", 1];
typescript - 元组控制:
type Point = [number, number?]; // 可选元素
typescript
4. 类型严格性层级
类型 | 接受值范围 | 典型用途 |
---|---|---|
any | 任何值(完全禁用检查) | 兼容旧代码 |
Object | 包含原始类型的对象 | 不推荐使用 |
object | 仅非原始类型对象 | 安全的对象类型约束 |
{...} | 精确结构匹配 | API响应/配置对象 |
5. 联合类型核心技巧
- 类型守卫模式:
function test(val: string | number) { if (typeof val === "string") { val.toUpperCase(); // 类型收窄 } }
typescript - 可辨识联合:
type Action = | { type: "ADD"; payload: number } | { type: "DELETE"; id: string };
typescript
学习路径优化建议
常见陷阱解决方案
- 问题:对象字面量多余属性报错
// 解决方案1:类型断言 const obj = { name: "Alice", age: 20 } as User; // 解决方案2:索引签名 interface Flexible { name: string; [key: string]: any; }
typescript
实战推荐
- 练习项目:实现类型安全的购物车系统
- 代码挑战:将JavaScript项目迁移到TypeScript
- 研究源码:分析Vue3或React的类型定义
扩展资源
- 官方文档:TypeScript Handbook
- 进阶书籍:《Effective TypeScript》
- 在线练习:TypeScript Playground
掌握这些核心概念,你已经具备了构建健壮类型系统的能力! 🎯 建议通过实际项目巩固知识,逐步探索更高级的类型特性。
↑