😬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博客
- 运行创建项目命令:
javascript
# 使用npm
npm create vite@latest
# 使用yarn
yarn create vite
# 使用pnpm
pnpm create vite
- 输入项目名称,默认是 vite-project
- 选择前端框架 Vue
- 选择项目类型 JS
- 创建完毕 cd 项目 yarn安装依赖 yarn dev运行项目
- 可以使用code .打开项目
安装依赖使用pnpm install
代码分析
对 vite 初始化的代码进行分析
- 需要切换插件
vue3 组件代码和 vue2 有些不一样,使用的语法提示和高亮插件也不一样。
- vetur 插件需要禁用,安装 volar插件。
- 总结 vue3 写法不同
- 组件一个根节点非必需
- 创建应用挂载到根容器
- 入口页面,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介绍
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 函数
场景:当需要依赖一个数据得到新的数据使用计算属性
函数或者对象的形式
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()书写简单,只要你想监听的东西都写在里面,一变化就会触发回调
缺点:性能不好,但是没有想象的那么夸张。
生命周期
使用步骤:
- 先从vue中导入以on打头的生命周期钩子函数
- 在setup函数中调用生命周期函数并传入回调函数
- 生命周期钩子函数可以调用多次
具体内容:
- 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元素
步骤:
-
导入ref from 'vue'
-
使用 ref 声明数据,初始值为null=> const hRef = ref(null)
-
模板中建立关联 =>
我是标题
-
使用 => 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要用变量接收
:::
不需要导入就可以使用
目标:能够实现组件通讯中的父传子组件通讯
步骤:
- 父组件提供数据
- 父组件将数据传递给子组件
- 子组件通过 defineProps 进行接收
- 子组件渲染父组件传递的数据
注意:
- 如果使用 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导入
目标:能够实现组件通讯中的子传父组件通讯
步骤:
- 子组件通过 defineEmits获取 emit 函数(因为没有this)
- 不需要import导入
- 子组件通过 emit 触发事件,并且传递数据
- 父组件提供方法
- 父组件通过自定义事件的方式给子组件注册事件
总结:
- 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://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自己会帮你推断出类型-鼠标摸一摸
发生类型推断的几个场景:
- 声明变量并初始化时
- 决定函数返回值时
:::
知道: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)
}
字面量类型-精确
二、字面量类型
- 字面量类型介绍-写死个字面量类型,数据就不能改
例如:
const str2 = Hello TS!
str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS
- 字面量类型应用
类型更加精确-举例性别和方向-配合联合类型
配合联合类型来使用,表示:一组明确的可选的值
优势: 相比于 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
- 使用 as 关键字实现类型断言
- 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
- 通过类型断言,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总结
- 类型推断----ts自己会帮你推论出类型 鼠标帮你摸一摸
- 字面量类型介绍 -----写死字面量类型---数据就不能改了
- 字面量类型应用-类型更加精确--举例性别和方向---配合联合类型** |**
- any类型---任意类型---逃避ts检查--不建议使用---用了相当于写js
- 函数参数给初始值 ---直接等于---不用加?--本质上也是可选
- 初识类型断言---as--断言--- 目的是比ts更确定一个类型,要确定的才断言
- 类型断言-img元素赋值src----练习
- 泛型别名---泛型本质是类型传参--语法<>写在别名后面 尖括号传你想要的参数
- 泛型别名模拟接口数据上-不建议使用交叉类型**&**-复用性没有泛型好
- 泛型别名模拟接口数据下-使用泛型优化----提高复用和灵活性
code msg 固定 data传一个T---- 动态传入的类型传参
- 泛型接口-内置的泛型接口-Array
数组,Array<>--专业术语--泛型接口 数组的方法push forEach方法 拿到T类型
- 泛型接口-手写泛型接口-接口名后加尖括号
泛型接口,在接口名后面加尖括号
泛型别名:别名后面加尖括号---概念 类型传参
- 泛型函数-在执行小括号前面加尖括号--想传几个就传几个
- 补充-泛型函数-不能随意想当然乱写泛型T加T会报错
- 上午总结
- ts实现访问历史记录的功能---综合案例
- 综合练习-formatTime基本实现-date对象上的方法
- 综合练习-formatTime扩展支持传字符串传date对象-可选参数-类型守卫
- 综合练习-时间前面补0操作-padStart
- 综合练习-定义数据格式类型-分析对象数组-对象里有2个属性
- 综合练习-本地存取时间数据-localStorage
- 综合练习-拿到本地数据渲染页面-数组转成字符串-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写法
- 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
- 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要用一个函数,为了组件复用,新的对象,新的引用。
- 如果需要给 props 设置默认值,需要使用 withDefaults 函数:
两个参数,一个是defineProps 函数第二个是对象--默认值
typescript
const props = withDefaults(defineProps<{
money: number;
car?: string;
}>(),{
car: '宝马车'
})
- 废弃的响应式语法-默认值用withDefaults
解构一般会丢失响应式
defineEmits的TS写法
30-defineEmits结合ts写法-配置泛型-函数返回值void-参数事件名-子传父的数据
- defineEmits 的基本用法:
typescript
const emit = defineEmits(['changeMoney', 'changeCar'])
- defineEmits 通过泛型参数来定义,可以实现更细粒度的校验:
typescript
const emit = defineEmits<{
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
了解:扩展TS语法 调用签名
ref的TS写法
ref() 会隐式的 依据数据 推导类型
- 如果是简单类型,推荐使用类型推导:
typescript
// const money = ref<number>(10)
const money = ref(10)
https://cn.vuejs.org/guide/typescript/composition-api.html#typing-ref
- 如果是复杂类型,推荐指定泛型:
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() 也会隐式的依据数据推导类型
- 默认值属性是固定的,推荐使用类型推导:
typescript
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3 在线医疗' })
- 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:
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#
- computed() 会从其计算函数的返回值上推导出类型:
typescript
import { ref, computed } from 'vue'
const count = ref(100);
const doubleCount = computed(() => count.value * 2);
- 可以通过泛型参数显式指定类型:
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#
- 不加类型,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>
- 处理类型:
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 的值,下面的属性或函数的访问赋值:
- 可选链
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>
- 逻辑判断
typescript
if (input.value) {
console.log(input.value.value)
input.value.value = '123'
}
- 非空断言
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 文件提供该类型,实现类型共享。
- 操作步骤:
- 创建 index.d.ts 类型声明文件。
- 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
- 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。
记得写导入时侯要写type ,为了符合规范!!!!!
imd export
给JS文件提供类型#
了解:使用类型声明文件 给JS文件 添加类型
-
在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
-
declare 关键字:
- 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
- 对于 type interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
- 其他** 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
表示文章的类型,控制台粘贴返回的数据,用插件的快捷键!!导出导入
列表渲染
声明一个响应式数据,赋值,渲染
- ref简单数据--什么都不写---类型推导
复杂数据--配置泛型----摸一下返回的结果是Ariticle[] ,c一下
- 赋值
- 渲染之前看一下控制台
类型声明文件写的详细,点出来的内容就更多
接口文档快速生成
接口文档生成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
- 安装pinia
- 创建实例,使用pinia插件
- 在count.ts中定义,创建仓库,按需导出
- defineStore('唯一标识',箭头函数) 箭头函数里面提供数据和修改方法
- 按需导入 使用仓库
- 在控制台上看可以看见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