typescript-接口

TypeScript的接口:用于约束类、对象、函数的契约(标准)

和类型别名一样,接口,不出现在编译结果中

在TypeScript中,接口(Interface)用于定义对象的结构和类型。它是一种约定,用于描述对象应该具有哪些属性和方法。接口可以提高代码的可读性、可维护性和可重用性。

属性类型接口

  • 属性类接口 接口的定义使用关键字interface,后面跟着接口的名称一对花括号。 在花括号中,可以定义接口的属性,方法和其他成员。
typescript 复制代码
interface Userinfo{
    name:string;
    age:number;
    sayhello:()=>void;
}

在上面的示例中,我定义 另一个Userinfo的接口,它具有三个成员:name,agesayhello。其中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
  1. 可索引的类型

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);
  1. 类类型
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 {}
  1. 继承接口
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>
相关推荐
袁煦丞3 分钟前
SimpleMindMap私有部署团队脑力风暴:cpolar内网穿透实验室第401个成功挑战
前端·程序员·远程工作
li理10 分钟前
鸿蒙 Next 布局开发实战:6 大核心布局组件全解析
前端
EndingCoder11 分钟前
React 19 与 Next.js:利用最新 React 功能
前端·javascript·后端·react.js·前端框架·全栈·next.js
li理13 分钟前
鸿蒙 Next 布局大师课:从像素级控制到多端适配的实战指南
前端
前端赵哈哈17 分钟前
Vite 图片压缩的 4 种有效方法
前端·vue.js·vite
Nicholas6824 分钟前
flutter滚动视图之ScrollView源码解析(五)
前端
电商API大数据接口开发Cris26 分钟前
Go 语言并发采集淘宝商品数据:利用 API 实现高性能抓取
前端·数据挖掘·api
风中凌乱的L31 分钟前
vue 一键打包上传
前端·javascript·vue.js
GHOME35 分钟前
Vue2与Vue3响应式原理对比
前端·vue.js·面试
张元清37 分钟前
useMergedRefs: 组件封装必不可少的自定义Hook
前端·javascript·面试