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:

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

vite 的原理:

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

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

问题:

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

vite 创建项目

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

  1. 运行创建项目命令:
javascript 复制代码
# 使用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插件。
  1. 总结 vue3 写法不同
    1. 组件一个根节点非必需
    2. 创建应用挂载到根容器
    3. 入口页面,ESM 加载资源
javascript 复制代码
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

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函数声明,返回模板需要数据与函数。
vue 复制代码
<script>
  export default {
    setup() {
      const say = () => console.log('hi')
      return { say }
    }
  }
</script>

解法:

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

快捷插件

Vue VSCode Snippets插件

直接输入 v3 tab选择

JavaScript (ES6) code snippets

imp imd nfn箭头函数

computed 函数

响应式 API:核心 | Vue.js

场景:当需要依赖一个数据得到新的数据使用计算属性

函数或者对象的形式

watch函数

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

https://cn.vuejs.org/api/reactivity-core.html#watch

大致内容:

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

落地代码:

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


第三种:使用 watch 监听响应式对象数据中的一个属性(简单)

:::info

精确侦听对象的某个属性

一个函数,返回一个值

:::

第四种:使用 watch 监听响应式对象数据中的一个属性(复杂),配置深度监听

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

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

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

总结:

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

:::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

使用方式 导入,调用该函数,参数是回调函数,在回调函数中写业务逻辑

Vue2 Vue3
created setup
mounted onMounted
destoryed onUnmounted
选项式API下的生命周期函数使用 组合式API下的生命周期函数使用
beforeCreate 不需要(直接写到setup函数中)
created 不需要(直接写到setup函数中)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated

引入组件 vue2导入注册使用,Vue3导入使用

import 组件名 from 'xxx路径'

组件名驼峰或者连字符的写法

vue 复制代码
<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属性。

vue 复制代码
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

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

父传子-defineProps函数

:::info

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

:::

不需要导入就可以使用

目标:能够实现组件通讯中的父传子组件通讯

步骤:

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

注意:

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

App.vue

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

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

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

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 保持响应式
vue 复制代码
<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 属性映射数据

vue 复制代码
<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
javascript 复制代码
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

全局安装:

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

查看版本

bash 复制代码
tsc -v

编译 TS:

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

思考:

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

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

无法重新声明块范围变量 如何解决-包个块级作用域

解决给一个块级作用域!!!!

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

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

插槽后备内容啥的xxxxx

创建 vue-ts 项目

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

bash 复制代码
# 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 类型注解 【我的理解:注释&解释】

示例代码:

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

错误演示:

typescript 复制代码
let age: number = 18;
// 报错:不能将类型"string"分配给类型"number"
age = '19';

小结:

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

补充--错误不提示

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

补充-插件

error-lens插件

及mainjs报错信息处理

原始类型

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

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

TS 常用类型:

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

原始类型:

  • 使用简单,完全按照 JS 的类型来书写即可
typescript 复制代码
/**
 * 原始类型
 * 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尖括号里面写类型

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

typescript 复制代码
/**
 * 数组类型
 * 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 类型,这个数组的类型如何书写?

typescript 复制代码
/**
 * 联合类型
 * 语法 类型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 类型别名 等号 类型

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

示例代码:

typescript 复制代码
/**
 * 类型别名
 * 是什么
 *  给类型起别名
 * 怎么用
 *  关键字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-函数类型-基本使用-分别指定参数和返回值都加类型注解-同时指定-类型别名

掌握:给函数指定类型

  • 给函数指定类型,其实是给 参数 和 返回值 指定类型。
  • 两种写法:
    • 在函数基础上 分别指定 参数和返回值类型
    • 使用类型别名 同时指定 参数和返回值类型
typescript 复制代码
/**
 * 函数类型-基本使用
 * 本质上就是 给参数和返回值 加类型注解
 * 两种写法
 *  分别指定
 *      语法(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
typescript 复制代码
/**
 * 函数类型-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 函数,定义函数参数类型

typescript 复制代码
/**
 * 函数类型-可选参数 返回值是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) 这样是不行的

1+undefined=NaN

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

写了默认值就代表end是可选参数,TS帮你识别出来了

问号写在冒号前面

对象类型

26-对象类型-基本使用-单行-一定要加分号或者逗号-多行可以省略-语法 属性 冒号 类型

27-对象类型-扩展用法-箭头函数-属性可选-类型别名写法

基本使用

掌握:对象类型语法

  • TS 的对象类型,其实就是描述对象中的 属性 方法 的类型,因为对象是由属性和方法组成的。
  • 先写JS 摸一摸 直接copy!!!类型注解
typescript 复制代码
/**
 * 对象类型-基本使用
 */
{
  // 空对象 :{}
  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

扩展用法

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

  • 函数使用箭头函数类型 搭个架子
typescript 复制代码
/**
 * 对象类型-扩展用法
 * 函数使用箭头函数类型,
 *  写法不同,但意思是等价的
 *  属性(参数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: '',

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

小结:

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

对象类型-综合练习

练习

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

  • 属性:必选属性:姓名、性别、成绩,可选属性:身高
  • 方法:学习、打游戏(可选)
typescript 复制代码
/**
 * 对象类型-综合练习
 * 
 */
{
    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')

}

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

接口interface--类

基本使用

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

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

  • 接口声明是命名对象类型的另一种方式
typescript 复制代码
/**
 * 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 实现接口继承,达到类型复用

思考:

  • 有两个接口,有相同的属性或者函数,如何提高代码复用?可以将公共的属性或方法抽离出来,通过继承来实现复用
typescript 复制代码
/**
 * 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 (一个与的符号)

typescript 复制代码
/**
 * 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个不同


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

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

不同的点:

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

小结:

  • 它们都可以定义对象类型
  • 它们都可以复用,interface 使用 extends , type 使用 &
  • type 不能重复定义,interface 可以重复会合并

综合代码

typescript 复制代码
/**
 * 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 提示看到类型
typescript 复制代码
/**      
 * 类型推断(你不用写类型,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)
}

字面量类型-精确

二、字面量类型

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

例如:

const str2 = Hello TS!

str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS

  1. 字面量类型应用

类型更加精确-举例性别和方向-配合联合类型

配合联合类型来使用,表示:一组明确的可选的值

优势: 相比于 string 类型,使用字面量类型更加精确、严谨

字面量类型介绍

知道:什么是字面量类型

  • js 字面量如:18 'jack' ['a'] {age: 10} 等等。
  • 使用 js字面量 作为变量类型,这种类型就是字面量类型。
typescript 复制代码
// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;

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

字面量类型应用

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

知道:字面量类型的应用场景

例如:性别只能是 男 和 女,不会出现其他值。

typescript 复制代码
/**  
 * 字面量类型应用   
 * 一般用于联合类型配合字面量类型,能让我们类型更加的精准
 */
{
  // 比如 性别 如果用字符串类型 是不是范围太广不太符合逻辑 只有可能是男或者女
  //   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重命名

any 类型-逃避ts检查

:::success

三、any类型

任意类型逃避ts检查-不建议用any-用了相当于写js

作用: 逃避 TS 的类型检查

:::

typescript 复制代码
/**  
 * 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()
}

小结:

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

函数参数给初始值

:::tips

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

:::

类型断言

:::warning

四、类型断言

初识类型断言-as -比ts更确定一个类型-要确定了才能断言

例子--类型守卫--img元素赋值src练习

:::

有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,

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

综合代码-两种方法

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

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

练习-img元素赋值src

泛型-类型传参

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

泛型别名

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

掌握:泛型别名基本使用,实现类型复用

语法 名字后面加尖括号 传入你想要传入的类型,作为参数

a:A 类型

b:B类型

typescript 复制代码
/**  
 * 泛型
 *  类型传参
 *  类比 函数 函数是不是可以传参数
 *      类型 也可以传参数
 * 泛型别名 
 * 语法 名字后面加尖括号 传入你想要传入的类型,作为参数
 *  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
}

泛型别名模拟接口数据上

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

typescript 复制代码
/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  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

    // 交叉类型演示结束
}

小结:

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

泛型别名模拟接口数据下

typescript 复制代码
/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  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
}

泛型接口

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

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

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

内置的泛型接口:

typescript 复制代码
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 + 鼠标左键) 去查看内置的泛型接口

泛型函数

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

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

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

小结

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

总的代码如下:

vue 复制代码
/**  
 * 泛型
 *  类型传参
 *  类比 函数 函数是不是可以传参数
 *      类型 也可以传参数
 * 泛型别名 
 * 语法  类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  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
}

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

vue 复制代码
/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  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

    // 交叉类型演示结束
}

泛型别名演示后端数据

vue 复制代码
/**  
 * 泛型别名 练习 模拟发送请求后,拿到后端接口的数据
 * 语法 类型别名后加尖括号 传入你想要传入的类型,作为参数
 *  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

vue 复制代码
/**  
 * 泛型接口 - 了解内置的泛型接口
 * 我们之前已经学过了?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)
}

手写泛型接口

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

vue 复制代码
/**  
 * 泛型接口 - 自己写泛型接口
 * 接口名后面加尖括号
 * 语法 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会报错

比如一个数组+另外一个数组,一个正则加一个正则,返回的并不是你想要的结果并且没有任何的意义。

vue 复制代码
/**  
 * 泛型函数 - 补充 不能想当然的乱写泛型
 * 比如 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

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

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

typescript 复制代码
/**
 * 综合练习
 * 刷新页面后,展示访问历史记录,记录包含:次数和时间。
 * 
 * 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 中,基本数据类型(如字符串、数字、布尔值等)是不可变的,也就是说它们的值是不可被修改的。当你给基本数据类型设置属性时,实际上是在创建一个临时包装对象,该对象具有该属性,但是对象在跳出作用域后会立即被销毁,而且不会对原始的基本数据类型产生影响。

让我们来看一个示例:

javascript 复制代码
let num = 10;
num.foo = 'bar';
console.log(num.foo); // undefined

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

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

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 的基本使用:
typescript 复制代码
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 包装类构造函数

原理:赋值构造函数转成包装类,获取属性的时候已经销毁了

JS基本数据类型可以点操作,用完就会销毁

typescript 复制代码
const props = defineProps<{
  money: number
  car?: string
}>()

泛型里面的类型是小写!!!

可选参数用问号,先看文档再去看视频
https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props

当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:

默认数组类型:返回工厂函数 和vue2中data要用一个函数,为了组件复用,新的对象,新的引用。

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

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

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

解构一般会丢失响应式

defineEmits的TS写法

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

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

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

ref的TS写法

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

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

const money = ref(10)

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

  1. 如果是复杂类型,推荐指定泛型:
typescript 复制代码
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)

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

课上代码:

typescript 复制代码
<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. 默认值属性是固定的,推荐使用类型推导:
typescript 复制代码
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3 在线医疗' })
  1. 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:
typescript 复制代码
// 我们想要的类型:{ 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

computed和TS#

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

const count = ref(100);
const doubleCount = computed(() => count.value * 2);
  1. 可以通过泛型参数显式指定类型:
typescript 复制代码
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,类型不安全:
typescript 复制代码
<script setup lang="ts">
  // 提示:参数"event"隐式具有"any"类型。  
const handleChange = (event) => {
  console.log(event.target.value)
}
  </script>

  <template>
  <input type="text" @change="handleChange" />
  </template>
  1. 处理类型:
typescript 复制代码
// `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)
}

总结代码

typescript 复制代码
<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。

总结代码

typescript 复制代码
 <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. 可选链
vue 复制代码
<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. 逻辑判断
typescript 复制代码
if (input.value) {
  console.log(input.value.value)
  input.value.value = '123'
}
  1. 非空断言
typescript 复制代码
// 一定要确定不为空!!!
console.log(input.value!.value)
input.value!.value = '123'

总结代码

typescript 复制代码
<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 也会自动加载该类声明包,以提供该库的类型声明

自定义类型声明文件

共享类型(重要)#

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

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

记得写导入时侯要写type ,为了符合规范!!!!!

imd export

给JS文件提供类型#

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

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

  • declare 关键字:

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

省流:

上午总结

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<数据类型>() 其他请求方法类似
  • 提供的类型要注意啥?
    • 类型需要根据接口返回的数据类声明,或者根据接口文档

配置泛型--返回值类型

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

json2TS插件

shift+ctrl+alt+v 导出导入

Convert JSON object to typescript interfaces

渲染数据

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

记录高亮的id activeId===item.id 当前的Id 就激活active类

文章列表也用到了activeId

状态提升--数据声明到父组件--App.vue

https://gitee.com/qianfengg/vue3-ts-108/commit/f14af12cfc0b9e22ff5334a309968b090ff4196c

defineProps《{}》()

分析列表渲染请求

监听对象中的一个属性---一个函数返回一个值
https://gitee.com/qianfengg/vue3-ts-108/commit/4a957fffe38ae23c37a5a7de420406fe0808cc31

表示文章的类型,控制台粘贴返回的数据,用插件的快捷键!!导出导入

列表渲染

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

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

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

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

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

接口文档快速生成

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

Pinia

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

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

小结:

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

Pinia 15分钟上手#

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

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

注意:getters是computed函数!!!其他同步异步是普通函数

注意定义仓库后要返回属性和方法

const xxx=computed(()=>{})

官方文档pinia

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

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

数据改变--视图没有更新

小结:

  • 当你想从 store 中解构对应的状态,需要使用 storeToRefs
  • 解构方法 不要响应式,直接解构即可!!!
vue 复制代码
<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/

老师的代码

vue 复制代码
{
  "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类型

vue 复制代码
<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---泛型编程

vue 复制代码
<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

相关推荐
如若1233 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~1 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语1 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript