06. TypeScript基础:类型系统入门

TypeScript 基础:类型系统入门

TypeScript

JavaScript 是动态类型语言,变量类型在运行时才确定。这带来了灵活性,但也导致了很多运行时错误。TypeScript 给 JavaScript 加上了静态类型系统,让错误在编译时就能被发现。

这一期,我们学习 TypeScript 的基础类型系统。

为什么需要 TypeScript

JavaScript 的痛点

// 这段代码在 JavaScript 中完全合法
let name = "Alice"
name = 123  // 没有报错
name.toUpperCase()  // 运行时报错:name.toUpperCase is not a function

TypeScript 的解决方案

let name: string = "Alice"
name = 123  // 编译时报错:Type 'number' is not assignable to type 'string'

TypeScript 的优势

优势 说明
类型安全 编译时发现错误
智能提示 IDE 自动补全
代码文档 类型即文档
重构友好 改代码有信心

安装 TypeScript

全局安装

npm install -g typescript

项目安装

npm install typescript --save-dev

初始化配置

npx tsc --init

这会生成 tsconfig.json 文件。

基础类型

布尔值

let isDone: boolean = false

数字

let decimal: number = 6
let hex: number = 0xf00d
let binary: number = 0b1010
let octal: number = 0o744

字符串

let color: string = "blue"
color = 'red'
color = <code>green

数组

// 方式1:元素类型后面接 []
let list1: number[] = [1, 2, 3]

// 方式2:使用数组泛型
let list2: Array = [1, 2, 3]

元组

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

let tuple: [string, number]

tuple = ["hello", 10]  // OK
tuple = [10, "hello"]  // Error

访问元素:

console.log(tuple[0].substring(1))  // OK
console.log(tuple[1].substring(1))  // Error, number 没有 substring

枚举

enum Color {
  Red,
  Green,
  Blue
}

let c: Color = Color.Green
console.log(c)  // 1

自定义值:

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4
}

let c: Color = Color.Green

反向映射:

enum Color {
  Red = 1,
  Green,
  Blue
}

let colorName: string = Color[2]
console.log(colorName)  // 'Green'

any

任意类型,放弃类型检查。

let notSure: any = 4
notSure = "maybe a string"
notSure = false

注意:尽量少用 any,否则就失去了 TypeScript 的意义。

unknown

类型安全的 any。

let notSure: unknown = 4

// 必须类型检查后才能使用
if (typeof notSure === "number") {
  console.log(notSure.toFixed(2))
}

void

没有任何类型,通常用于函数返回值。

function warnUser(): void {
  console.log("This is my warning message")
}

null 和 undefined

let u: undefined = undefined
let n: null = null

默认情况下 nullundefined 是所有类型的子类型。

never

永不存在的值的类型。

// 抛出异常的函数
function error(message: string): never {
  throw new Error(message)
}

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

object

非原始类型。

declare function create(o: object | null): void

create({ prop: 0 })  // OK
create(null)         // OK
create(42)           // Error
create("string")     // Error

类型断言

告诉 TypeScript "我知道这个类型"。

尖括号语法

let someValue: any = "this is a string"

let strLength: number = (someValue).length

as 语法(推荐)

let someValue: any = "this is a string"

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

类型推断

TypeScript 会自动推断类型。

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

// 从右向左推断
let z = x + 1  // number

联合类型

表示可以是多种类型之一。

let id: string | number

id = "abc123"  // OK
id = 123       // OK
id = false     // Error

实际应用:

function formatValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase()
  } else {
    return value.toFixed(2)
  }
}

字面量类型

精确到具体的值。

let x: "hello"
x = "hello"  // OK
x = "world"  // Error

联合字面量:

type Direction = "up" | "down" | "left" | "right"

function move(dir: Direction) {
  // ...
}

move("up")     // OK
move("north")  // Error

类型别名

给类型起个名字。

type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver

function getName(n: NameOrResolver): Name {
  if (typeof n === "string") {
    return n
  } else {
    return n()
  }
}

实践建议

1. 避免使用 any

// Bad
let data: any = fetchData()

// Good
interface User {
  id: number
  name: string
}

let data: User = fetchData()

2. 使用类型推断

// Bad
let x: number = 10

// Good(让 TS 推断)
let x = 10

3. 优先使用 interface

// Bad
type User = {
  name: string
}

// Good(interface 可扩展)
interface User {
  name: string
}

4. 使用严格模式

tsconfig.json 中启用:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

总结

TypeScript 的基础类型系统包括:

  • 原始类型:boolean, number, string
  • 复合类型:array, tuple, enum
  • 特殊类型:any, unknown, void, never
  • 类型操作:联合类型、类型断言、类型推断

掌握了这些,你已经可以用 TypeScript 写代码了。下一期,我们学习接口、泛型和高级类型。


学习资源

Views: 13

03. Vue3基础:响应式系统和组件入门

Vue3基础:响应式系统和组件入门

Vue Logo

终于进入Vue3的世界了!这一期,我们学习Vue3最核心的概念:响应式系统和组件。

理解这两个概念,你就掌握了Vue3的80%。

响应式系统

什么是响应式?

// 普通变量 - 不是响应式的
let count = 0
count++
// Vue不知道count变了,UI不会更新

// 响应式变量 - Vue会追踪变化
const count = ref(0)
count.value++
// Vue知道count变了,自动更新UI

响应式 = 数据变化时,UI自动更新。

ref vs reactive

Vue3提供两种响应式API:

ref:包装单个值

import { ref } from 'vue'

const count = ref(0)
const message = ref('Hello')
const user = ref({ name: 'Vue' })

// 读取和修改需要.value
console.log(count.value)  // 0
count.value++

reactive:包装对象/数组

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'Vue'
  }
})

// 直接访问属性
console.log(state.count)  // 0
state.count++

// 数组
const list = reactive([1, 2, 3])
list.push(4)

怎么选?

场景 推荐
基本类型(数字、字符串) ref
对象 都可以
需要整体替换 ref
解构友好 reactive

我的建议:默认用ref,对象很大时用reactive

模板中自动解包


import { ref } from 'vue'

const count = ref(0)

  <!-- 不需要.value,自动解包 -->
  <p>Count: {{ count }}</p>
  <button>+1</button>

响应式丢失问题

// ❌ 解构会丢失响应性
const state = reactive({ count: 0 })
let { count } = state  // count是普通数字
count++  // 不会触发更新!

// ✅ 使用toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state)  // count是ref
count.value++  // 会触发更新

// ✅ 使用toRef(单个属性)
import { toRef } from 'vue'
const count = toRef(state, 'count')

computed

计算属性:基于响应式数据派生新值,自动缓存。


import { ref, computed } from 'vue'

const firstName = ref('Vue')
const lastName = ref('3')

// 只读计算属性
const fullName = computed(() => <code>${firstName.value} ${lastName.value})

// 可写计算属性
const fullName2 = computed({
  get: () => ${firstName.value} ${lastName.value},
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})

  

{{ fullName }}

watch

侦听数据变化:


import { ref, watch, watchEffect } from 'vue'

const count = ref(0)

// watch:明确指定侦听源
watch(count, (newVal, oldVal) => {
  console.log(<code>count changed: ${oldVal} -> ${newVal})
})

// watch多个源
watch([count, anotherRef], ([newCount, newAnother]) => {
  // ...
})

// watchEffect:自动追踪依赖
watchEffect(() => {
  console.log(count is ${count.value})
})

// 立即执行 + 可停止
const stop = watchEffect(() => {
  // ...
}, { immediate: true })

// 停止侦听
stop()

watch vs watchEffect

  • watch:明确指定侦听源,可访问新旧值
  • watchEffect:自动追踪,适合副作用

组件基础

定义组件


import { ref } from 'vue'

const count = ref(0)

  <button>
    Clicked {{ count }} times
  </button>

button {
  color: blue;
}

``是Vue3的语法糖,自动暴露变量给模板。

Props:父传子

<!-- Child.vue -->

// 定义props
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

// 使用props
console.log(props.title)

  <h1>{{ title }}</h1>
  <p>Count: {{ count }}</p>
<!-- Parent.vue -->

import Child from './Child.vue'

Emits:子传父

<!-- Child.vue -->

const emit = defineEmits(['update', 'delete'])

function handleClick() {
  emit('update', { id: 1, name: 'Vue' })
}

  <button>Update</button>
<!-- Parent.vue -->

import Child from './Child.vue'

function onUpdate(data) {
  console.log('Received:', data)
}

v-model:双向绑定

<!-- 自定义组件支持v-model -->

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

Vue3.4+可以用defineModel


const modelValue = defineModel()

插槽

<!-- Card.vue -->

  <div class="card">
    <div class="header">

    </div>
    <div class="content">

    </div>
    <div class="footer">

    </div>
  </div>
<!-- 使用 -->

    <h1>Title</h1>

  <p>Content goes here</p>

    <button>OK</button>

作用域插槽:

<!-- List.vue -->

const items = ['Apple', 'Banana', 'Orange']

  <ul>
    <li>

    </li>
  </ul>
<!-- 使用 -->

    {{ index + 1 }}. {{ item }}

模板语法

文本插值


  <p>{{ message }}</p>
  <p>{{ count + 1 }}</p>
  <p>{{ formatDate(date) }}</p>

指令


  <!-- 条件渲染 -->
  <div>Type A</div>
  <div>Type B</div>
  <div>Other</div>

  <!-- 列表渲染 -->
  <ul>
    <li>
      {{ item.name }}
    </li>
  </ul>

  <!-- 事件绑定 -->
  <button>Click</button>
  <button>+1</button>
  ...

  <!-- 属性绑定 -->
  <img />
  <div>...</div>
  <div>...</div>

  <!-- 双向绑定 -->

  ...

  <!-- 显示/隐藏 -->
  <div>...</div>

v-if vs v-show

特性 v-if v-show
渲染方式 条件为假时不渲染 始终渲染,用CSS隐藏
切换成本 高(销毁/重建) 低(只改CSS)
初始成本 低(不渲染) 高(总是渲染)

建议:频繁切换用v-show,条件很少改变用v-if

Class和Style绑定


  <!-- 对象语法 -->
  <div></div>

  <!-- 数组语法 -->
  <div></div>

  <!-- 混合 -->
  <div></div>

  <!-- Style对象 -->
  <div></div>

  <!-- Style数组 -->
  <div></div>

生命周期


import { onMounted, onUpdated, onUnmounted } from 'vue'

onMounted(() => {
  console.log('组件已挂载')
})

onUpdated(() => {
  console.log('组件已更新')
})

onUnmounted(() => {
  console.log('组件已卸载')
})

生命周期钩子:

选项式API 组合式API
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted

小结

你现在掌握了:

  • 响应式系统(ref、reactive、computed、watch)
  • 组件通信(props、emits、v-model、插槽)
  • 模板语法(指令、条件、循环)
  • 生命周期钩子

下一期,我们深入学习组合式API - Vue3最强大的特性。你会学会如何组织代码、复用逻辑、提取组合式函数。


练习任务

  1. 创建一个计数器组件,用ref实现
  2. 创建一个父子组件,通过props和emits通信
  3. 实现一个自定义v-model组件
  4. 使用作用域插槽渲染列表

下期预告:《Vue3组合式API:代码组织的艺术》—— 组合式函数怎么写?如何复用逻辑?

Views: 14