阅读视图

发现新文章,点击刷新页面。

Typescript之类型总结大全

TypeScript 中的基本类型

TypeScript 的基本类型涵盖了 JavaScript 的原始类型,并添加了一些 TypeScript 特有的类型。


1. JavaScript 原始类型(Primitive Types)

这些是 JavaScript 原有的基本数据类型,TypeScript 为它们提供了类型注解。

boolean - 布尔值

let isDone: boolean = true;
let isLoading: boolean = false;

number - 数字

TypeScript 中的所有数字都是浮点数,支持十进制、十六进制、二进制和八进制。

let decimal: number = 6;
let hex: number = 0xf00d;      // 十六进制
let binary: number = 0b1010;   // 二进制
let octal: number = 0o744;     // 八进制
let float: number = 3.14;
let infinity: number = Infinity;
let notANumber: number = NaN;

string - 字符串

let name: string = "张三";
let sentence: string = `你好,${name}!`;  // 模板字符串

bigint - 大整数(ES2020+)

表示大于 2^53 - 1 的整数。

let big: bigint = 9007199254740991n;
let big2: bigint = BigInt(9007199254740991);

symbol - 符号(ES2015+)

创建唯一的标识符。

typescript

let sym1: symbol = Symbol();
let sym2: symbol = Symbol("description");

2. 特殊原始类型

null - 空值

let n: null = null;

undefined - 未定义

let u: undefined = undefined;

注意:在 strictNullChecks 模式下,null 和 undefined 只能赋值给它们自己或 any 类型。


3. TypeScript 特有类型

any - 任意类型

关闭类型检查,兼容所有类型。

let notSure: any = 4;
notSure = "可能是字符串";
notSure = false;  // 没问题

unknown - 未知类型

比 any 更安全的类型,使用时需要类型检查或断言。

let value: unknown;

// 需要类型检查后才能使用
if (typeof value === "string") {
    console.log(value.length);
}

// 或使用类型断言
let str: string = (value as string);

void - 空类型

表示函数没有返回值。

function warnUser(): void {
    console.log("警告信息");
    // 没有 return 语句
}

never - 永不存在的值

表示永远不会发生的类型,用于总是抛出异常或无限循环的函数。

// 抛出错误
function error(message: string): never {
    throw new Error(message);
}

// 无限循环
function infiniteLoop(): never {
    while (true) {}
}

object - 非原始类型

表示非原始类型的值(不是 number, string, boolean, symbol, null, undefined)。

let obj: object = {};
let arr: object = [];
let func: object = function() {};

4. 数组类型

有两种表示方式:

类型 + 方括号

let list: number[] = [1, 2, 3];
let strings: string[] = ["a", "b", "c"];

泛型 Array<类型>

let list: Array<number> = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];

只读数组

let readonlyArr: ReadonlyArray<number> = [1, 2, 3];
// readonlyArr.push(4);  // ❌ 错误:只读数组不能修改

5. 元组(Tuple)

表示已知元素数量和类型的数组。

// 定义元组类型
let tuple: [string, number];
tuple = ["hello", 10];  // ✅ 正确
// tuple = [10, "hello"];  // ❌ 错误:类型不匹配

// 访问元组元素
console.log(tuple[0].substring(1));  // "ello"
console.log(tuple[1].toFixed(2));    // "10.00"

// 可选元素(3.0+)
let optionalTuple: [string, number?];
optionalTuple = ["hello"];           // ✅ 正确
optionalTuple = ["hello", 42];       // ✅ 正确

// 剩余元素
let restTuple: [string, ...number[]];
restTuple = ["hello", 1, 2, 3];      // ✅ 正确

6. 枚举(Enum)

数字枚举

enum Direction {
    Up = 1,      // 从 1 开始
    Down,        // 自动递增为 2
    Left,        // 3
    Right        // 4
}

let dir: Direction = Direction.Up;

字符串枚举

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}

常量枚举(编译时完全删除)

const enum Colors {
    Red,
    Green,
    Blue
}

let color = Colors.Red;  // 编译后:let color = 0;

7. 字面量类型

字符串字面量

type EventType = "click" | "scroll" | "mousemove";
let event: EventType = "click";  // ✅
// event = "hover";  // ❌ 错误

数字字面量

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 3;  // ✅
// roll = 7;  // ❌ 错误

布尔字面量

type Truthy = true;
let isTrue: Truthy = true;
// isTrue = false;  // ❌ 错误

8. 类型推断与联合类型

类型推断

let x = 3;            // x 被推断为 number
let y = "hello";      // y 被推断为 string

联合类型

let id: string | number;
id = "abc123";  // ✅
id = 123;       // ✅
// id = true;   // ❌ 错误

// 类型守卫
if (typeof id === "string") {
    console.log(id.toUpperCase());
} else {
    console.log(id.toFixed(2));
}

9. 类型别名

// 基本类型别名
type ID = string | number;
type Point = {
    x: number;
    y: number;
};

let userId: ID = "user-123";
let position: Point = { x: 10, y: 20 };

类型总结表

类型 示例 描述
boolean let isDone: boolean = true; 布尔值
number let count: number = 10; 所有数字类型
string let name: string = "John"; 字符串
bigint let big: bigint = 100n; 大整数
symbol let sym: symbol = Symbol(); 唯一标识符
null let n: null = null; 空值
undefined let u: undefined = undefined; 未定义
any let anything: any = 4; 任意类型
unknown let unsure: unknown = 30; 未知类型
void function(): void {} 无返回值
never function error(): never {} 永不存在的值
object let obj: object = {}; 非原始类型
Array<T> let list: number[] = [1, 2, 3]; 数组
[T1, T2] let tuple: [string, number]; 元组
enum enum Color { Red, Green } 枚举

实用示例

// 完整示例
function processInput(input: string | number | boolean): string {
    if (typeof input === "string") {
        return `字符串: ${input.toUpperCase()}`;
    } else if (typeof input === "number") {
        return `数字: ${input.toFixed(2)}`;
    } else {
        return `布尔值: ${input}`;
    }
}

// 严格空值检查
function greet(name: string | null): string {
    if (name === null) {
        return "你好,访客!";
    }
    return `你好,${name}!`;
}

// 使用 never 进行穷尽检查
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
    switch (shape) {
        case "circle":
            return Math.PI;
        case "square":
            return 1;
        case "triangle":
            return 0.5;
        default:
            // 确保处理了所有情况
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck;
    }
}

TypeScript 的基本类型系统提供了强大的类型安全保证,帮助开发者在编译时捕获错误,提高代码质量。

Vue3响应式API-reactive的原理

这段代码实现了一个简化版的响应式系统(类似 Vue 3 的 reactive),包含依赖收集和触发更新的核心机制。让我详细讲解每个部分:

一、核心结构

1. reactive 函数

function reactive<T extends object>(target: T) {
  return createReactiveObject(target);
}
  • 入口函数,接收一个普通对象
  • 返回该对象的响应式代理
  • 使用泛型 T extends object 确保只能处理对象类型

2. createReactiveObject 函数

创建 Proxy 代理对象的核心函数:

function createReactiveObject<T extends object>(target: T) {
  const handler = {
    // get 拦截器
    get(target: object, key: keyof object, receiver: () => void) {
      const result = Reflect.get(target, key, receiver);
      track(target, key);  // 依赖收集
      if (typeof result === "object" && result !== null) {
        return createReactiveObject(result);  // 深度响应式
      }
      return result;
    },
    
    // set 拦截器
    set(target: object, key: keyof object, value: unknown, receiver: () => void) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key);  // 触发更新
      }
      return result;
    },
  };
  return new Proxy(target, handler);
}

关键点:

  • 深度响应式:当访问的属性值是对象时,递归创建响应式代理
  • 惰性转换:只有在访问嵌套对象时才进行响应式转换
  • Reflect API:使用 Reflect.get/set 确保正确的 this 绑定

二、依赖管理系统

1. 存储结构

const targetMap = new WeakMap<object, Map<string, Set<Function>>>();
let activeEffect: null | Function = null;

结构说明:

WeakMap
  key: 原始对象 (target)
  value: Map
    key: 属性名 (string)
    value: Set<Function>  // 依赖该属性的 effect 集合

为什么用 WeakMap?

  • 键是对象,不影响垃圾回收
  • 当原始对象不再使用时,对应的依赖关系会自动清除

2. track - 依赖收集

function track(target: object, key: string) {
  if (!activeEffect) return;  // 没有 activeEffect 时不收集
  
  // 获取或创建 target 对应的 depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取或创建 key 对应的 dep(effect 集合)
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  dep.add(activeEffect);  // 将当前 effect 加入依赖集合
}

3. trigger - 触发更新

function trigger(target: object, key: keyof object) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  const dep = depsMap.get(key);
  if (!dep) return;
  
  // 创建副本避免无限循环
  const effects = new Set(dep);
  effects.forEach((effect) => {
    if (effect !== activeEffect) {  // 避免当前 effect 触发自身
      effect();
    }
  });
}

三、Effect 系统

export function effect(fn: Function) {
  const effectFn = () => {
    const prevEffect = activeEffect;
    activeEffect = effectFn;  // 设置当前活跃的 effect

    try {
      return fn();
    } finally {
      activeEffect = prevEffect;  // 恢复之前的 effect
    }
  };
  
  effectFn();  // 立即执行一次,进行初始依赖收集
  return effectFn;
}

执行流程:

  1. 创建 effectFn 包装函数
  2. 执行时设置 activeEffect = effectFn
  3. 执行用户传入的 fn()
  4. 在 fn() 执行期间,所有对响应式属性的访问都会调用 track
  5. track 将当前 activeEffect 收集为依赖
  6. 执行完成后恢复之前的 activeEffect

四、使用示例

const state = reactive<{ todos: string[] }>({ todos: [] });

// 创建一个 effect
effect(() => {
  console.log('todos 改变了:', state.todos);
  // 首次执行时,访问 state.todos,触发 get
  // track 收集当前 effect 作为 todos 的依赖
});

// 修改状态
state.todos.push('学习响应式原理');  // 触发 set -> trigger -> 执行 effect

五、完整工作流程

  1. 初始化响应式对象

    const state = reactive({ todos: [] });
    // 创建 Proxy 代理
    
  2. 创建 effect

    effect(() => console.log(state.todos));
    // 1. 设置 activeEffect = 当前 effect
    // 2. 执行回调,访问 state.todos
    // 3. Proxy.get 触发,track 收集依赖
    
  3. 数据变更

    state.todos = ['新任务'];
    // 1. Proxy.set 触发
    // 2. trigger 查找依赖的 effects
    // 3. 执行所有依赖的 effect 函数
    

Vue真的是单向数据流?

Vue 单向数据流的深入解析:对象 Props 的陷阱

🔍 核心问题:对象 Props 的可变性

你指出的完全正确!这是 Vue 单向数据流中最大的陷阱

🚨 问题的本质

1. 对象/数组 Props 的引用传递

<!-- 父组件 -->
<script setup>
import { reactive } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 父组件的响应式对象
const user = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京',
    street: '长安街'
  }
})
</script>

<template>
  <!-- 传递对象 prop -->
  <ChildComponent :user="user" />
</template>

<!-- 子组件 ChildComponent.vue -->
<script setup>
const props = defineProps(['user'])

// ⚠️ 危险操作:可以直接修改父组件的数据!
const updateUser = () => {
  props.user.name = '李四'  // ✅ 生效,且修改了父组件
  props.user.age++         // ✅ 生效
  props.user.address.city = '上海'  // ✅ 生效
  props.user.address.street = '南京路'  // ✅ 生效
}

// 甚至可以直接添加新属性
const addProperty = () => {
  props.user.email = 'new@email.com'  // ✅ 会添加到父组件对象
}
</script>

📊 为什么会出现这个问题?

2. JavaScript 引用机制 + Vue 响应式系统

// 1. JavaScript 对象是引用传递
const parentObj = { name: 'Parent' }
const childProp = parentObj  // 同一个引用!

childProp.name = 'Modified'  // 修改了 parentObj
console.log(parentObj.name)  // 'Modified'

// 2. Vue 的响应式系统基于 Proxy
const reactiveParent = reactive({ data: 'original' })
// 子组件获得的是同一个 Proxy 对象

// 3. Vue 的开发模式警告有限制
// 直接赋值 props.user = {} ❌ 会警告
// 但修改属性 props.user.name = 'new' ⚠️ 不会警告!

🔄 Vue 官方态度

3. 文档中的说明

Vue 官方文档明确指出:

"注意在 JavaScript 中对象和数组是通过引用传入的,
所以对于一个数组或对象类型的 prop 来说,
在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。"

"这在 Vue 中是不被推荐的,因为它破坏了单向数据流的清晰性。"

4. 警告机制的局限

// Vue 只能检测到这些情况:

// 情况1:直接重新赋值 ❌ 会警告
props.user = { name: 'new' }
// 警告: Attempting to mutate prop "user"

// 情况2:修改基本类型 prop ❌ 会警告
props.count = 2
// 警告: Attempting to mutate prop "count"

// 情况3:修改对象/数组的属性 ⚠️ 不会警告!
props.user.name = 'new'
props.list.push('item')
// 无警告,但破坏了单向数据流

🛡️ 如何避免这个问题?

5. 解决方案1:传递解构后的值

<!-- 父组件 -->
<template>
  <!-- 传递基本类型或深度解构 -->
  <ChildComponent 
    :user-name="user.name"
    :user-age="user.age"
    :user-city="user.address.city"
  />
  
  <!-- 或者使用 computed 创建只读副本 -->
  <ChildComponent :user="readonlyUser" />
</template>

<script setup>
import { computed } from 'vue'

const user = reactive({ name: '张三', age: 25 })

// 创建只读版本
const readonlyUser = computed(() => ({
  name: user.name,
  age: user.age
}))
</script>

6. 解决方案2:使用深度只读

<!-- 父组件 -->
<script setup>
import { readonly } from 'vue'

const user = reactive({ name: '张三', age: 25 })

// 使用 readonly 包装
const readonlyUser = readonly(user)
</script>

<template>
  <ChildComponent :user="readonlyUser" />
</template>

<!-- 子组件 -->
<script setup>
const props = defineProps(['user'])

const updateUser = () => {
  // ❌ 现在会触发警告
  props.user.name = '李四'
  // 警告: Set operation on key "name" failed: target is readonly.
}
</script>

7. 解决方案3:使用深度复制

<!-- 子组件处理 -->
<script setup>
import { ref, watch } from 'vue'

const props = defineProps(['user'])

// 深度复制到本地状态
const localUser = ref(JSON.parse(JSON.stringify(props.user)))

// 或者使用 lodash 的 cloneDeep
import { cloneDeep } from 'lodash-es'
const localUser = ref(cloneDeep(props.user))

// 监听 props 变化同步更新
watch(() => props.user, (newUser) => {
  localUser.value = cloneDeep(newUser)
}, { deep: true })
</script>

📈 最佳实践模式

8. 工厂函数模式

// composables/useSafeProps.js
import { ref, watch, toRaw } from 'vue'

export function useSafeProp(propValue, options = {}) {
  const { deep = true, immediate = true } = options
  
  // 创建本地副本
  const localValue = ref(structuredClone(toRaw(propValue)))
  
  // 监听 props 变化
  watch(() => propValue, (newValue) => {
    localValue.value = structuredClone(toRaw(newValue))
  }, { deep, immediate })
  
  return localValue
}

// 在组件中使用
const props = defineProps(['user', 'list'])
const localUser = useSafeProp(props.user)
const localList = useSafeProp(props.list)

9. 类型安全的深度只读

// types/utilities.ts
export type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function 
      ? T[P] 
      : DeepReadonly<T[P]>
    : T[P]
}

// 父组件使用
import type { DeepReadonly } from '@/types/utilities'

const user = reactive({ 
  name: '张三', 
  profile: { age: 25, address: { city: '北京' } } 
})

const readonlyUser = user as DeepReadonly<typeof user>

// 传递给子组件
<ChildComponent :user="readonlyUser" />

// 子组件中 TypeScript 会阻止修改
props.user.name = 'new'  // ❌ TypeScript 错误
props.user.profile.age = 30  // ❌ TypeScript 错误

🎯 结论:Vue 真的是单向数据流吗?

正确答案

  1. 设计理念上:Vue 设计为单向数据流 ✅
  2. 语法层面:通过 props + events 强制执行单向流 ✅
  3. 实现层面:由于 JavaScript 限制,存在对象引用漏洞 ⚠️
  4. 实践层面:需要开发者自觉遵守规范 ⚠️

关键认知

  • Vue 的单向数据流是"约定大于强制"
  • 框架提供了基础,但开发者需要负责具体实现
  • 对象/数组 props 的易变性是已知的设计取舍
  • 通过工具、规范和最佳实践可以避免问题

最终建议

如果你想保持严格单向数据流:

  1. 始终使用 readonly() 包装对象 props
  2. 使用 TypeScript 的只读类型
  3. 配置 ESLint 严格规则
  4. 通过事件通信,而不是直接修改
  5. 对于复杂对象,传递解构后的基本类型 但也要理解 Vue 的设计哲学:"给开发者选择权,而不是强制约束"

Vue 的设计哲学是"渐进式"和"灵活" ,它提供了单向数据流的基础,但也允许在需要时绕过限制。这既是优点(灵活性),也是挑战(需要团队规范)。

❌