TypeScript的接口:用于约束类、对象、函数的契约(标准)
和类型别名一样,接口,不出现在编译结果中
在TypeScript中,接口(Interface)用于定义对象的结构和类型。它是一种约定,用于描述对象应该具有哪些属性和方法。接口可以提高代码的可读性、可维护性和可重用性。
属性类型接口
- 属性类接口 接口的定义使用
关键字interface
,后面跟着接口的名称
和一对花括号
。 在花括号中,可以定义接口的属性,方法
和其他成员。
typescript
interface Userinfo{
name:string;
age:number;
sayhello:()=>void;
}
在上面的示例中,我定义 另一个Userinfo
的接口,它具有三个成员:name
,age
和sayhello
。其中name是字符串类型
,age是数字类型
,sayhello
的一个没有参数和返回值的方法
。
我可以用使用该接口来声明变量,并确保变量符号该接口所描述的类型结构:
typescript
let userion:Userinfo = {
name:"Alice",
age:25,
sayHello:()=>{
console.log("hello!")
}
}
console.log("userion",userion)

- 可选属性
可选属性名字定义的后面加一个?
符号。加上?后,表示该属性可以存在,也可以不存在
。在赋值
或传参数
时,不要强制要求你提供这个字段的。【作用就是属性可有可无】
typescript
interface Userinfo {
name:string;
age?:number;
}
let userinfo1:Userinfo = {
name:"Alice"
};
console.log("userinfo1",userinfo1)
let userinfo2:Userinfo = {
name:"kimi",
age:30
}
console.log("userinfo2",userinfo2)

- 只读属性
typescript
interface Userinfo {
readonly x: number;
readonly y: number;
}
//通过复制一个对象字面量来构造Userinfo。赋值后,x和y再也不能被改变了。
let userinfo: Userinfo = { x: 10, y: 20 };
console.log("userinfo", userinfo);
userinfo.x = 50; // ❌ TypeScript 编译错误!
console.log("userinfo", userinfo);

只读数组类型:ReadonlyArray<T>
目的:确保数组一旦创建,就不能被修改,防止意外变更。
普通数组(有可变性风险)
typescript
let numbers: number[] = [1, 2, 3];
numbers.push(4); //允许
numbers[0] = 99; //允许
numbers.pop(); //允许
console.log(numbers); // [99, 2, 3] ------ 被意外修改了!

使用ReadonlyArray<T>
防止修改
typescript
// 定义一个只读数字数组
let numbers: ReadonlyArray<number> = [1, 2, 3];
// ❌ 以下操作都会在 TypeScript 编译时报错!
// numbers.push(4) // ❌ Error: Property 'push' does not exist
// numbers.pop() // ❌ Error: Property 'pop' does not exist
// numbers[0] = 10 // ❌ Error: Index signature in type 'readonly number[]' only permits reading
// numbers.length = 0 // ❌ 不允许修改 length
// ✅ 但你可以读取和使用它:
console.log("只读",numbers[0]); // ✅ 允许读取
numbers.forEach((n) => console.log("遍历",n)); // ✅ 允许遍历
const doubled = numbers.map((x) => x * 2); // ✅ 允许生成新数组
console.log("doubled",doubled)

- 额外的属性检查
函数类型接口
函数类型参数(或函数类型签名)不是"定义函数",而是"描述函数的形状"或"规定函数长什么样" 。
typescript
//定义函数类型接口。
interface SearchFunc {
(source: string, subString: string): boolean;
}
//声明变量,类型为SearchFunc。
let mySearch: SearchFunc;
//赋值一个函数。
//(source:string,subString:string)这个函数接受两个参数。
//source代表原始值,subString表示要查找的子串。
//:boolean表示这个函数发返回值是true和false(布尔类型)。
//总结:定义一个函数,接收两个字符串,返回一个布尔值。
mySearch = function (source: string, subString: string): boolean {
//source.search(subString)是js的字符串方法,它会在soure字符串中查找subString第一次出现的位置(索引)。
let result = source.search(subString);
//判断result>-1,如果是0、1、2、3就是true,小于-1就是false。
return result > -1;
};
//调用函数并打印结果
console.log(mySearch("hello world","world")) //true
console.log(mySearch("hello world","bye")) //false
- 可索引的类型
TypeScript支持两种索引签名:字符串和数字。
可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。
也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
typescript
import { ref } from "vue";
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// 如果你想在模板中使用,可以用 ref
const first = ref(myArray[0]);
const second = ref(myArray[1]);
console.log(myStr);
console.log(myArray[1]);
console.log(myArray);

可以将索引签名设置为只读,这样就防止了给索引赋值:
typescript
import { ref } from "vue";
interface ReadonlyStringArray {
[index: number]: string;
}
let myArray: ReadonlyStringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// 如果你想在模板中使用,可以用 ref
const first = ref(myArray[0]);
const second = ref(myArray[1]);
console.log(myStr);
console.log(myArray[1]);
console.log(myArray);

- 类类型
typescript
// 1.定义接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void; // 建议加上返回类型"空的","没有返回值"。
}
// 2.实现接口的类
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date): void {
this.currentTime = d;
}
constructor(h: number, m: number) {
//虽然 h 和 m 没使用,但我们用它们设置初始时间
const now = new Date();
now.setHours(h, m, 0, 0); // 设置时h、分m,秒s和毫秒ms归零
this.currentTime = now;
}
}
// 3.创建实例
const clock = new Clock(9, 15); // 创建一个 9:15 的钟
// 4.打印:创建时的时间
console.log("初始时间:", clock.currentTime);
// 5.修改时间
clock.setTime(new Date(2025, 5, 1, 14, 30)); // 设置为 2025年6月1日 14:30
// 6.打印:修改后的时间
console.log("设置后的时间:", clock.currentTime);
// 7.打印整个对象
console.log("Clock 实例:", clock);
- 实现接口
- 类静态部分与实例部分的区别
typescript
// 1. 定义构造函数的接口
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
// 2. 定义实例的接口
interface ClockInterface {
tick(): void;
}
// 3. 工厂函数:根据类创建实例
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}
// 4. 数字钟
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {
// 构造函数接收 h 和 m,但暂未使用
}
tick(): void {
console.log("beep beep");
}
}
// 5. 指针钟
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) {
// 同上
}
tick(): void {
console.log("tick tock");
}
}
// 6. 创建实例
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
// 7. 调用 tick() 方法(这才是"打印"的关键!)
digital.tick(); // 输出: beep beep
analog.tick(); // 输出: tick tock
// 8. 可选:打印实例本身(查看类型)
console.log("digital:", digital); // 输出: DigitalClock {}
console.log("analog:", analog); // 输出: AnalogClock {}
- 继承接口
typescript
// 1. 定义基础接口:形状
interface Shape {
color: string;
}
// 2. 定义另一个接口:笔画
interface PenStroke {
penWidth: number;
}
// 3. Square 接口继承了两个接口
interface Square extends Shape, PenStroke {
sideLength: number; // 正方形特有的属性
}
// 4. 创建一个空对象,并断言为 Square 类型
let square = <Square>{};
// 5. 设置属性
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
// 6. ✅ 打印属性
console.log("颜色:", square.color); // blue
console.log("边长:", square.sideLength); // 10
console.log("笔宽:", square.penWidth); // 5
// 7. ✅ 打印整个对象
console.log("Square 对象:", square);
7. 混合类型
typescript
// 1. 定义 Counter 接口
interface Counter {
(start: number): string; // 函数类型:接收 number,返回 string
interval: number; // 属性:数字
reset(): void; // 方法:无返回值
}
// 2. 工厂函数:创建一个符合 Counter 接口的对象
function getCounter(): Counter {
// 创建一个函数,并断言为 Counter 类型
let counter = <Counter>function (start: number): string {
console.log(`计数器启动,起始值: ${start}`);
return `Started at ${start}`; // 返回字符串(满足返回类型 string)
};
// 添加属性
counter.interval = 123;
// 添加方法
counter.reset = function (): void {
console.log("计数器已重置");
};
return counter;
}
// 3. 创建实例
let c = getCounter();
// 4. ✅ 打印和调用
console.log("初始 interval:", c.interval); // 123
// 调用函数本身(像函数一样使用)
c(10); // 输出: 计数器启动,起始值: 10
// 调用 reset 方法
c.reset(); // 输出: 计数器已重置
// 修改 interval
c.interval = 5.0;
console.log("修改后的 interval:", c.interval); // 5
8. 接口继承类
typescript
// 想象 private state 是一个"家族秘密"。
// SelectableControl 说:"只有知道这个家族秘密的人才能加入。"
// Button 和 TextBox 是家族后代(extends Control),知道秘密
// Image 不是家族成员,不知道秘密 不能加入
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() {}
}
class TextBox extends Control {
select() {}
}
class Text extends Control{
select(){}
}
// 错误:"Image"类型缺少"state"属性。没有继承contrl
class Image implements SelectableControl {
select() {}
}
class Location {}
在vue3和uni中的接口
typescript
<template>
<view class="post-list">
<!-- 加载状态提示 -->
<view v-if="posts.list.length === 0 && loading">
<text>加载中...</text>
</view>
<!-- 渲染文章列表 -->
<view v-for="post in posts.list" :key="post.id" class="post-item">
<text class="title">{{ post.title }}</text>
<text class="author">作者:{{ post.author.name }}</text>
<!-- 可以加更多字段,如 content -->
</view>
<!-- 显示总数 -->
<view v-if="posts.list.length > 0">
<text>共 {{ posts.total }} 篇文章</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
// 1. 定义 User 接口
interface User {
id: number;
name: string;
email?: string; //表示可选
}
// 2. 定义所有API响应式的统一格式(泛型设计)
//记忆口诀:通用响应三件套:code、message、data。
// 关键点是 <T> ------ 泛型(Generic)
//T 是一个占位符,表示 data 可以是任何类型。
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 3. 定义 Post 和 PostListResponse
//定义一篇文章和文章列表的结构。
//记忆技巧:"复杂数据=简单类型的组合+数组+嵌套"
//author: User;
// list: Post[];
//这叫类型组合,是TS的精髓。
interface Post {
id: number;
title: string;
content: string;
author: User;
}
interface PostListResponse {
list: Post[];
total: number;
}
// 4. 响应式数据
const posts = ref<PostListResponse>({
list: [],
total: 0,
});
const loading = ref<boolean>(true); // 添加加载状态
// 5. 模拟 API 请求函数(实际项目中替换为 uni.request) 模拟API请求函数(Promise+泛型)
const getPostList = (): Promise<ApiResponse<PostListResponse>> => {
return new Promise((resolve, reject) => {
// 🔁 开关:是否使用 mock 数据
const useMock = true; // 开发时设为 true,上线前改为 false
if (useMock) {
// 直接返回 mock 数据,不发真实请求
setTimeout(() => {
const mockData: ApiResponse<PostListResponse> = {
code: 200,
message: "success",
data: {
list: [
{
id: 1,
title: "群青",
content: "YOASOBI...",
author: {
id: 101,
name: "YOASOBI",
email: "xxxxxxxxxx@qq.com",
},
},
{
id: 2,
title: "勇者",
content: "短暂旅程的记忆...",
author: {
id: 102,
name: "YOASOBI",
},
},
],
total: 2,
},
};
resolve(mockData);
}, 800); // 模拟网络延迟
return; // 不执行后面的 uni.request
}
// 如果 useMock = false,才发真实请求
uni.request({
url: 'https://your-api.com/api/posts',
method: 'GET',
header: {
'Content-Type': 'application/json'
},
success: (res) => {
const responseData = res.data;
if (res.statusCode === 200) {
resolve(responseData as ApiResponse<PostListResponse>);
} else {
resolve({
code: res.statusCode,
message: '请求失败',
data: { list: [], total: 0 }
});
}
},
fail: (err) => {
resolve({
code: -1,
message: '网络错误',
data: { list: [], total: 0 }
});
}
});
});
};
// 6. 获取数据
const fetchPosts = async () => {
try {
const res: ApiResponse<PostListResponse> = await getPostList();
if (res.code === 200) {
posts.value = res.data;
} else {
console.error("请求失败:", res.message);
}
} catch (err) {
console.error("网络错误:", err);
} finally {
loading.value = false;
}
};
// 7. 页面加载时获取数据
onMounted(() => {
fetchPosts();
});
</script>
<style>
.post-list {
padding: 20px;
}
.post-item {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
}
.title {
font-size: 16px;
font-weight: bold;
display: block;
margin-bottom: 5px;
}
.author {
color: #666;
font-size: 14px;
}
</style>
