vue3+ts+案例

😬Vue3–2天+ ts2天+ pinia1天+ 项目7天
项目笔记:
简介 | 优医问诊H5
黑马网上项目:
https://www.bilibili.com/video/BV1HV4y1a7n4?p=151&vd_source=d824e6f1c7311e50c5b96a40803b1243

day01 0705-Vue3

vite 构建工具

了解:vite 工具作用和特点

vite(法语意为 “快速的”,发音 /vit/,发音同 “veet”) 是一种新型前端构建工具,能够显著提升前端开发体验。

对比 webpack:

  • 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢

image.png

vite 的原理:

  • 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块/编译并响应

image.png
注明:项目打包的时候最终还是需要打包成静态资源的,打包工具 Rollup
问题:

  • 基于 webpack 构建项目,基于 vite 构建项目,谁更快体验更好?vite
  • 基于 webpack 的 vue-cli 可以创建 vue 项目吗?可以,慢一点而已

vite 创建项目

yarn create vite my-vue-app --template /vue关于yarn创建vite所遇到的坑,创建vite错误_前端筱攻城狮的博客-CSDN博客

  1. 运行创建项目命令:
# 使用npm
npm create vite@latest

# 使用yarn
yarn create vite

# 使用pnpm
pnpm create vite
  1. 输入项目名称,默认是 vite-project
  2. 选择前端框架 Vue
  3. 选择项目类型 JS
  4. 创建完毕 cd 项目 yarn安装依赖 yarn dev运行项目
  5. 可以使用code .打开项目

安装依赖使用pnpm install

代码分析

对 vite 初始化的代码进行分析

  1. 需要切换插件

vue3 组件代码和 vue2 有些不一样,使用的语法提示和高亮插件也不一样。

  • vetur 插件需要禁用,安装 volar插件。

image.png

  1. 总结 vue3 写法不同
    1. 组件一个根节点非必需
    2. 创建应用挂载到根容器
    3. 入口页面,ESM 加载资源
import { createApp } from 'vue'
import App from './App.vue'
// 根据App组件创建一个应用实例
const app = createApp(App)
// app应用挂载(管理)index.html的 #app 容器
app.mount('#app')

总结:

  • 安装 volar 禁用 veter,也可以使用工作区模式启用对应插件
  • vue3 中是使用 createApp() 管理容器,不是 new Vue()

CompositionAPI

组合式API介绍

组合式 API 征求意见稿 | Vue 组合式 API
image.png

3.2 setup函数

setup函数是组合式API的入口函数

  • setup 函数是 Vue3 特有的选项,作为组合式API的起点
  • 从组件生命周期看,它在 beforeCreate 之前执行
  • 函数中 this 不是组件实例,是 undefined
  • 如果数据或者函数在模板中使用,需要在 setup 返回

总结:

  • 今后在vue3的项目中几乎用不到 this , 所有的东西通过函数获取。

reactive函数

通常使用它定义 对象类型 响应式数据

疑问:以前在 data 函数中返回对象数据就是响应式的,现在 setup 中返回对象数据是响应式的吗?

  • 不是,需要使用 reactive 转成响应式

使用步骤:

  • 从 vue 中导出 reactive 函数
  • 在 setup 函数中,使用 reactive 函数,传入一个普通对象,返回一个响应式数据对象
  • 最后 setup 函数返回一个对象,包含该响应式对象即可,模板中可使用

ref函数

通常使用它定义响应式数据,不限类型
使用步骤:

  • 从 vue 中导入 ref 函数
  • 在 setup 函数中,使用 ref 函数,传入普通数据(简单or复杂),返回一个响应式数据
  • 最后 setup 函数返回一个对象,包含该响应式数据即可
  • 注意:使用 ref 创建的数据,js 中需要 .value ,template 中可省略

总结:

  • ref 可以把简单数据或者复杂数据转换成响应式数据,注意使用js上加上 .value,不过模板可省略。
  • 疑问:定义响应式数据使用 ref 还是 reactive 呢?

推荐:以后声明数据,统一用 ref => 统一了编码规范

Proxy补充

Proxy第一个参数是对象,第二个值是get方法

reactive 与 ref 的选择

知道:在定义响应式数据的时候如何选择reactive和ref

开始分析:

  • reactive 可以转换对象成为响应式数据对象,但是不支持简单数据类型。
  • ref 可以转换简单数据类型为响应式数据对象,也支持复杂数据类型,但是操作的时候需要 .value 。
  • 它们各有特点,现在也没有最佳实践,没有明显的界限,所有大家可以自由选择。

推荐用法:

  • 如果能确定数据是对象且字段名称也确定,可使用 reactive 转成响应式数据,其他一概使用 ref 。这样就没有 心智负担 。

总结:

  • 在定义响应式数据的函数选择上,遵循:尽量使用 ref 函数支持所有场景,确定字段的对象使用 reactive 可以省去.value。

setup语法糖

简化 setup 固定套路代码 ,让代码更简洁
发现:

  • 使用 setup 有几件事必须做:默认导出配置选项,setup函数声明,返回模板需要数据与函数。
<script>
  export default {
    setup() {
      const say = () => console.log('hi')
      return { say }
    }
  }
</script>

解法:

  • 使用 setup 语法糖
<script setup>
  const say = () => console.log('hi')
</script>
// setup语法糖
// 写法上script标签加上属性setup
// 相当于是简写的语法,此后我们在script标签里的写的内容直接可以在模板中使用

快捷插件

Vue VSCode Snippets插件

image.png
直接输入 v3 tab选择

JavaScript (ES6) code snippets

image.png
imp imd nfn箭头函数

computed 函数

响应式 API:核心 | Vue.js
场景:当需要依赖一个数据得到新的数据使用计算属性
函数或者对象的形式

watch函数

掌握:使用watch函数监听数据的变化

https://cn.vuejs.org/api/reactivity-core.html#watch
大致内容:

  • 使用 watch 监听一个响应式数据
  • 使用 watch 监听多个响应式数据
  • 使用 watch 监听响应式对象数据中的一个属性(简单)
  • 使用 watch 监听响应式对象数据中的一个属性(复杂),配置深度监听
  • 使用 watch 监听,配置默认执行

落地代码:

  • 使用 watch 监听一个响应式数据

image.png
image.png
第三种:使用 watch 监听响应式对象数据中的一个属性(简单)
:::info
精确侦听对象的某个属性
一个函数,返回一个值
:::
image.png
第四种:使用 watch 监听响应式对象数据中的一个属性(复杂),配置深度监听

默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep选项

image.png
第五种:使用 watch 监听,配置默认执行

      {
        // 开启深度监听
        deep: true,
        // 默认执行一次
        immediate: true
      }

总结:

  • watch(需要监听的数据,数据改变执行函数,配置对象) 来进行数据的侦听
  • 数据:单个数据,多个数据,函数返回对象的属性,属性复杂需要开启深度监听
  • 配置对象:deep 深度监听 immediate 默认执行

image.png
:::info
当直接侦听一个响应式reactive对象时,侦听器会自动开启深层模式。
此时只针对响应式reactive
:::

6.1 停止监听stop

const stop=watch(()=>{})
stop()
当网站访问人数,停止监听
https://cn.vuejs.org/api/reactivity-core.html#watch

6.2 watchEffect

https://cn.vuejs.org/api/reactivity-core.html#watcheffect
立即执行,第一个参数回调函数,函数中,只要处理了响应式数据的字段,字段改变了,这个函数就会触发。
注意ref对象要写成count.value
想监听对象中的属性,要写点属性才能监听到

watchEffect() 相比,watch() 使我们可以:

  • 懒执行副作用;watch默认不会执行
  • 更加明确是应该由哪个状态触发侦听器重新执行;
  • 可以访问所侦听状态的前一个值和当前值。watch可以拿到新值和旧值

watchEffect()书写简单,只要你想监听的东西都写在里面,一变化就会触发回调
缺点:性能不好,但是没有想象的那么夸张。

生命周期

使用步骤:

  1. 先从vue中导入以on打头的生命周期钩子函数
  2. 在setup函数中调用生命周期函数并传入回调函数
  3. 生命周期钩子函数可以调用多次

具体内容:

  • Vue3和vue2的生命周期对比

8进3 vue3组合式API
使用方式 导入,调用该函数,参数是回调函数,在回调函数中写业务逻辑

Vue2Vue3
createdsetup
mountedonMounted
destoryedonUnmounted
选项式API下的生命周期函数使用组合式API下的生命周期函数使用
beforeCreate不需要(直接写到setup函数中)
created不需要(直接写到setup函数中)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated

引入组件 vue2导入注册使用,Vue3导入使用
import 组件名 from ‘xxx路径’
组件名驼峰或者连字符的写法
image.png

<template>
  <div>

    <button @click="show = !show">生命周期</button>
    <div v-if="show">
      <HelloWorld></HelloWorld>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
const show = ref(true)
onMounted(() => {
  console.log('mounted1');
})
onMounted(() => {
  console.log('mounted2');
})
</script>

父组件中引入子组件,导入使用即可
生命周期可以加载执行多次
当点击后show为false,子组件不显示,
子组件的生命周期钩子onUnmounted打印我挂了

//子组件
onUnmounted(() => {
  console.log('挂了')
})

ref获取DOM元素

元素上使用 ref属性关联响应式数据,获取DOM元素

步骤:

  1. 导入ref from ‘vue’
  2. 使用 ref 声明数据,初始值为null=> const hRef = ref(null)
  3. 模板中建立关联 =>

    我是标题

  4. 使用 => hRef.value [h1元素,innerHTML、textCOntent]

注意:默认值是null,需要在渲染完毕后访问DOM属性。

import { ref, onMounted } from 'vue'
const hRef = ref(null)
onMounted(() => {
  setTimeout(function () {
    console.log(hRef)
    hRef.value.textContent = '帅峰真帅'
  }, 2000)
})

ref操作组件-defineExpose

组件上使用 ref属性关联响应式数据,获取组件实例,拿到组件上面的方法和属性

步骤:

  • 使用

要访问组件实例的属性和方法,组件必须暴露出该属性和方法
vue3为什么要这么做呢,因为安全,只能暴露了才能改
defineExpose不需要导入,可以直接在组件中用
语法 是个函数,函数里参数是个对象,对象里配置的属性,就是你要暴露的属性和方法
组件中暴露的ref响应式数据,使用的时候自动解套不需要.value
defineExpose------定义暴露
觉得可以拿到所有的数据和方法,并且可以随意更改随意调用 不安全
控制用户到底能拿到哪些数据哪些方法
我想拿到组件中a数据,那组件里就应该把a暴露出去
如果不暴露就拿不到
https://cn.vuejs.org/api/sfc-script-setup.html#defineexpose
src/App.vue

<template>
  <div>
    <h1>ref操作组件</h1>
    <hello-world ref="compRef"></hello-world>
    <button @click="handleClick">click</button>
  </div>
</template>

<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { ref } from 'vue';
/**
 * 0. 导入ref
 * 1. 要用ref声明数据,初始值null
 * 2. 在组件上绑定ref属性
 * 3. .value访问组件实例
 * 
 * 注意 要访问组件实例的属性和方法,组件必须暴露出该属性和方法
 *  vue3要做这个事情呢,是因为他觉得安全,只有他暴露了才能改
 * defineExpose - 该函数就是用来暴露属性和方法的
 * 不需要导入,可以直接在组件里用
 * 语法 是个函数,函数里参数是个对象,对象里配置的属性,就是你要暴露的属性和方法
 * 组件中暴露的ref响应式数据,使用的时候自动解套不需要.value
 */
const compRef = ref(null)
const handleClick = () => {
  // console.log(compRef.value.count);
  // console.log(compRef.value.sayHi);
  compRef.value.count += 10
  compRef.value.sayHi()
}
</script>

<style lang="scss" scoped>

</style>

src/components/HelloWorld.vue

image.png

父传子-defineProps函数

:::info
vue3父传子-defineProps-参数是个对象里面的配置和vue2一样-忘记摸摸或看文档-js中用到props要用变量接收
:::

不需要导入就可以使用
目标:能够实现组件通讯中的父传子组件通讯
步骤:

  1. 父组件提供数据
  2. 父组件将数据传递给子组件
  3. 子组件通过 defineProps 进行接收
  4. 子组件渲染父组件传递的数据

注意:

  • 如果使用 defineProps 接收数据,这个数据只能在模板中渲染
  • 如果想要在 script 中也操作 props 属性,应该接收返回值

App.vue

<template>
  <div>
    <h1>父子通信</h1>
    <son-comp :money="money"></son-comp>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import SonComp from './components/SonCom.vue'
// 1. 父组件提供数据
// 2. 父组件将数据传递给子组件
// 3. vue2用props vue3用defineProps
// 4. 子组件渲染父组件传递过来的数据
const money = ref(100)
// const money = ref()
</script>

<style lang="scss" scoped></style>

SonCom.vue

<template>
  <div>
    <h1>Son Comp</h1>
    <h2>父亲给我的零花钱 {{ money }}</h2>
    <button @click="handleClick">花钱</button>
  </div>
</template>

<script setup>
const props = defineProps({
  money: {
    type: Number,
    default: 1000
  }
})
// vue2中 使用我们传过来的props 通过this.xxx访问
// vue3有this?没有this,this是undefined
// 如果你要访问,需要用 const props 接受defineProps返回值
const handleClick = () => {
  console.log(props.money)
}
</script>

<style lang="scss" scoped></style>

子传父-defineEmits函数

:::info
vue3子传父-defineEmits-参数是个数组-emit变量然后触发事件
:::
不需要import导入
目标:能够实现组件通讯中的子传父组件通讯
步骤:

  1. 子组件通过 defineEmits获取 emit 函数(因为没有this)
    1. 不需要import导入
  2. 子组件通过 emit 触发事件,并且传递数据
  3. 父组件提供方法
  4. 父组件通过自定义事件的方式给子组件注册事件

总结:

  • defineEmits 获取 emit 函数,且组件需要触发的事件需要显性声明出来

App.vue

<template>
  <div>
    <h1>父子通信</h1>
    <son-comp :money="money" @change-money="handleChangeMoney"></son-comp>
    <!-- <son-comp></son-comp> -->
  </div>
</template>

<script setup>
  import { ref } from 'vue';
  import SonComp from './components/SonComp.vue'
  /**
 * 1. 父组件提供数据
 * 2. 父组件将数据传递给子组件
 * 3. vue2用props vue3用defineProps
 * 4. 子组件渲染父组件传递过来的数据
 */
  const money = ref(100)
  const handleChangeMoney = (val) => {
    console.log('儿子给我的数据', val);
    if (val < 70) {
      alert('别花了,花太多了')
    } else {
      money.value = val
    }
  }
</script>

<style lang="scss" scoped></style>

src/components/SonComp.vue

<template>
    <div>
        <h1>Son Comp</h1>
        <h2>父亲给我的零花钱 {{ money }}</h2>
        <button @click="handleClick">花钱</button>
    </div>
</template>

<script setup>
// 子组件defineProps接受数据 该函数也不需要import
// 语法 参数可以接受数组也可以接受对象 和vue2基本一样
// defineProps()
const props = defineProps({
    money: {
        type: Number,
        default: 1000
    }
})
/**
 * 子传父步骤
 * 1. defineEmits获取emit 因为没有this (vue2 this.$emit)
 *      不需要import导入
 *      是个函数,参数是个数组,数组里每一项是字符串(事件名)
 * 2. 子组件通过emit触发事件 传递数据
 * 3. 父组件提供方法
 * 4. 通过自定义事件的方式给子组件注册事件
 */

 const emit = defineEmits(['changeMoney'])


// vue2中 使用我们传过来的props 通过this.xxx访问
// vue3有this?没有this,this是undefined
// 如果你要访问,需要用 const props 接受defineProps返回值
const handleClick = () => {
    // console.log(props.money);
    // this.$emit() error vue2
    // 和vue2概念,子组件不能直接修改父组件数据,因为违反单向数据流
    emit('changeMoney', props.money - 10)
}

</script>

<style lang="scss" scoped>

</style>

跨级组件通讯provide与inject函数

:::info
在源码里面github里面,通过1s可以用vscode打开
祖孙间通讯provide和inject函数-provide参数有两个键值对-inject参数key值
:::
为什么组件里面的form表单,有个disabled属性表单里面的属性都禁用了,用了祖孙级别的通信
对自己进行特殊的设置disabled,先看自己的属性,自己的优先级更高


vue2里面有这个属性,vue3中也有
学习语法:
provide(key,value)
inject(key)
GrandSonComp.vue
总结:

  • provide和inject是解决跨级组件通讯的方案
    • provide 提供后代组件需要依赖的数据或函数
    • inject 注入(获取)provide提供的数据或函数
  • 官方术语:依赖注入
    • App是后代组件依赖的数据和函数的提供者,Child是注入(获取)了App提供的依赖

toRefs保持响应式reactive

:::info
toRefs函数保持响应式-解构reactive对象会失去响应式-用toRefs保持字段是响应式-解构出来字段是ref对象 要用value获取
:::
解构出来的数据没有办法做响应式,我们使用toRefs包装,注意包装成了一个对象,要想在js中获取值,需要.value才醒
toRefs 函数的作用,与使用场景

  • 作用:把对象中的每一个属性做一次包装成为响应式数据
  • 响应式数据展开的时候使用,解构响应式数据的时候使用

总结:

  • 当去解构和展开响应式数据对象使用 toRefs 保持响应式
<template>
  <div>
    <h1>toRefs</h1>
    <!-- <h2>{{ person.name }}------ {{ person.age }}</h2> -->
    <h2>{{ name }}------ {{ age }}</h2>
    <button @click="changeAge">click 28</button>
    <button @click="changeAge2">click 38</button>
  </div>
</template>

<script setup>
import { reactive, toRefs } from 'vue';

const  obj = reactive({
  name: 'ccccc',
  age:18
})
const { name, age } = toRefs(obj)
console.log(name, age)
const changeAge = () => {
  age.value=28
  console.log(age)
}
const changeAge2 = () => {
  age.value = 38
  console.log(age)
 
}
</script>

<style lang="scss" scoped>

</style>

综合案例

:::info
综合案例-渲染列表数据功能-crud刷新列表封装在一个方法-发送请求时机
综合案例-删除功能-作用域插槽拿到id发送删除接口
:::
显示 渲染 删除的功能
https://zhoushugang.gitee.io/patient-h5-note/vue/case.html
mock 模拟数据
onMounted函数,里面是回调函数
prop 属性映射数据

<script setup>
import { onMounted, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'

const list = ref([])

async function getList() {
  const { data } = await axios.get('/list')
  // console.log(data);
  list.value = data
}
onMounted(async () => {
  getList()
})
/**
 * 确认框 是否确认删除  vue2 this.$confirm => vue3 看文档
 * 1. 发送删除请求
 * 删除成功提示 vue2 this.$message => vue3 看文档
 * 2. 刷新列表
 * @param {*} id 
 */
const handleDel = async (id) => {
  ElMessageBox.confirm(
    '确定删除吗',
    '温馨提示',
    {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning',
    }
  )
    .then(async () => {
      // 点击确认删除逻辑
      // console.log(id);
      await axios.delete(`/del?id=${id}`)
      ElMessage({
        type: 'success',
        message: '删除成功',
      })
      // 删除成功后刷新列表
      getList()
    })
    .catch(() => {
      // 点击取消
      ElMessage({
        type: 'info',
        message: '取消删除',
      })
    })

}
</script>

<template>
  <div class="app">
    <el-table :data="list">
      <el-table-column label="ID" prop="id"></el-table-column>
      <el-table-column label="姓名" width="150" prop="name"></el-table-column>
      <el-table-column label="籍贯" prop="place"></el-table-column>
      <el-table-column label="操作" width="100">
        <template #default="{ row }">
          <!-- {{ row }} -->
          <el-button type="primary" link @click="handleDel(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>
<style>
.app {
  width: 980px;
  margin: 100px auto 0;
}
</style>

注意使用element-插槽
要用#default={row} ,模版中 使用 插值表达式去展示
重启项目,就可以获取新的mock数据

笔记总结

百度网盘:https://pan.baidu.com/s/1ol7In_4pzPAj2kbCAM8ATw?pwd=6nzn#list/path=%2Fsharelink3831943639-635974732214202%2F108%E6%9C%9F&parentPath=%2Fsharelink3831943639-635974732214202
在线笔记
https://zhoushugang.gitee.io/patient-h5-note/
vue3基础
https://gitee.com/qianfengg/vue3-base-108
Vue3基础综合案例
https://gitee.com/qianfengg/vue3-demo-108
vue3+ts基础
https://gitee.com/qianfengg/vue3-ts-108

day02 0707-TS

资源起步

在线笔记
https://zhoushugang.gitee.io/patient-h5-note/ts/
代码:
代码—基础–demo :https://gitee.com/qianfengg/vue3-base-108
e3+ts基础 https://gitee.com/qianfengg/vue3-ts-108/tree/51eb65ced60552d68ed168246c21bd8bdd9d9cae

TS补充视频课:23-元组_哔哩哔哩_bilibili

TypeScript 是一种带有 类型语法 的 JavaScript 语言,在任何使用 JavaScript 的开发场景中都可以使用。
注意:TS 需要编译才能在浏览器运行。
总结:TS 是 JS 的超集,支持了JS 语法和扩展了类型语法。

TypeScript 作用

知道:TS作用是在编译时进行类型检查提示错误
发现:JS

  • 在程序运行的时候 Uncaught TypeError 这个错误挺常见的
  • 这些错误导致在开发项目的时候,需要花挺多的时间去定位和处理 BUG
const num = 18;
num.toLowerCase()  应该字符串才有该写法
// Uncaught TypeError: num.toLowerCase is not a function

原因:

  • JS 是动态类型的编程语言,动态类型最大的特点就是它只能在 代码执行 期间做类型的相关检查,所以往往你发现问题的时候,已经晚了。【运行时】

方案:

  • TS 是静态类型的编程语言,代码会先进行编译然后去执行,在 代码编译 期间做类型的相关检查,如果有问题编译是不通过的,也就暴露出了问题。
  • 配合 VSCode 等开发工具,TS 可以提前到在 编写代码 的时候就能发现问题,更准确更快的处理错误。

TS 优势:写代码的时候要放在代码块里面更好演示—编译时

  • 更早发现错误,提高开发效率
  • 随时随地提示,增强开发体验
  • 强大类型系统,代码可维护性更好,重构代码更容易【一看就知道什么类型】
  • 类型推断机制,减少不必要类型注解,让编码更简单
  • 最后:Vue3源码TS重写,React和TS完美配合,Angular默认支持TS,大中型前端项目首选。

Vue3 + TS 最新的开发技术栈,你还在等什么?

typeScript 编译

知道:如何使用 tsc 编译 ts 代码 compile
为什么编译:浏览器不认识,全局安装typescript
全局安装:

# npm 安装
npm i -g typescript
# yarn 安装
yarn global add typescript
# 部分mac电脑安装需要sudo权限
# sudo npm i -g typescript
# sudo yarn global add typescript

查看版本

tsc -v

编译 TS:

  • 新建 hello.ts 文件
  • 当前目录打开命令行窗口,执行 tsc hello.ts 命令,同级目录生成 hello.js 文件
  • 执行 node hello.js 验证一下 【注意:一定要切换到正确的文件夹】

思考:

  • 以后我们写 ts 都是手动的编译执行吗?
    • 在开发中:一般使用 webpack vite 等工具自动构建编译。

补充知识-无法重新声明块范围变量

无法重新声明块范围变量 如何解决-包个块级作用域
image.png
解决给一个块级作用域!!!!

补充-组件card和dialog为什么标题可以传入又可以用插槽

组件card和dialog为什么标题可以传入又可以用插槽
插槽后备内容啥的xxxxx

创建 vue-ts 项目

创建一个基于 ts 的 vue 项目,来学习 ts 语法–练习ts准备的环境-创建项目的时候选ts

# yarn
yarn create vite my-vue-ts-app --template vue-ts

# pnpm
pnpm create vite my-vue-ts-app --template vue-ts

在基于 vite 的项目中可以直接验证 ts 代码结果,因为已经配置好了 ts 环境。
老师是yarn create vite 输入项目名字 回车 选择Vue TS cd进入运行yarn dev
https://juejin.cn/post/7124142007659790372

我的项目代码:E:\108期就业班\14-vue3\代码

补充内容-vimium扩展

vscode 小灯泡
小窗 vim命令
找搜索的页面,关掉的页面 ,快捷键
扩展程序,管理里面有一个vimium cu全键盘操作浏览器


类型注解

知道:TypeScript 类型注解 【我的理解:注释&解释】
示例代码:

// 约定变量 age 的类型为 number 类型
let age: number = 18;
age = 19;
  • : number 就是类型注解,目的:它为变量提供类型约束。
  • 约定了什么类型,就只能给该变量赋值什么类型的值,否则报错。
  • 而且:约定类型之后,代码的提示也会非常清晰。

错误演示:

let age: number = 18;
// 报错:不能将类型“string”分配给类型“number”
age = '19';

小结:

  • 什么是类型注解?
    • 变量后面约定类型的语法,就是类型注解
  • 类型注解作用?
    • 约定类型,明确提示
      :::info
      学ts小技巧,先写js,之后去摸鼠标,摸一摸变量
      变量:类型=值
      :类型 类型注解
      作用就类型约束—给变量什么类型,后面赋值操作都是这个类型,否则就会报错
      :::
/**
 * 类型注解 (注释解释)
 * 学ts小技巧,先写js,之后去摸鼠标摸一摸变量
 * 语法
 * let age: number = 18
 * 变量:类型 = 值
 * :类型  类型注解
 * 作用
 * 有类型约束 - 给变量什么类型,后面赋值操作都要是这个类型,否则会报错
 * 代码提示会更清晰
 */
{
    let age: number = 18;
    age = 19
    // age = '12'
    // age = true
    let str: string = 'abc'
    str.includes('a')
}

补充–错误不提示

image.png
与volar冲突,这样就有提示了

补充-插件

error-lens插件
image.png
及mainjs报错信息处理
image.png

原始类型

原始类型-基本数据类型添加类型注解-先写js在鼠标摸一摸

知道:ts 有哪些类型,掌握:原始类型使用
TS 常用类型:

  • JS 已有类型
    • 简单类型,number string boolean null undefined symbol
    • 复杂类型,对象 数组 函数
  • TS 新增类型
    • 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any、泛型 等

原始类型:

  • 使用简单,完全按照 JS 的类型来书写即可
/**
 * 原始类型
 * number string boolean undefined null
 * undefined和null 用鼠标摸上去类型注解是any 比较特殊,因为我们一般用于初始值
 * js什么类型,类型注解就对应的写
 */
{
   let num: number = 1
   num
   let str: string = '2'
   str
   let flag: boolean = true
   flag
   let u: undefined = undefined
   u
   let n: null = null 
   n
}

数组类型

数组类型-类型中括号-Array尖括号里面写类型

掌握:数组类型的两种写法

/**
 * 数组类型
 * 2种写法
 * 第一种: 类型加上[] 比如number[], string[]
 * 第二种:Array<类型> 比如Array<number> Array<null>
 * 建议使用第一种
 */
{
 let arr: number[] = [1, 2, 3] 
 arr
 let arr2: boolean[] = [true, false,] 
 console.log(arr2);
 let arr3: Array<undefined> = [undefined, undefined, undefined]
 arr3
}

推荐使用:

  • number[] 写法

思考:

  • 如果数组需要存储多种类型数据呢?

联合类型 |

联合类型-语法是一个竖杠-或者的意思

掌握:通过联合类型将多个类型合并为一个类型
需求:数组中有 number 和 string 类型,这个数组的类型如何书写?

/**
 * 联合类型
 * 语法 类型A | 类型B | ... 是类型A或者是类型B或者是...
 * 注意联合类型 是一根竖线 
 * js或的逻辑---两根竖线
 */
{
  let arr: (string | number)[] = [1, '2', 3, '3']
  arr

  let arr2: (boolean | undefined | null)[] = [true, undefined, null]
  arr2

  // 字符串或者是数组类型的数组
  let arr3: string | number[] = [1, 2, 3]
  arr3

  /**
     * 联合类型综合小练习
     * 写延时器 延时器的初始值null
     * 赋值setTimeout
     */
  let timer: null | number = null
  timer = setTimeout(() => {}, 1000)
}

类型别名type

类型别名-用处方便复用-语法 type 类型别名 等号 类型

掌握:使用类型别名语法给类型取别字 利于复用
示例代码:

/**
 * 类型别名
 * 是什么
 *  给类型起别名
 * 怎么用
 *  关键字type - 记忆方式 let a = 1, 
 *  let 变量名 = 值
 *  type 类型名字 = 类型(单个类型或者联合等都可以)
 *  类型名字规范用大驼峰 所有首字母都要大写
 *  之后类型别名的使用 和 类型注解的方式一样
 *  :类型别名
 * 为什么要用
 *  为了复用,简化
 */
{
  type MyArr = (string | number)[]
  let arr: MyArr = [1, '2', 3]
  arr
  let arr2: MyArr = [2, '3', 4]
  arr2
  let arr3: MyArr = [3, '4', 5]
  arr3

  type MyNumber = number
  let a: MyNumber = 1
  a
}

类型别名:

  • type 类型别名 = 具体类型 基本语法
  • 定义类型别名,遵循大驼峰命名规范,类似于变量
  • 使用类型别名,与类型注解的写法一样即可

使用场景:

  • 当同一类型(复杂)被多次使用时,可以通过类型别名,简化 该类型的使用

函数类型

基本使用

23-函数类型-基本使用-分别指定参数和返回值都加类型注解-同时指定-类型别名

掌握:给函数指定类型

  • 给函数指定类型,其实是给 参数 和 返回值 指定类型。
  • 两种写法:
    • 在函数基础上 分别指定 参数和返回值类型
    • 使用类型别名 同时指定 参数和返回值类型
/**
 * 函数类型-基本使用
 * 本质上就是 给参数和返回值 加类型注解
 * 两种写法
 *  分别指定
 *      语法(a: 类型A, b: 类型B, ....):返回值类型
 *  同时指定
 *      用类型别名 函数的语法去处理
 *      type xxx = (a: 类型A, b: 类型B, ...) => 返回值类型
 *      注意 只能用在函数表达式
 * 
 * 函数声明 和 函数表达式
 * function xxx () {}
 * const xxx = () => {}
 */
{
    // 两个数字相加
    function add1(a: number, b: number): number {
        return a + b
    }
    add1(1, 2)
    // add1('1', 2) error
    // 两个字符串拼接
    const add2 = (a: string, b: string): string => a + b 
    add2('1', '2')
    // add2('1', 222)

    // type 自定义类型别名 = 类型  大驼峰
    type MyAddFn = (a: number, b: number) => number 
    const add3: MyAddFn = (a, b) => a + b
    add3(1, 2)
    add3(2, 3)

    // function add4(a, b) {
    //     return a + b
    // }
}

注意:
通过类似箭头函数形式的语法来为函数添加类型,只适用于 函数表达式

void 类型

24-函数类型-void类型-默认函数的返回值-返回值是undefined类型必须返回undefined

掌握:void 函数返回值类型

  • 如果函数没有返回值,JS中 默认返回undefined;
  • 但在TS中,定义函数类型时返回值类型为 void
  • 如果函数没有返回值,且没有定义函数返回值类型的时候,默认是 void
/**
 * 函数类型-void类型
 * function fn(): void
 * 函数里面啥都不返回,ts帮你推断 说返回值就是void 他指的就是没有返回任何东西
 * 在ts最新5.1版本,返回值类型配置是undefined 可以不写return undefined
 * 其他低版本 返回值undefined必须要写return undefined 
 * 低版本中返回值void和undefined是有区别的
 * 建议:返回值类型是undefined 就返回return undefined 
 因为不管高低版本都可以
 */
{
  function fn (): undefined {
    console.log('say hi');
    return undefined
  }
  console.log(fn());  
}

注意:

  • 在 JS 中如果没有返回值,默认返回的是 undefined
  • 但是 void 和 undefined 在 TypeScript 中并不是一回事
  • 如果指定返回值类型是 undefined 那返回值必须是 undefined【最新版本不一样】

可选参数

25-函数类型-可选参数-问号语法-参数可以传也可以不传-补充–默认值表示可选

补充:slice 字符串和数组都有的方法—切片
重点细节:参数可以传,可以不传
掌握: 使用 ? 将参数标记为可选

  • 如果函数的参数,可以传也可以不传,这种情况就可以使用 可选参数 语法,参数后加 ? 即可

练习,模拟 slice 函数,定义函数参数类型

/**
 * 函数类型-可选参数 返回值是void
 * 举例说明 slice方法。可以不传参数 也可以传1个 也可以传2个
 * 可选参数语法 在参数名后面加一个? 可选(可传可不传)
 */
{
  function mySlice (a?: number, b?: number) {
    console.log(a, b);
  }
  mySlice(1, 2)
  mySlice(1)
  mySlice()

  // 注意事项: 可选的必须要在必选的后面 必选的必须在前面
  // 下面的函数参数例子是错误示范
  // function mySlice2 (a?: number, b: number) {
  //     console.log(a, b);
  // }
}

注意:

  • 必选参数不能位于可选参数后 (start?: number, end: number) 这样是不行的

image.png1+undefined=NaN

补充:写了默认值是可选参数

写了默认值就代表end是可选参数,TS帮你识别出来了
问号写在冒号前面
image.png

对象类型

26-对象类型-基本使用-单行-一定要加分号或者逗号-多行可以省略-语法 属性 冒号 类型
27-对象类型-扩展用法-箭头函数-属性可选-类型别名写法

基本使用

掌握:对象类型语法

  • TS 的对象类型,其实就是描述对象中的 属性 方法 的类型,因为对象是由属性和方法组成的。
  • 先写JS 摸一摸 直接copy!!!类型注解
/**
 * 对象类型-基本使用
 */
{
  // 空对象 :{}
  const obj1: {} = {}
  obj1

  // 对象里有属性 换行的写法 写分号 写逗号或者不写
  const obj2: {
    name: string,
    age: number
  } = {
    name: 'zs',
    age: 18
  }
  obj2

  // 对象里有属性 不换行写在一行里,需要用分号或者逗号隔开
  const obj3: {a: number ;b: boolean} = {
    a: 1,
    b: true
  }
  obj3

  // type 类型别名 = 类型
  type MyObj = {
    c: null
    d: undefined
    sayHi(): void
    code(skill: string): string
  }
  // 对象中方法的语法 方法名(参数1: 参数类型...): 返回值类型
  const obj4: MyObj = {
    c: null,
    d: undefined,
    sayHi() {
      console.log('1');
    },
    code (skill: string) {
      console.log(skill);
      return skill
    }
  }
  obj4
}

小结:

  • 使用声明描述对象结构?{} 先写JS 摸一摸 直接copy!!!类型注解
  • 属性怎么写类型?属性名: 类型
  • 方法怎么写类型? 方法名(): 返回值类型 sayHi(): void

扩展用法

掌握:对象类型中,函数使用箭头函数类型,属性设置可选,使用类型别名。

  • 函数使用箭头函数类型 搭个架子
/**
 * 对象类型-扩展用法
 * 函数使用箭头函数类型,
 *  写法不同,但意思是等价的
 *  属性(参数A: 类型A...): 返回值类型
 *  属性: (参数A: 类型A...) => 返回值类型
 * 属性设置可选,语法 属性名后面加?
 * 使用类型别名
 */
{
  type MyObj = {
    c: number;
    d: string;
    // a(): void;
    a: () => void;
    // b(foo: boolean): boolean; 
    b: (foo: boolean) => boolean;
    e?: null;
    f?: (a: string, b: string) => string
  }
  const obj1: MyObj = {
    c: 1,
    d: '2',
    a() {

    },
    b(foo: boolean) {
      return foo
    },
    // e: null
    f(a, b) {
      return a + b
    }
  } 
  obj1

  type Config = {
    url: string;
    method?: string;
  };

  // axios参数 对象类型 类型注解
  /**
     * axios({
     *  url: '/aaaa',
     *  method: 'xxx' //不写有默认值get
     * })
     */
  function myAxios (config: Config) {
    console.log(config);
  }
  myAxios({
    url: '',

  })
}

  • 对象属性可选 属性名后面加?
// 例如:axios({url,method}) 如果是 get 请求 method 可以省略
const axios = (config: { url: string; method?: string }) => {};
  • 使用类型别名
// {} 会降低代码可阅读性,建议对象使用类型别名
// const axios = (config: { url: string; method?: string }) => {};
type Config = {
  url: string;
  method?: string;
};
const axios = (config: Config) => {};

小结:

  • 对象的方法使用箭头函数类型怎么写?{sayHi:()=>void}
  • 对象的可选参数怎么设置?{name?: string}
  • 对象类型会使用 {} 如何提供可阅读性?类型别名

对象类型-综合练习

练习
创建一个学生对象,该对象中具有以下属性和方法:

  • 属性:必选属性:姓名、性别、成绩,可选属性:身高
  • 方法:学习、打游戏(可选)

image.png

/**
 * 对象类型-综合练习
 * 
 */
{
    type Student = {
        name: string;
        gender: string;
        score: number;
        height?: number
        study(): void // 学习没有参数 没有返回 无尽的学习
        // 游戏名 string 返回数字 箭头函数类型练习
        play?: (game: string) => number
    }

    const student: Student = {
        name: 'zs',
        gender: 'M',
        score: 100,
        study() {

        },
        play(game) {
            console.log(game);
            return 1
        }
    }
    // 补充 因为play方法是可选的(可以写可以不写)
    // 注意 可选的方法,即使你写了 ts也会给你提示 可能未定义
    // 如何解决 3种方案 第1种 可选链 第二种 加判断(类型守卫)第三种非空断言

    //    student.play?.('王者荣耀')

    // 类型守卫
    //    if (student.play) {
    //     student.play('游戏A')
    //    }
    student.play && student.play('111')

    student.play!('11')

}

image.png
如何解决 3种方案 第1种 可选链 第二种 加判断(类型守卫)第三种非空断言!!!!!

接口interface–类

image.png

基本使用

26-interface的基本使用-配置对象类型 大驼峰

掌握:使用 interface 声明对象类型

  • 接口声明是命名对象类型的另一种方式
/**
 * interface 基本使用
 * es6类的语法 class Person {}
 * interface语法类比 描述对象 语法没有等号,类型别名语法才有等号
 * 接口,不要把所有的属性写在一行 规范一行一个属性
 * interface 接口名(大驼峰) {
 *  属性名: 类型
 *  属性方法名: 类型
 * }
 * 使用方式,就是和type的类型注解一样
 * :接口名
 */
{
  // 写个接口Student name age? study方法 play方法?
  interface Student {
    name: string;
    age?: number;
    study(): void;
    play?: (game: string) => boolean;
  }
  let student: Student = {
    name: 'ls',
    study () {

    },
  }
  student
}

小结:

  • interface 后面是接口名称,和类型别名的意思一样。
  • 指定 接口名称 作为变量的类型使用。
  • 接口的每一行只能有 一个 属性或方法,每一行不需要加分号。

interface 继承 extends

掌握:使用 extends 实现接口继承,达到类型复用
思考:

  • 有两个接口,有相同的属性或者函数,如何提高代码复用?可以将公共的属性或方法抽离出来,通过继承来实现复用
/**
 * interface 继承
 * 继承关键字 extends
 * interface A extends B(接口) 
 * A里面有B的属性和方法
 */
{
    // interface 接口名 {
    //  属性:类型
    //}
    interface Point2D {
        x: number,
        y: number
    }
    const point2d: Point2D = {
        x: 1,
        y: 2
    }
    point2d
    // Point3D 继承了 Point2D的属性和方法
    // Point3D 他有自己的z属性 同时还有了Point2D里面的属性方法
    interface Point3D extends Point2D {
        z: number
    }
    const point3d: Point3D = {
        x: 1,
        y: 2,
        z: 3,
    }
    point3d
}

小结:

  • 接口继承的语法:interface 接口A extends 接口B {}
  • 继承后 接口A 拥有 接口B 的所有属性和函数的类型声明

type 交叉类型 &

掌握:使用 交叉类型 实现 接口的继承效果
语法 类型A & 类型B (一个与的符号)

/**
 * type交叉类型 
 * interface extends 接口的继承 为了复用
 * type 复用的语法不是extends 他是用交叉类型 复用 相当于接口的继承
 * 语法 类型A & 类型B (一个与的符号)
 */
{
  type Point2D = {
    x: number,
    y: number
  }
  const point2d: Point2D = {
    x: 1,
    y: 2
  }
  point2d
  // 类型别名 A & 类型别名 B
  // 返回的类型就是A和B的属性都有 - 在这个例子中 xyz三个属性都有了
  type Point3D = Point2D & {  
    z: number
  }
  const point3d: Point3D = {
    x: 1,
    y: 2,
    z: 3,
  }
  point3d
}


小结:

  • 使用 & 可以合并连接的对象类型,也叫:交叉类型

interface vs type

29-interface和type的区别-2个相同-3个不同

image.png
image.png

了解:interface 和 type 的相同点和区别

  • 类型别名和接口非常相似,在许多情况下,可以在它们之间自由选择。

不同的点:

  • type 不可重复定义
type Person = {
  name: string;
};
// 标识符“Person”重复  Error
type Person = {
  age: number;
};
  • interface 重复定义会合并
interface Person {
  name: string;
}
interface Person {
  age: number;
}
// 类型会合并,注意:属性类型和方法类型不能重复定义
const p: Person = {
  name: 'jack',
  age: 18,
};

小结:

  • 它们都可以定义对象类型
  • 它们都可以复用,interface 使用 extends , type 使用 &
  • type 不能重复定义,interface 可以重复会合并
综合代码
/**
 * interface vs type
 *                          interface          type
 * 支持对象类型(相同)           支持                支持
 * 是否可以复用(相同)           可以                可以
 * 支持其他类型(不同)           不支持              支持
 * 复用的语法(不同)             extends            &(交叉类型)
 * 重复声明(不同)              可以,会合并属性      不可以,会报错
 */
{
    // type MyNumberString = number | string
    // type MyNumber = number

    // 重复声明的问题 有的公司规范喜欢第一个字母写个大写I - 意思就是interface
    // 接口重复声明 属性会合并
    interface IMyObj {
        name: string
    }
    interface IMyObj {
        // name: boolean
        age: number
    }
    const myObjInterface: IMyObj = {
        name: 'zs',
        age: 18
    }
    myObjInterface

    // 类型别名重复声明 - 重复声明直接报错,以下代码会报错
    // type TMyObj = {
    //     name: string
    // }
    // type TMyObj = {
    //     age: number
    // }

}

day03-0708-TS

类型推断

:::info
一、类型推断
类型推断-ts自己会帮你推断出类型-鼠标摸一摸
发生类型推断的几个场景:

  1. 声明变量并初始化时
  2. 决定函数返回值时
    :::

知道:TS 的的类型推断机制作用

在 TS 中存在类型推断机制,在没有指定类型的情况下,TS 也会给变量提供类型。
建议:

  • 将来在开发项目的时候,能省略类型注解的地方就省略充分利用TS推断 的能力,提高开发效率。
  • 在你还没有熟悉 ts 类型的时候建议都加上类型,比如今天第一次写 ts 最好都写上
  • 如果你不知道类型怎么写,可以把鼠标放至变量上,可以通过 Vscode 提示看到类型
/**      
 * 类型推断(你不用写类型,ts自己能帮你推断出来什么类型就是昨天鼠标摸一摸的操作)      
 * 1. 变量的初始化
 * 2. 函数的返回值
 */
{
   let age = 18 
   age
   let obj = {a: 1, b: '2'}
   obj

   function add (a: number, b: number) {
    return a + b
   }
   add(1,2)
}

image.png

字面量类型-精确

二、字面量类型

  1. 字面量类型介绍-写死个字面量类型,数据就不能改

例如:
const str2 = Hello TS!
str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS

  1. 字面量类型应用

类型更加精确-举例性别和方向-配合联合类型
配合联合类型来使用,表示:一组明确的可选的值
优势: 相比于 string 类型,使用字面量类型更加精确、严谨

字面量类型介绍

知道:什么是字面量类型

  • js 字面量如:18 ‘jack’ [‘a’] {age: 10} 等等。
  • 使用 js字面量 作为变量类型,这种类型就是字面量类型。

image.png

// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;

// 报错:不能将类型“19”分配给类型“18”
age = 19;

字面量类型应用

04-字面量类型应用-类型更加精确-举例性别和方向-配合联合类型 |

知道:字面量类型的应用场景
例如:性别只能是 男 和 女,不会出现其他值。

/**  
 * 字面量类型应用   
 * 一般用于联合类型配合字面量类型,能让我们类型更加的精准
 */
{
  // 比如 性别 如果用字符串类型 是不是范围太广不太符合逻辑 只有可能是男或者女
  //   let gender: string = '13241234'  
  let gender: '男' | '女' = '男'
  gender

  type MyDir = 'up' | 'down' | 'left' | 'right';

  // 方法,方法传入的参数是方向 up  down left right
  function changeDirection (dir: MyDir) {
    console.log(dir);
  }
  // changeDirection('down')
  changeDirection('down')
}

小结:

  • 字面量类型配合联合类型来使用,表示:一组明确的可选的值
  • 解释:参数 direction 的值只能是 up/down/left/right 中的任意一个
  • 优势:相比于 string 类型,使用字面量类型更加精确、严谨
重构命令-电灯泡

F2重命名
image.png

any 类型-逃避ts检查

:::success
三、any类型
任意类型逃避ts检查-不建议用any-用了相当于写js
作用: 逃避 TS 的类型检查
:::

/**  
 * any类型 - 逃避ts检查(随便你怎么写不会报错相当于写js) any英文的意思任意类型
 * anyscript不建议 他相当于你就在写js
 * 1. 显式 - 自己写类型注解 :any  在浏览器环境会报错!!
 * 2. 隐式
 *      初始化没有给值的时候,类型推断就是any类型
 *      函数的参数没有给定类型
 */
{
  let obj: any = { a: 1, b: 2 }
  obj = {
    a: 3,
    b: '5'
  }
  obj = 3
  obj = true;
  obj = null
  obj()
}

image.png
小结:

  • any 的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any 类型,尽量避免使用。

函数参数给初始值

:::tips
补充:函数参数给初始值-不用加问号-本质上也是可选
:::
image.png
image.png

类型断言

:::warning
四、类型断言
初识类型断言-as-比ts更确定一个类型-要确定了才能断言
例子–类型守卫–img元素赋值src练习
:::
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,

// aLink 的类型 HTMLElement,该类型只包含所有标签公共的属性或方法
// 这个类型太宽泛,没包含 a 元素特有的属性或方法,如 href
const aLink = document.getElementById('link')
  • 但是我们明确知道获取的是一个 A 元素,可以通过 类型断言 给它指定一个更具体的类型。
const aLink = document.getElementById('link') as HTMLAnchorElement
  1. 使用 as 关键字实现类型断言
  2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
  3. 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了

综合代码-两种方法

/**  
 * 类型断言
   
 * 在我们下面这个例子中,因为我们能确定页面中是有这样一个元素,不可能为null
 * 断言必须是你确定有才能去断言 一定是你确定没问题的
 * 
 * 元素的类型确定类型有2种方案
 * 第一种 打印去控制台看
 * 第二种(推荐) 你创建个你想要的元素,摸一摸然后在把代码删除
 */
{
    // Element | null
    // ts他并不清楚你是个a标签元素,他就只知道你获取的是个元素

    const link = document.querySelector('#link') as HTMLAnchorElement
    console.dir(link);
    link.href
}

image.png

练习-img元素赋值src

image.png

泛型-类型传参

  • 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
  • 在TypeScript中,泛型是一种创建可复用代码组件的工具。这种组件不只能被一种类型使用,而是能被多种类型复用。类似于参数的作用,泛型是一种用以增强类型(types)、接口(interfaces)、函数类型等能力的非常可靠的手段。

泛型别名

09-初识-泛型别名-类型传参-别名的后面写尖括号

掌握:泛型别名基本使用,实现类型复用
语法 名字后面加尖括号 传入你想要传入的类型,作为参数
a:A 类型
b:B类型

/**  
 * 泛型
 *  类型传参
 *  类比 函数 函数是不是可以传参数
 *      类型 也可以传参数
 * 泛型别名 
 * 语法 名字后面加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 */
{
  type MyObj<A = number, B = string> = {
    a: A
    b: B
  }
  
  // type MyObj1 = {
  //     a: number;
  //     b: string;
  // }
  // type MyObj2 = {
  //     a: boolean;
  //     b: null;
  // }
  
  const obj1: MyObj<{}, Date> = {
    a: {},
    b: new Date()
  }
  obj1
  const obj2: MyObj<boolean, undefined> = {
    a: true,
    b: undefined
  }
  obj2
}

泛型别名模拟接口数据上

不建议使用交叉类型-复用性没有泛型好

/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 * 
 * {
 *  code: 200,
 *  msg: 'xxx',
 *  data: 数据
 * }
 * 
 * 在我们没学泛型前 会用交叉类型做复用,但在这样的例子中不太合适
 *     type UserData = CodeAndMsg & { data: UserList }
 *     type ArticelData = CodeAndMsg & { data: ArticleDetail }
 * 用交叉类型还是有很多重复的代码
 */
{
    // 交叉类型演示 开始
    type CodeAndMsg = {
        code: number
        msg: string;
    }
    type User = {
        name: string
        age: number
    }
    // number[] string[] User[]
    type UserList = User[]
    type UserData = CodeAndMsg & { data: UserList }
    const userData: UserData = {
        code: 200,
        msg: '获取成功',
        data: [
            {
                name: 'zs',
                age: 18
            }
        ]
    }
    userData
    type ArticleDetail = {
        id: string
        content: string
    }
    /**
     * {
     *  code: 200,
     *  msg: '获取详情成功'
     *  data: {
     *      id: '',
     *      content: ''
     *  }
     * }
     */
    type ArticelData = CodeAndMsg & { data: ArticleDetail }
    const ArticleData: ArticelData = {
        code: 200,
        msg: '获取成功',
        data: {
            id: '1',
            content: '文章'
        }
    }
    ArticleData

    // 交叉类型演示结束
}

小结:

  • 泛型:定义类型别名后加上<类型参数> 就是泛型语法, 使用的时候传入具体的类型即可
  • 是一个变量,可以随意命名,建议遵循大驼峰即可。
  • 和类型别名配合,在类型别名后加上泛型语法,然后类型别名内就可以使用这个类型参数
  • 泛型可以提高类型的复用性和灵活性

泛型别名模拟接口数据下

/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 * 
 * {
 *  code: 200,
 *  msg: 'xxx',
 *  data: 数据
 * }
 * 
 * 在我们没学泛型前 会用交叉类型做复用,但在这样的例子中不太合适
 *     type UserData = CodeAndMsg & { data: UserList }
 *     type ArticelData = CodeAndMsg & { data: ArticleDetail }
 * 用交叉类型还是有很多重复的代码,需要用泛型优化
 *      type UserData = ApiData<UserList>
 *      type ArticelData = ApiData<ArticleDetail>
 */
{
    // 一般只有一个类型参数,规范上起名会写大写的T 意思就是Type
    type ApiData<T> = {
        code: number
        msg: string;
        data: T
    }
    type User = {
        name: string
        age: number
    }
    // number[] string[] User[]
    type UserList = User[]
    type UserData = ApiData<UserList>
    const userData: UserData = {
        code: 200,
        msg: '获取成功',
        data: [
            {
                name: 'zs',
                age: 18
            }
        ]
    }
    userData
    type ArticleDetail = {
        id: string
        content: string
    }
    /**
     * {
     *  code: 200,
     *  msg: '获取详情成功'
     *  data: {
     *      id: '',
     *      content: ''
     *  }
     * }
     */
    type ArticelData = ApiData<ArticleDetail>
    const ArticleData: ArticelData = {
        code: 200,
        msg: '获取成功',
        data: {
            id: '1',
            content: '文章'
        }
    }
    ArticleData
}

泛型接口

掌握:泛型接口基本使用,实现类型复用,了解内置泛型接口

// 对象,获取单个ID函数,获取所有ID函数,
//ID的类型肯定是一致的,但是可能是数字可能是字符串
interface IdFn<T> {
  id: () => T;
  ids: () => T[];
}

const idObj: IdFn<number> = {
  id() { return 1 },
  ids() { return [1, 2] },
};
  • 在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口,接口中所有成员都可以使用类型变量。

内置的泛型接口:

const arr = [1, 2, 3];
// TS有自动类型推断,其实可以看做:const arr: Array<number> = [1, 2, 3]
arr.push(4);
arr.forEach((item) => console.log(item));
  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键) 去查看内置的泛型接口

泛型函数

掌握:泛型函数基本使用,保证函数内类型复用,且保证类型安全

// 函数的参数是什么类型,返回值就是什么类型
function getId<T>(id: T): T {
  return id
}

let id1 = getId<number>(1)
let id2 = getId('2')
// TS会进行类型推断,参数的类型作为泛型的类型 getId<string>('2')

小结

  • 泛型函数语法?
    • 函数名称后加上 , T是类型参数,是个类型变量,命名建议遵循大驼峰即可。
  • T 什么时候确定?
    • 当你调用函数的时候,传入具体的类型,T 或捕获到这个类型,函数任何位置均可使用。
  • 泛型函数好处?
    • 让函数可以支持不同类型(复用),且保证类型是安全的。
  • 调用函数,什么时候可以省略泛型?
    • 传入的数据可以推断出你想要的类型,就可以省略。
// 我需要的类型 { name: string, age?: number } 但是推断出来是 { name: string}
let id2 = getId({name:'jack'})

总的代码如下:

/**  
 * 泛型
 *  类型传参
 *  类比 函数 函数是不是可以传参数
 *      类型 也可以传参数
 * 泛型别名 
 * 语法  类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 */
{
    type MyObj<A = number, B = string> = {
        a: A
        b: B
    }
    // type MyObj1 = {
    //     a: number;
    //     b: string;
    // }
    // type MyObj2 = {
    //     a: boolean;
    //     b: null;
    // }
    const obj1: MyObj<{}, Date> = {
        a: {},
        b: new Date()
    }
    obj1
    const obj2: MyObj<boolean, undefined> = {
        a: true,
        b: undefined
    }
    obj2
}

交叉类型演示后端接口数据

/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 * 
 * {
 *  code: 200,
 *  msg: 'xxx',
 *  data: 数据
 * }
 * 
 * 在我们没学泛型前 会用交叉类型做复用,但在这样的例子中不太合适
 *     type UserData = CodeAndMsg & { data: UserList }
 *     type ArticelData = CodeAndMsg & { data: ArticleDetail }
 * 用交叉类型还是有很多重复的代码
 */
{
    // 交叉类型演示 开始
    type CodeAndMsg = {
        code: number
        msg: string;
    }
    type User = {
        name: string
        age: number
    }
    // number[] string[] User[]
    type UserList = User[]
    type UserData = CodeAndMsg & { data: UserList }
    const userData: UserData = {
        code: 200,
        msg: '获取成功',
        data: [
            {
                name: 'zs',
                age: 18
            }
        ]
    }
    userData
    type ArticleDetail = {
        id: string
        content: string
    }
    /**
     * {
     *  code: 200,
     *  msg: '获取详情成功'
     *  data: {
     *      id: '',
     *      content: ''
     *  }
     * }
     */
    type ArticelData = CodeAndMsg & { data: ArticleDetail }
    const ArticleData: ArticelData = {
        code: 200,
        msg: '获取成功',
        data: {
            id: '1',
            content: '文章'
        }
    }
    ArticleData

    // 交叉类型演示结束
}

泛型别名演示后端数据

/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  type xxx<类型参数A = 可以设置默认值, 类型参数B ...> = {
 *      字段A: 类型参数A
 *      。。。。
 *  }
 * 
 * {
 *  code: 200,
 *  msg: 'xxx',
 *  data: 数据
 * }
 * 
 * 在我们没学泛型前 会用交叉类型做复用,但在这样的例子中不太合适
 *     type UserData = CodeAndMsg & { data: UserList }
 *     type AraticelData = CodeAndMsg & { data: ArticleDetail }
 * 用交叉类型还是有很多重复的代码,需要用泛型优化
 *      type UserData = ApiData<UserList>
 *      type ArticelData = ApiData<ArticleDetail>
 */
{
    // 一般只有一个类型参数,规范上起名会写大写的T 意思就是Type
    type ApiData<T> = {
        code: number
        msg: string;
        data: T
    }
    type User = {
        name: string
        age: number
    }
    // number[] string[] User[]
    type UserList = User[]
    type UserData = ApiData<UserList>
    const userData: UserData = {
        code: 200,
        msg: '获取成功',
        data: [
            {
                name: 'zs',
                age: 18
            }
        ]
    }
    userData
    type ArticleDetail = {
        id: string
        content: string
    }
    /**
     * {
     *  code: 200,
     *  msg: '获取详情成功'
     *  data: {
     *      id: '',
     *      content: ''
     *  }
     * }
     */
    type ArticelData = ApiData<ArticleDetail>
    const ArticleData: ArticelData = {
        code: 200,
        msg: '获取成功',
        data: {
            id: '1',
            content: '文章'
        }
    }
    ArticleData
}

泛型接口-内置的泛型接口-Array

/**  
 * 泛型接口 - 了解内置的泛型接口
 * 我们之前已经学过了?interface Array<T>
 * 面试官问你 什么是泛型 回答:比如我先介绍下内置的泛型接口 interface Array<T>
 * 类型传参
 */
{
    // number[]
    let arr: Array<string | number> = ["1, 2, 3"]
    arr.forEach(item => {
        item
    })
    // push方法是干什么用的 在数组后面添加 返回值是什么 添加后的数组长度
    arr.push(1,2,3)
}

手写泛型接口
泛型函数:语法 在执行的括号前面加尖括号 - 箭头函数和普通函数都适用

/**  
 * 泛型接口 - 自己写泛型接口
 * 接口名后面加尖括号
 * 语法 interface xxx<T>
 * 
 * 公司量产机器人 分为id是数字型的机器人 id是字符串型的机器人 。。。。
 * sayHi返回值 把id返回
 */
{
    interface Robot<T = number> {
        id: T
        sayHiA(id: T): T
        sayHiB?: (id: T) => T
    }

    const numberRobot: Robot = {
        id: 1,
        sayHiA(id) {
            return id
        }
    }
    numberRobot
    const stringRobot: Robot<string> ={
        id: '1',
        sayHiA(id) {
            return id
        }
    }
    stringRobot
}



  /**  
 * 泛型函数
 * 语法 在执行的括号前面加尖括号 - 箭头函数和普通函数都适用
 * 
 */
{
    function fn<T>(a: T, b: T) {
        console.log(a, b);
    }
    function fn1(a: number, b: number) {
        console.log(a, b);
    }
    fn1(1, 2)
    function fn2(a: string, b: string) {
        console.log(a, b);
    }
    fn2('1', '2')

    // 隐式 通过类型推导
    // 如果类型推导,推导出来的就是你想要的类型,你尖括号泛型可以省略
    fn('1', '2')
    // 显式 自己指定类型
    fn<boolean>(true, false)

    const foo = <A, B>(a: A, b: B) => {
        console.log(a, b);
    }
    foo<number, undefined>(1, undefined)
}

15-补充-泛型函数-不能随意想当然乱写 泛型T加T会报错
比如一个数组+另外一个数组,一个正则加一个正则,返回的并不是你想要的结果并且没有任何的意义。

/**  
 * 泛型函数 - 补充 不能想当然的乱写泛型
 * 比如 T+T会报错
 * 语法 在执行的括号前面加尖括号 - 箭头函数和普通函数都适用
 */
{
    // function add<T>(a: T, b: T) {
    //     console.log(a, b);
    // }
    function add1(a: number, b: number) {
        return a + b
    }
    add1(1, 2)
    function add2(a: string, b: string) {
        return a + b
    }
    add2('a', 'b')
}

day03总结

  1. 类型推断----ts自己会帮你推论出类型 鼠标帮你摸一摸
  2. 字面量类型介绍 -----写死字面量类型—数据就不能改了
  3. 字面量类型应用-类型更加精确–举例性别和方向—配合联合类型** |**
  4. any类型—任意类型—逃避ts检查–不建议使用—用了相当于写js
  5. 函数参数给初始值 —直接等于—不用加?–本质上也是可选
  6. 初识类型断言—as–断言— 目的是比ts更确定一个类型,要确定的才断言
  7. 类型断言-img元素赋值src----练习
  8. 泛型别名—泛型本质是类型传参–语法<>写在别名后面 尖括号传你想要的参数
  9. 泛型别名模拟接口数据上-不建议使用交叉类型**&**-复用性没有泛型好
  10. 泛型别名模拟接口数据下-使用泛型优化----提高复用和灵活性

code msg 固定 data传一个T---- 动态传入的类型传参

  1. 泛型接口-内置的泛型接口-Array

数组,Array<>–专业术语–泛型接口 数组的方法push forEach方法 拿到T类型

  1. 泛型接口-手写泛型接口-接口名后加尖括号

泛型接口,在接口名后面加尖括号
泛型别名:别名后面加尖括号—概念 类型传参

  1. 泛型函数-在执行小括号前面加尖括号–想传几个就传几个
  2. 补充-泛型函数-不能随意想当然乱写泛型T加T会报错
  3. 上午总结
  4. ts实现访问历史记录的功能—综合案例
  5. 综合练习-formatTime基本实现-date对象上的方法
  6. 综合练习-formatTime扩展支持传字符串传date对象-可选参数-类型守卫
  7. 综合练习-时间前面补0操作-padStart
  8. 综合练习-定义数据格式类型-分析对象数组-对象里有2个属性
  9. 综合练习-本地存取时间数据-localStorage
  10. 综合练习-拿到本地数据渲染页面-数组转成字符串-map-join

综合案例-刷新一次,记录一次

代码如下:刷新页面后,展示访问历史记录,记录包含:次数和时间。

/**
 * 综合练习
 * 刷新页面后,展示访问历史记录,记录包含:次数和时间。
 * 
 * 1. 时间戳 => 时分秒 
 *      date对象上才有对应获取时分秒的方法
 *         时 getHours
 *         分 getMinutes
 *         秒 getSeconds
 * 
 *     formtTime() 可以不传参数 不传参数直接获取当前时间 转换时分秒
 *     formatTime('2023-07-07 01:00:00') 可以传时间格式的字符串 转换时分秒
 *      formatTime(new Date()) 可以传date类型数据,转换时分秒
 *      
 *      时分秒如果只有一位的 需要前面补0 01:00:00
 *      字符串方法 padStart(最大长度, ’补的字符串‘)
 *      '1'.padStart(2, '0') 往前补, 一共补2位, 用0补
 * 2. 定义次数和时间 数据格式
 *      是个对象数组
 *      对象里有2个属性 次数 - number 时间 - string
 * 3. 数据持久化 localStorage
 *      定义个变量 key 
 *      getItem 取数据
 *      setItem 存数据
 *      先实现取数据 为什么?
 *       1. 添加数据要先获取老的数据(数组)
 *        2.  然后在通过push方法添加最新的数据
 *          3.存到本地
 *  4. 渲染
 *      1. 获取本地的数据
 *      2. 数据渲染到页面        
 */
{
  const formatTime = (date?: string | Date) => {
    // 可能没有 当前时间
    if (!date) date = new Date()
    // 字符串 => 时间date对象
    if (typeof date === 'string') date = new Date(date)
    // 代码走到这里的时候 date必然是个Date类型 
    // date对象才能调用以下3个方法转成时分秒
    const h = date.getHours().toString().padStart(2, '0')
    const m = date.getMinutes().toString().padStart(2, '0')
    const s = date.getSeconds().toString().padStart(2, '0')
    // xx:xx:xx
    return `${h}:${m}:${s}`
  }
  // console.log(formatTime());
  // console.log(formatTime('2023-07-07 01:00:00'));
  // console.log(formatTime(new Date()));

  // 对象数组里的每一项 就是一个Time对象
  type Time = {
    count: number
    time: string
  }

  // 页面展示的列表数据类型
  type List = Array<Time>

  const key = 'vue3-ts-108-time-record'

  // 取数据 - 数组 即使没有数据也希望返回空数组
  function getData () {
    const str = localStorage.getItem(key)
    // 因为我们确定返回的数据就是个对象Time数组 所以可以用类型断言,返回值就是List数据 
    return JSON.parse(str || '[]') as List
  }

  // 存数据
  function setData () {
    // 1. 获取老的数据 要么是有数据的数组 要么是空数组
    const list = getData()

    // 2. push新的数据 Time数据 次数 时间 添加好新的数据
    list.push({
      // 假设是空数组 添加数据 次数1, length原本是0 length加1即可
      // 假设里面是有数据的数组 举例 2条数据 新加的数据 次数3 也是length+1
      // count的逻辑 就是length + 1
      count: list.length + 1,
      time: formatTime()
    })
    // 3. 存到本地
    localStorage.setItem(key, JSON.stringify(list))
  }
  // setData()
  function render () {
    // 每次一刷新 调用render 要记录新的数据
    setData()
    const list = getData()
    // 获取div#app的元素
    const el = document.querySelector('#app') as HTMLDivElement
    // console.log(list);
    // 元素里渲染数据 - innerHTML
    // 对象数组转成字符串
    el.innerHTML = list.map(item => `次数:${item.count} 时间: ${item.time}`).join('<br/>')
  }
  render()
}

js基本数据为什么能点操作

https://blog.csdn.net/tanxin2721/article/details/112306417
在 JavaScript 中,基本数据类型(如字符串、数字、布尔值等)是不可变的,也就是说它们的值是不可被修改的。当你给基本数据类型设置属性时,实际上是在创建一个临时包装对象,该对象具有该属性,但是对象在跳出作用域后会立即被销毁,而且不会对原始的基本数据类型产生影响。
让我们来看一个示例:

let num = 10;
num.foo = 'bar';
console.log(num.foo); // undefined

在这个例子中,我们给一个数值类型的变量 num 设置了一个属性 foo。但是,当我们尝试访问 num.foo 时,返回的是 undefined。这是因为在设置属性时,JavaScript 会暂时创建一个临时对象,该对象具有该属性,但是在访问时已经被销毁,因此我们无法访问到它。

相反,如果你想给一个对象类型的变量设置属性,那么这个属性将会被正确地添加到该对象,并且在后续的访问中仍然可见。

let obj = {};
obj.foo = 'bar';
console.log(obj.foo); // 'bar'

在这个示例中,我们给一个空对象 obj 设置了一个属性 foo,并且我们可以成功地访问到这个属性。

总结来说,基本数据类型的属性设置不会产生预期效果,因为这些属性只存在于临时对象中,并且会在对象离开作用域后被销毁。如果你需要给一个变量设置属性,那么变量应该是一个对象类型。

day03

应用就是说和vue结合在一起

TypeScript与Vue

typescript 配合 Vue3 composition-api 使用
https://staging-cn.vuejs.org/guide/typescript/composition-api.html
前提:script 加上 lang=“ts” 才能写ts代码

defineProps的TS写法

  1. defineProps 的基本使用:
const props = defineProps({
  money: {
    type: Number,
    required: true
  },
  car: {
    type: String,
    required: false,
    default: '宝马车'
  }
})
console.log(props.money) // number
console.log(props.car) // string | undefined
  1. defineProps 通过泛型参数来定义 props 的类型通常更直接: TS写法

看提示写泛型!!!之前是大写Number vue2中也是大写,因为是JS基本数据类型
泛型是小写
大写Number 包装类构造函数
image.png
原理:赋值构造函数转成包装类,获取属性的时候已经销毁了
JS基本数据类型可以点操作,用完就会销毁

const props = defineProps<{
  money: number
  car?: string
}>()

泛型里面的类型是小写!!!
可选参数用问号,先看文档再去看视频
https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:
image.png
默认数组类型:返回工厂函数 和vue2中data要用一个函数,为了组件复用,新的对象,新的引用。

  1. 如果需要给 props 设置默认值,需要使用 withDefaults 函数:

两个参数,一个是defineProps 函数第二个是对象–默认值

const props = withDefaults(defineProps<{
  money: number;
  car?: string;
}>(),{
  car: '宝马车'
})
  1. 废弃的响应式语法-默认值用withDefaults

解构一般会丢失响应式

defineEmits的TS写法

30-defineEmits结合ts写法-配置泛型-函数返回值void-参数事件名-子传父的数据

  1. defineEmits 的基本用法:
const emit = defineEmits(['changeMoney', 'changeCar'])
  1. defineEmits 通过泛型参数来定义,可以实现更细粒度的校验:
const emit = defineEmits<{
  (e: 'changeMoney', money: number): void
  (e: 'changeCar', car: string): void
}>()

image.png

了解:扩展TS语法 调用签名

ref的TS写法

ref() 会隐式的 依据数据 推导类型

  1. 如果是简单类型,推荐使用类型推导
// const money = ref<number>(10)

const money = ref(10)

https://cn.vuejs.org/guide/typescript/composition-api.html#typing-ref

  1. 如果是复杂类型,推荐指定泛型:
type Todo = {
  id: number
  name: string
  done: boolean
}
const list = ref<Todo[]>([])

setTimeout(() => {
  list.value = [
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true }
  ]
}, 1000)

复杂数据一般是后台返回数据,默认值是空,无法进行类型推导。

课上代码:

<script setup lang="ts">
import { Ref, onMounted, ref } from 'vue';

// ref + ts 
// 简单数据建议使用类型推导 什么都不写 摸上去
const count: Ref<number> = ref(0)

const count=ref<number>() //number|undefined

  count
// 复杂数据建议使用泛型配置类型
type User = {
  name: string
  age: number
}
//泛型可以嵌套泛型
const userList = ref<User[]>([])
onMounted(() => {
  setTimeout(() => {
    userList.value = [
      {
        name: 'zs',
        age: 18
      }
    ]
  }, 2000)
})
</script>

reactive的TS写法

文档搜typescript—组合式API
reactive() 也会隐式的依据数据推导类型

  1. 默认值属性是固定的,推荐使用类型推导:
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3 在线医疗' })
  1. 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:
// 我们想要的类型:{ title: string, year?: number }
type Book = {
  title: string
  year?: number
}
const book: Book = reactive({ title: 'Vue3 在线医疗' })
book.year = 2022
  • 官方:不推荐使用 reactive() 的泛型参数,因为底层和 ref() 实现不一样。

https://cn.vuejs.org/guide/typescript/composition-api.html#typing-reactive
image.png

computed和TS#

  1. computed() 会从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'

const count = ref(100);
const doubleCount = computed(() => count.value * 2);
  1. 可以通过泛型参数显式指定类型:
const doubleMoney = computed<string>(
  () => (count.value * 2).toFixed(2) //string
);

https://cn.vuejs.org/guide/typescript/composition-api.html#typing-computed

总结

18-综合练习-formatTime基本实现-date对象上方法
19-综合练习-formatTime扩展–支持传字符串传date对象-可选参数-类型守卫
20-综合练习-时间前面补0操作-padStart
21-综合练习-定义数据格式类型-分析对象数组-对象里有2个属性
22-综合练习-本地存取时间数据-localStorage
23-综合练习-拿到本地数据渲染页面-数组转成字符串-map-join-br
24-vue结合ts说明-搭架子-script标签上加lang值是ts
25-defineProps结合ts写法-配置泛型-配置对象-属性和类型
26-补充-大写Number-上
27-补充-大写Number-下
28-withDefaults设置父传子的默认值-默认值配置第二个参数,函数工厂函数
29-废弃的响应式语法-默认值用withDefaults–不要解构
30-defineEmits结合ts写法-配置泛型-函数返回值void**-参数事件名-子传父的数据
31-ref结合ts写法-简单建议
类型推导**-复杂建议配置泛型
32-作业-自学reactive和computed.itcast
33-day03下午总结
作业
01-reactive结合ts-不推荐用泛型-直接用类型注解
02-computed结合ts-泛型函数-括号前面加尖括号

day04-0710

事件处理与TS#

  1. 不加类型,event默认是any,类型不安全:
<script setup lang="ts">
  // 提示:参数“event”隐式具有“any”类型。  
const handleChange = (event) => {
  console.log(event.target.value)
}
  </script>

  <template>
  <input type="text" @change="handleChange" />
  </template>
  1. 处理类型:
// `event` 隐式地标注为 `any` 类型,如何指定:event 类型?
// 1. @change="handleChange($event)"" 查看$event类型
// 2. 鼠标摸一下事件 @change 查看类型
const handleChange = (event: Event) => {
  // `event.target` 是 `EventTarget | null` 类型,如何指定具体类型?
  // document.querySelector('input') 查看返回值类型
  console.log((event.target as HTMLInputElement).value)
}

总结代码

<script setup lang="ts">
/**
 * 获取表单框里的内容 e来获取 e.target.value
 * 类型注解如何去配置 
 *  传参$event 用鼠标摸一摸
 */
const handleInput = (e: Event) => {
  // 类型断言
  console.log((e.target as HTMLInputElement).value);
}
</script>

<style scoped>

Template Ref与TS#

模板 ref 需要通过一个显式指定的泛型参数,建议默认值 null

  • 注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。
  • 这是因为直到组件被挂载前,这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null。

总结代码

 <input type="text" ref="inputRef">
   
<script setup lang="ts">
import { onMounted, ref } from 'vue';

/**
 * 页面中有一个input元素
 * input元素自动聚焦功能
 * 
 * 1. 先获取元素(ref获取元素)ts需要配置联合类型,因为初始值是null
 挂载以后才能拿到input元素
 *    null | HTMLInputElement
 *    如果我们在这个元素上使用了v-if 且值是false 把这个元素销毁了,
 他会自动把值改成null
 * 2. 挂载后 调用focus的方法
 */
const inputRef = ref<null | HTMLInputElement>(null)
onMounted(() => {
  // 可选链
  // inputRef.value?.focus()
  // 类型守卫
  if (inputRef.value) {
    inputRef.value.focus()
  }
})
</script>

非空断言#

处理类型可能是 null 或 undefined 的值,下面的属性或函数的访问赋值:

  1. 可选链
<script setup lang="ts">
  import { onMounted, ref } from 'vue';


  const input = ref< HTMLInputElement | null >(null)

  onMounted(()=>{
    // 只能访问
    console.log(input.value?.value);
  })

</script>

<template>
  <div>App组件</div>
  <input type="text" ref="input" value="abc">
  </template>
  1. 逻辑判断
if (input.value) {
  console.log(input.value.value)
  input.value.value = '123'
}
  1. 非空断言
// 一定要确定不为空!!!
console.log(input.value!.value)
input.value!.value = '123'

总结代码

<script setup lang="ts">
import { onMounted, ref } from 'vue';

/**
 * input框里面的内容赋值 
 * input框里没有内容 赋值sf input元素.value = 'sf'
 * 该逻辑也在元素挂载后再处理
 * 
 * 非空断言的语法,就是当你确定这个东西是有的时候,在这个东西后面加上!
 * 举例在这个例子中 inputRef.value.value = 'sf'
 * ts觉得是inputRef.value可能是空,但是在这个例子中这个inputRef.value不可能为空
 * 所以我们可以使用非空断言,语法就是在ts觉得为空的这个东西后面加!
 * inputRef.value!这个意思断言他非空
 * inputRef.value!.value = 'sf'
 */
// const show = ref(true)
const inputRef = ref<null | HTMLInputElement>(null)
onMounted(() => {
  // 可选链不能用 可选链只能用于获取值,不能用于赋值
  // inputRef.value?.value = 'sf'
  // if (inputRef.value) {
  //   inputRef.value.value = 'sf'
  // }
  inputRef.value!.value = 'sf'
})
</script>

TypeScript类型声明文件#

typescript 类型声明文件相关知识
.d.ts 类型声明文件

内置类型声明文件#

知道:什么是内置的类型什么文件
发现,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:

TypeScript 给 JS 运行时可用的所有标准化内置 API 都提供了声明文件,这个声明文件就是 内置类型声明文件

  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
    • 查看 forEach 的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts 类型声明文件中
    • 像 window、document 等 BOM、DOM API 也都有相应的类型声明文件 lib.dom.d.ts

第三方库类型声明文件#

掌握:给第三方库添加对应的类型声明文件
首先,常用的第三方库都有相应的类型声明文件,只是使用的方式不同而已。
情况1:库本身自带类型声明文件

  • 比如:axios,安装后可查看 node_modules/axios 可发现对应的类型声明文件。
  • 导入 axios 后就会加载对应的类型文件,提供该库的类型声明。

情况2:由 DefinitelyTyped 提供

  • 比如:jquery,安装后导入,提示:需要安装 @types/jquery 类型声明包
  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
  • 当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明

image.png

自定义类型声明文件

共享类型(重要)#

掌握:使用类型声明文件 提供 需要共享的 TS类型

  • 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
  • 操作步骤:
    1. 创建 index.d.ts 类型声明文件。
    2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
    3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。

image.png
记得写导入时侯要写type ,为了符合规范!!!!!
imd export

给JS文件提供类型#

了解:使用类型声明文件 给JS文件 添加类型

  • 在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。

  • declare 关键字:

    • 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
    1. 对于 type interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
    2. 其他** JS 变量**,应该使用 declare 关键字,明确指定此处用于类型声明。
  • export 导出方法

省流:
image.png
image.png

上午总结

02事件与TS-参数传$event-鼠标摸一摸itcast
03-TemplateRef与TS-获取元素-null和元素类型做联合类型
04-非空断言-感叹号语法-可选链不能赋值-一定要确定的东西才能断言.itcast
05-类型声明文件基本介绍-ts-又可以写类型又可以写执行代码-dts-只能写类型itcast
06-内置类型声明文件-数组和dom等方法有内置的dts文件.itcast
07-第三方库类型声明文件-第三方库自带的类型声明文件-举例axios.itcast
08-第三方库类型声明文件-第三方库没有自带类型声明文件-举例jq-装@types xxxitcast
09-共享类型-重要-自己写一个dts文件复用-写好要导出-使用时导入.itcast
10-给is文件提供类型-了解-declare-最后记得导出方法.itcast
11-黑马头条-综合案例介绍.itcast
12-黑马头条-搭建结构-cv样式和结构.itcast
13-黑马头条-axios配置响应数据类型-上-请求别名函数!!!配置itcast14-黑马头条-axios配置响应数据类型-下-泛型第一个参数是返回数据类型.itcast

TS 黑马头条案例

第一步:搭建框架—基础结构–别忘记导入样式
第二步:掌握axios配合泛型,设置响应数据类型
小结:

  • 使用axios的时 候怎么给返回数据 提供类型?
    • axios.request<数据类型>() 其他请求方法类似
  • 提供的类型要注意啥?
    • 类型需要根据接口返回的数据类声明,或者根据接口文档

配置泛型–返回值类型

image.png
配置泛型参数,第一个参数就是返回值的类型,第一个参数就是返回值的类型,第一个参数就是返回值的类型,第一个参数就是返回值的类型,第一个参数就是返回值的类型

json2TS插件

shift+ctrl+alt+v 导出导入

Convert JSON object to typescript interfaces

渲染数据

image.png

导航切换-点那个那个高亮

记录高亮的id activeId===item.id 当前的Id 就激活active类
文章列表也用到了activeId
状态提升–数据声明到父组件–App.vue
image.png
https://gitee.com/qianfengg/vue3-ts-108/commit/f14af12cfc0b9e22ff5334a309968b090ff4196c
defineProps《{}》()

分析列表渲染请求

image.png
监听对象中的一个属性—一个函数返回一个值
https://gitee.com/qianfengg/vue3-ts-108/commit/4a957fffe38ae23c37a5a7de420406fe0808cc31
image.png
表示文章的类型,控制台粘贴返回的数据,用插件的快捷键!!导出导入
image.png

列表渲染

声明一个响应式数据,赋值,渲染

  1. ref简单数据–什么都不写—类型推导

复杂数据–配置泛型----摸一下返回的结果是Ariticle[] ,c一下

  1. 赋值
  2. 渲染之前看一下控制台

类型声明文件写的详细,点出来的内容就更多

接口文档快速生成

接口文档生成TS类型声明文件-后端接口文档给力的话可以生成代码.itcast

Pinia

https://zhoushugang.gitee.io/patient-h5-note/pinia/
image.png

  • 可以创建多个全局仓库,不用像 Vuex 一个仓库嵌套模块,结构复杂。
  • 管理数据简单,提供数据和修改数据的逻辑即可,不像Vuex需要记忆太多的API。

小结:

  • Pinia 是一个简单实用的状态管理工具,和菠萝一样 香

Pinia 15分钟上手#

掌握:实用Pinia使用,管理计数器的状态
https://pinia.vuejs.org/zh/getting-started.html
image.png
image.png

  1. 安装pinia
  2. 创建实例,使用pinia插件
  3. 在count.ts中定义,创建仓库,按需导出
  4. defineStore(‘唯一标识’,箭头函数) 箭头函数里面提供数据和修改方法
  5. 按需导入 使用仓库
  6. 在控制台上看可以看见pinia

image.png
注意:getters是computed函数!!!其他同步异步是普通函数
注意定义仓库后要返回属性和方法
image.png
image.png
const xxx=computed(()=>{})

官方文档pinia

https://pinia.vuejs.org/zh/core-concepts/

storeToRefs解决解构丢失响应式函数

数据改变–视图没有更新
image.png
小结:

  • 当你想从 store 中解构对应的状态,需要使用 storeToRefs
  • 解构方法 不要响应式,直接解构即可!!!
<template>
  <div>
<!-- <p>{{ numStore.num }}</p>
<p>{{ numStore.threeAdd }}</p>
<p @click="numStore.add">click+1</p>
<p @click="numStore.asynAdd">click+2</p> -->
<p>{{ num }}</p>
<p>{{ threeAdd }}</p>
<p @click="add">click+1</p>
<p @click="asynAdd">click+2</p>
  </div>
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useNumStore } from './store/test';
const numStore = useNumStore()
const { num, threeAdd } = storeToRefs(numStore)
const {add,asynAdd}=numStore
</script>

<style scoped>

</style>

pinia改造需求

具体看文档笔记:
https://zhoushugang.gitee.io/patient-h5-note/pinia/#case

配置代码片段

地址:https://snippet-generator.app/
老师的代码

{
  "vue3base": {
    "prefix": "gqfVue3Base",
    "body": [
      "<script setup></script>",
      "",
      "<template>",
      "  <div class=\"$1-page\">",
      "    <h1>$1</h1>",
      "  </div>",
      "</template>",
      "",
      "<style scoped></style>",
      ""
    ],
    "description": "vue3base"
  },
  "vue-ts-scss template": {
    "prefix": "gqfVue3TS",
    "body": [
      "<script setup lang=\"ts\"></script>",
      "",
      "<template>",
      "  <div class=\"$1-page\">",
      "    <h1>$1</h1>",
      "  </div>",
      "</template>",
      "",
      "<style scoped lang=\"scss\"></style>",
      ""
    ],
    "description": "vue-ts-scss template"
  }
}

补充unknown类型

image.png

<script setup lang="ts">
// let obj:any = {
//   name: 'cc',
//   age:18
// }
// any不报错,unknown方法上报错:obj的类型未知
// any逃避规则 
let obj: unknown = {
  name: 'cc',
  age: 18
}
obj = 2
// obj()
// obj.a
// 解决办法如下  加上类型守卫 或者类型断言
if (typeof obj === 'object' && obj !== null && 'a' in obj) {
  obj.a
}
if (typeof obj === 'function') {
  obj()
}
let foo: number | unknown = 1
foo //foo就是unknown类型 联合类型  若是any还是any
</script>

补充never类型

Never类型:那些永不存在的值
项目课程:utility types—泛型编程

<template>
  <div>
    <!-- 1. unknown 与 any 区别 -->
    <!-- 2. never类型 -->
  </div>
</template>

<script setup lang="ts">
// 函数永远不会返回, 返回值类型 never
// 抛出异常 || 无限循环
function fn1 (): never {
  throw new Error('哈哈')
}
fn1
function fn2 (): never {
  while(true) {

  }
}
fn2
// 项目课 utility types - 类型编程
// type ObjA = {
//   a: number,
//   b: string
// }
// type PObjA = Partial<ObjA>

</script>

<style scoped>

</style>

extends受到约束
泛型传参

16-黑马头条json2TS插件演示并提取到dts文件处理共享类型 shift+ctrl+alt+v
17-黑马头条-渲染频道数据-染数据前看vue调试工具.itcast
18-黑马头条-切换导航-状态提升-因为子组件都用到高亮的频道id-声明在父组件.itcast
19-黑马头条-列表更新-发送请求-watch-immediate进入发送请求-监听频道id的变化itcast
20-黑马头条-列表更新-渲染列表.itcast

21-接口文档生成TS类型声明文件-后端接口文档给力的话可以生成代码.itcast
22-pinia介绍-是个共享状态的库-方便管理数据共享状态itcast
23-pinia15分钟上手-ref响应式数据-普通函数-computed函数itcast
24-pinia官方文档复盘.itcast export const useXXXStore=defineStore(‘xxx’,回调函数)
25-storeToRefs解决解构丢失响应式函数itcast
面试技巧—是什么,为什么,怎么用

26-头条综合案例环境处理并说明pinia改造需求.itcast
27-pinia改造头条.itcast
28-day04下午总结itcast
29-补充-代码片段itcast
30-补充-unknown类型.itcast
31-补充-never类型itcast

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue3中使用的是Composition API,而不是原来的Options API,因此原型和原型链的应用与Vue3+ts+setup的案例可能不太相关。不过,我们可以举一个简单的ts+class的例子来说明原型和原型链的应用。 假设我们有一个Animal类: ```typescript class Animal { name: string; constructor(name: string) { this.name = name; } eat(food: string) { console.log(`${this.name} is eating ${food}.`); } } ``` 我们可以创建一个Cat类,继承自Animal类: ```typescript class Cat extends Animal { constructor(name: string) { super(name); } meow() { console.log(`${this.name} is meowing.`); } } ``` 现在我们可以创建一个cat实例: ```typescript const cat = new Cat('Kitty'); ``` 我们可以调用cat实例上的eat和meow方法: ```typescript cat.eat('fish'); // 输出:Kitty is eating fish. cat.meow(); // 输出:Kitty is meowing. ``` 在这个例子中,Animal类是Cat类的父类,Cat类从Animal类中继承了属性和方法。当我们创建一个cat实例时,该实例实际上是Animal类的一个实例,同时也是Cat类的一个实例。因此,cat实例的原型链如下所示: ``` cat -> Cat.prototype -> Animal.prototype -> Object.prototype -> null ``` 其中,cat指向Cat.prototype,Cat.prototype指向Animal.prototype,Animal.prototype指向Object.prototype,Object.prototype指向null。这个原型链可以让cat实例继承Animal类和Object类上的属性和方法。例如,我们可以调用cat实例上的toString方法: ```typescript console.log(cat.toString()); // 输出:[object Object] ``` 这是因为cat实例继承了Object.prototype上的toString方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值