Vue3 + Vite + TypeScript + Pinia + Yarn
- yarn
- vite 构建工具
- Vue 3
- TypeScript
- Pinia
yarn
常用命令
# 查看yarn 版本
yarn -v
# 查看yarn配置
yarn config list
# 查看当前yarn源
yarn config get registry
# 修改yarn源(初始)
yarn config set registry https://registry.yarnpkg.com
# 修改yarn源(此处为淘宝的源)
yarn config set registry https://registry.npm.taobao.org
# yarn安装依赖
yarn add 包名 # 局部安装
yarn global add 包名 # 全局安装
# yarn 卸载依赖
yarn remove 包名 # 局部卸载
yarn global remove 包名 # 全局卸载(如果安装时安到了全局,那么卸载就要对应卸载全局的)
# yarn 查看全局安装过的包
yarn global list
npm install -g yarn # 安装yarn
yarn --version # 安装成功后,查看版本号
md yarn # 创建文件夹 yarn
cd yarn # 进入yarn文件夹
# 初始化项目
yarn init # 同npm init,执行输入信息后,会生成package.json文件
# yarn的配置项:
yarn config list # 显示所有配置项
yarn config get <key> # 显示某配置项
yarn config delete <key> # 删除某配置项
yarn config set <key> <value> [-g|--global] # 设置配置项
# 安装包:
yarn install # 安装package.json里所有包,并将包及它的所有依赖项保存进yarn.lock
yarn install --flat # 安装一个包的单一版本
yarn install --force # 强制重新下载所有包
yarn install --production # 只安装dependencies里的包
yarn install --no-lockfile # 不读取或生成yarn.lock
yarn install --pure-lockfile # 不生成yarn.lock
# 添加包(会更新package.json和yarn.lock):
yarn add [package] # 在当前的项目中添加一个依赖包,会自动更新到package.json和yarn.lock文件中
yarn add [package]@[version] # 安装指定版本,这里指的是主要版本,如果需要精确到小版本,使用-E参数
yarn add [package]@[tag] # 安装某个tag(比如beta,next或者latest)
# 不指定依赖类型默认安装到dependencies里,你也可以指定依赖类型:
yarn add --dev/-D # 加到 devDependencies
yarn add --peer/-P # 加到 peerDependencies
yarn add --optional/-O # 加到 optionalDependencies
# 默认安装包的主要版本里的最新版本,下面两个命令可以指定版本:
yarn add --exact/-E # 安装包的精确版本。例如yarn add foo@1.2.3会接受1.9.1版,但是yarn add foo@1.2.3 --exact只会接受1.2.3版
yarn add --tilde/-T # 安装包的次要版本里的最新版。例如yarn add foo@1.2.3 --tilde会接受1.2.9,但不接受1.3.0
yarn publish # 发布包
yarn remove <packageName> # 移除一个包,会自动更新package.json和yarn.lock
yarn upgrade # 更新一个依赖: 用于更新包到基于规范范围的最新版本
yarn run # 运行脚本: 用来执行在 package.json 中 scripts 属性下定义的脚本
yarn info <packageName> # 可以用来查看某个模块的最新版本信息
yarn cache # 缓存
yarn cache list # 列出已缓存的每个包
yarn cache dir # 返回 全局缓存位置
yarn cache clean # 清除缓存
vite 构建工具
对比 webpack:
- 需要查找依赖,打包所有的模块,然后才能提供服务,更新速度会随着代码体积增加越来越慢
vite 的原理:
- 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应
注明:项目打包的时候最终还是需要打包成静态资源的,打包工具 Rollup
问题:
- 基于
webpack
构建项目,基于vite
构建项目,谁更快体验更好?vite - 基于
webpack
的vue-cli
可以创建 vue 项目吗?可以,慢一点而已
vite 创建项目
- 运行创建项目命令:
# 使用npm
npm create vite@latest
# 使用yarn
yarn create vite
# 使用pnpm
pnpm create vite
- 输入项目名称,默认是 vite-project
- 选择前端框架
- 选择项目类型
- 创建完毕
- 进入项目目录,安装依赖,启动项目即可。
Vue 3
需要切换插件
vue3 组件代码和 vue2 有些不一样,使用的语法提示和高亮插件也不一样。
vetur
插件需要禁用,安装volar
插件。总结 vue3 写法不同
- 组件一个根节点非必需
- 创建应用挂载到根容器
- 入口页面,ESM 加载资源
平常组件
<template> <div>节点1</div> <div>节点2</div> </template>
main.js
import { createApp } from 'vue' import App from './App.vue' // 根据App组件创建一个应用实例 const app = createApp(App) // app应用挂载(管理)index.html的 #app 容器 app.mount('#app')
index.html
<div id="app"></div> <script type="module" src="/src/main.js"></script>
vue 3 组件库
库名称 | 简介 |
---|---|
ant-design-vue | PC 端组件库:Ant Design 的 Vue 实现,开发和服务于企业级后台产品 |
arco-design-vue | PC 端组件库:字节跳动出品的企业级设计系统 |
element-plus | PC 端组件库:基于 Vue 3,面向设计师和开发者的组件库 |
Naive UI | PC 端组件库:一个 Vue 3 组件库,比较完整,主题可调,使用 TypeScript,快,有点意思 |
vant | 移动端组件库:一个轻量、可靠的移动端组件库,于 2017 年开源 |
VueUse | 基于 composition 组合式 api 的常用函数集合 |
vue 文档
中文文档:
- 相关文档
- Vue3 中文文档(新) https://cn.vuejs.org/
- Vue2 中文文档(旧) https://v2.cn.vuejs.org/
- Vue3 设计理念 https://vue3js.cn/vue-composition/
- 了解框架优点特点
- 首次渲染更快
- diff 算法更快
- 内存占用更少
- 打包体积更小
- 更好的 Typescript 支持
Composition API
组合 API
组合式 API
1. setup 函数
setup函数是组合式API的入口函数
setup
函数作为组合式API的起点- 它在
beforeCreate
之前执行- 函数中
this
不是组件实例,是undefined
- 如果数据或者函数在模板中使用,需要在
setup
返回
<template>
<div class="container">
<h1 @click="say()">{{msg}}</h1>
</div>
</template>
<script>
export default {
setup () {
console.log('setup执行了')
console.log(this)
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
// 返回给模板使用
return { msg , say}
},
beforeCreate() {
console.log('beforeCreate执行了')
console.log(this)
}
}
</script>
2. reactive 函数
reactive
函数通常定义:复杂类型的响应式数据 且 知道明确字段
- 不可以转换简单数据
使用步骤:
- 从
vue
中导出reactive
函数 - 在
setup
函数中,使用reactive
函数,传入一个普通对象,返回一个响应式数据对象 - 最后
setup
函数返回一个对象,包含该响应式对象即可,模板中可使用
<template>
<div>
<p>姓名:{{state.name}}</p>
<p>年龄:{{state.age}} <button @click="state.age++">一年又一年</button></p>
</div>
</template>
<script>
// 1. 导入函数
import { reactive } from "vue";
export default {
setup() {
// 2. 创建响应式数据对象
const state = reactive({ name: 'tom', age: 18 })
// 3. 返回数据
return { state }
}
};
</script>
3. ref 函数
通常使用它定义响应式数据,不限类型
ref
可以把简单数据或者复杂数据转换成响应式数据,注意在JS使用时加上.value
,不过模板可省略。
使用步骤:
- 从
vue
中导出ref
函数 - 在
setup
函数中,使用ref
函数,传入普通数据(简单or复杂),返回一个响应式数据 - 最后
setup
函数返回一个对象,包含该响应式数据即可 - 注意:使用
ref
创建的数据,js
中需要.value
,template
中可省略
代码示例:
<template>
<div>
<p>
计数器:{{ count }}
<button @click="count++">累加1</button>
<!-- template中使用可省略.value -->
<button @click="increment">累加10</button>
</p>
</div>
</template>
<script>
// 1. 导入函数
import { ref } from "vue";
export default {
setup() {
// 2. 创建响应式数据对象
const count = ref(0);
const increment = () => {
// js中使用需要.value
count.value += 10;
};
// 3. 返回数据
return { count, increment };
},
};
</script>
4. reactive 和 ref 的选择
在定义响应式数据的函数选择上,遵循:尽量使用
ref
函数支持所有场景,确定字段的对象使用reactive
可以省去.value
。
reactive
可以转换对象成为响应式数据对象,但是不支持简单数据类型。ref
可以转换简单数据类型为响应式数据对象,也支持复杂数据类型,但是操作的时候需要.value
。
推荐用法:
- 如果能确定数据是对象且字段名称也确定,可使用
reactive
转成响应式数据,其他一概使用ref
。
参考代码:
// 1. 明确表单对象有两个字段
const form = reactive({
username: '',
password: ''
})
// 2. 后台返回的数据对象
const data = ref(null)
const res = await axios.get('/user/100')
data.value = res.data
5. setup 语法糖
在
script setup
中的顶层变量都可以在模板使用,数据,函数,组件。
发现
- 使用 setup 有几件事必须做:默认导出配置选项,setup函数声明,返回模板需要数据与函数。
<script>
export default {
setup() {
const say = () => console.log('hi')
return { say }
}
}
</script>
解法
- 使用 setup 语法糖
<script setup>
const say = () => console.log('hi')
</script>
示例代码
<script setup>
// 显示隐藏
const show = ref(true)
const toggle = () => {
show.value = !show.value
}
// 计数器
const count = ref(0)
const increment = () => {
count.value ++
}
</script>
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/logo.png" />
<hr />
计数器:{{ count }} <button @click="increment">累加</button>
</template>
6. computed 函数
使用
computed
定义计算属性,场景:当需要依赖一个数据得到新的数据使用计算属性
使用步骤:
- 从
vue
中导出computed
函数 - 在
setup
函数中,使用computed
函数,传入一个函数,函数返回计算好的数据 - 最后
setup
函数返回一个对象,包含该计算属性数据即可,然后模板内使用
代码示例:
<script setup>
import { ref, computed } from "vue";
const scoreList = ref([80, 100, 90, 70, 60]);
// 计算属性
const betterList = computed(() => scoreList.value.filter((item) => item >= 90));
// 改变数据,计算属性改变
setTimeout(() => {
scoreList.value.push(92, 66);
}, 3000);
</script>
<template>
<div>
<p>分数:{{ scoreList }}</p>
<p>优秀:{{ betterList }}</p>
</div>
</template>
7. watch 函数
watch(需要监听的数据,数据改变执行函数,配置对象)
来进行数据的侦听- 数据:单个数据,多个数据,函数返回对象属性,属性复杂需要开启深度监听
- 配置对象:
deep
深度监听immediate
默认执行watch('x', () => {}, {})
7.1 使用 watch
监听一个响应式数据
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
// 1. 监听一个响应式数据
// watch(数据, 改变后回调函数)
watch(count, () => {
console.log("count改变了");
});
// 2s改变数据
setTimeout(() => {
count.value++;
}, 2000);
</script>
<template>
<p>计数器:{{ count }}</p>
</template>
7.2 使用 watch
监听多个响应式数据
<script setup>
import { reactive, ref, watch } from "vue";
const count = ref(0);
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 2. 监听多个响应式数据
// watch([数据1, 数据2, ...], 改变后回调函数)
watch([count, user], () => {
console.log("数据改变了");
});
// 2s改变数据
setTimeout(() => {
count.value++;
}, 2000);
// 4s改变数据
setTimeout(() => {
user.info.age++;
}, 4000);
</script>
<template>
<p>计数器:{{ count }}</p>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
7.3 使用 watch
监听响应式对象数据中的一个属性(简单)
<script setup>
import { reactive, watch } from "vue";
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 3. 监听响应式对象数据的一个数据,简单类型
// watch(()=>数据, 改变后回调函数)
watch(()=>user.name, () => {
console.log("数据改变了");
});
// 2s改变数据
setTimeout(() => {
user.name = 'jack';
}, 2000);
// 4s改变数据
setTimeout(() => {
user.info.age = 60;
}, 4000);
</script>
<template>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
7.4 使用 watch
监听响应式对象数据中的一个属性(复杂),配置深度监听
<script setup>
import { reactive, watch } from "vue";
const user = reactive({
name: "tom",
info: {
gender: "男",
age: 18,
},
});
// 4. 监听响应式对象数据的一个数据,复杂类型
// watch(()=>数据, 改变后回调函数, {deep: true})
watch(
() => user.info,
() => {
console.log("数据改变了");
},
{
// 开启深度监听
deep: true,
}
);
// 2s改变数据
setTimeout(() => {
user.info.age = 60;
}, 2000);
</script>
<template>
<p>
姓名:{{ user.name }} 性别:{{ user.info.gender }} 年龄:{{ user.info.age }}
</p>
</template>
使用 watch
监听,配置默认执行
{
// 开启深度监听
deep: true,
// 默认执行一次
immediate: true
}
8. 声明周期函数
常用的
onMounted
组件渲染完毕:发请求,操作dom,初始化图表…
- 生命周期钩子函数可以调用多次
具体内容:
- Vue3和vue2的生命周期对比
选项式API下的生命周期函数使用 | 组合式API下的生命周期函数使用 |
---|---|
beforeCreate | 不需要(直接写到setup函数中) |
created | 不需要(直接写到setup函数中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroyed | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
代码示例:
<script setup>
import { onMounted } from "vue";
// 生命周期函数:组件渲染完毕
onMounted(()=>{
console.log('onMounted触发了')
})
onMounted(()=>{
console.log('onMounted也触发了')
})
</script>
<template>
<div>生命周期函数</div>
</template>
9. ref 获取 DOM 元素
元素上使用 ref属性关联响应式数据,获取DOM元素
- 默认值是
null
,需要在渲染完毕后访问DOM属性
步骤:
- 创建 ref =>
const hRef = ref(null)
- 模板中建立关联 =>
<h1 ref="hRef">我是标题</h1>
- 使用 =>
hRef.value
代码:
<script setup>
import { ref } from 'vue'
const hRef = ref(null)
const clickFn = () => {
hRef.value.innerText = '我不是标题'
}
</script>
<template>
<div>
<h1 ref="hRef">我是标题</h1>
<button @click="clickFn">操作DOM</button>
</div>
</template>
10. ref操作组件 - defineExpose
- 配合
defineExpose
暴露数据和方法,ref获取的组件实例才可以使用
步骤:
- 使用
<script setup>
的组件是默认关闭的,组件实例使用不到顶层的数据和函数。 - 需要配合
defineExpose
暴露给组件实例使用,暴露的响应式数据会自动解除响应式。
代码示例:
<script setup>
import { ref } from 'vue'
const count = ref(0)
const validate = () => {
console.log('表单校验方法')
}
// 暴露属性给外部组件使用
defineExpose({count, validate})
</script>
<template>
<h3>我是Form组件</h3>
</template>
ref操作组件
<script setup>
import { ref } from 'vue'
import Form from './components/Form.vue'
// 1. 提供一个ref
const formRef = ref(null)
// 2. 使用组件组件和方法
const fn = () => {
console.log(formRef.value.count)
formRef.value.validate()
}
</script>
<template>
<Form ref="formRef"></Form>
</template>
11. 父传子 - defineProps 函数
- 如果使用
defineProps
接收数据,这个数据只能在模板中渲染- 如果想要在
script
中也操作props
属性,应该接收返回值
步骤:
- 父组件提供数据
- 父组件将数据传递给子组件
- 子组件通过
defineProps
进行接收 - 子组件渲染父组件传递的数据
代码:
ParentCom.vue
<script setup>
import { ref } from 'vue'
import ChildCom from './components/ChildCom.vue'
const money = ref(100)
const car = ref('玛莎拉蒂')
</script>
<template>
<div>
<h1>我是父组件</h1>
<div>金钱:{{ money }}</div>
<div>车辆:{{ car }}</div>
<hr />
<ChildCom :money="money" :car="car"></ChildCom>
</div>
</template>
ChildCom.vue
<script setup>
import { computed } from 'vue'
// defineProps: 接收父组件传递的数据
const props = defineProps({
money: Number,
car: String,
})
// 使用props
console.log(props.money)
</script>
<template>
<div>
<h3>我是子组件</h3>
<div>{{ money }} --- {{ car }}</div>
</div>
</template>
12. 子传父 - defineEmits 函数
defineEmits
获取emit
函数,且组件需要触发的事件需要显性声明出来
步骤:
- 子组件通过
defineEmits
获取emit
函数(因为没有this) - 子组件通过
emit
触发事件,并且传递数据 - 父组件提供方法
- 父组件通过自定义事件的方式给子组件注册事件
代码:
ChildCom.vue
<script setup>
defineProps({
money: Number,
car: String,
})
// 得到emit函数,显性声明事件名称
const emit = defineEmits(['changeMoney'])
const change = () => {
emit('changeMoney', 10)
}
</script>
PrarentCom.vue
<script setup>
import { ref } from 'vue'
import ChildCom from './components/ChildCom.vue'
const money = ref(100)
const car = ref('玛莎拉蒂')
const changeMoney = (num) => {
money.value = money.value - num
}
</script>
<ChildCom :money="money" :car="car" @changeMoney="changeMoney"></ChildCom>
13. 跨级组件通讯provide与inject函数
- provide 和 inject 是解决跨级组件通讯的方案
- provide 提供后代组件需要依赖的数据或函数
- inject 注入(获取)provide提供的数据或函数
- 官方术语:依赖注入
- App是后代组件
依赖
的数据和函数的提供者
,Child是注入
(获取)了App提供的依赖
落地代码:
- 祖先组件:
App.vue
<script setup>
import { provide, ref } from 'vue';
import ParentCom from './ParentCom.vue';
// 1. app组件数据传递给child
const count = ref(0);
provide('count', count);
// 2. app组件函数传递给child,调用的时候可以回传数据
const updateCount = (num) => {
count.value += num;
};
provide('updateCount', updateCount);
</script>
<template>
<div
class="app-page"
style="border: 10px solid #ccc; padding: 50px; width: 600px"
>
app 组件 {{ count }} updateCount
<ParentCom />
</div>
</template>
- 父级组件:
ParentCom.vue
<script setup>
import ChildCom from './ChildCom.vue';
</script>
<template>
<div class="parent-page" style="padding: 50px">
parent 组件
<hr />
<ChildCom />
</div>
</template>
- 子级组件:
ChildCom.vue
<script setup>
const count = inject('count');
const updateCount = inject('updateCount');
</script>
<template>
<div class="child-page" style="padding: 50px; border: 10px solid #ccc">
child 组件 {{ count }} <button @click="updateCount(100)">修改count</button>
</div>
</template>
14. 保持响应式 - toRefs 函数
当去解构和展开响应式数据对象使用
toRefs
保持响应式
- 作用:把对象中的每一个属性做一次包装成为响应式数据
- 响应式数据展开的时候使用,解构响应式数据的时候使用
落地代码:
- 基础案例
<script setup>
import { reactive } from "vue";
const user = reactive({ name: "tom", age: 18 });
</script>
<template>
<div>
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }} <button @click="user.age++">一年又一年</button></p>
</div>
</template>
- 使用响应式数据,踩坑
<script setup>
import { reactive } from "vue";
const { name, age } = reactive({ name: "tom", age: 18 });
</script>
<template>
<div>
<p>姓名:{{ name }}</p>
<!-- 响应式丢失 -->
<p>年龄:{{ age }} <button @click="age++">一年又一年</button></p>
</div>
</template>
- 使用
toRefs
处理响应式数据,爬坑
import { reactive, toRefs } from "vue";
const user = reactive({ name: "tom", age: 18 });
const { name, age } = toRefs(user)
TypeScript
TypeScript
是一种带有类型语法
的 JavaScript 语言,在任何使用 JavaScript 的开发场景中都可以使用。
- TS 需要编译才能在浏览器运行。
TypeScript 文档
官方网站:https://www.typescriptlang.org/
中文官网: https://www.tslang.cn/
TypeScript 编译
全局安装:
# npm 安装
npm i -g typescript
# yarn 安装
yarn global add typescript
# 部分mac电脑安装需要sudo权限
# sudo npm i -g typescript
# sudo yarn global add typescript
查看版本:
tsc -v
编译 TS:
- 新建
hello.ts
文件 - 当前目录打开命令行窗口,执行
tsc hello.ts
命令,同级目录生成hello.js
文件 - 执行
node hello.js
验证一下
创建 vue-ts 项目
# npm 6.x
npm create vite@latest my-vue-ts-app --template vue-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-ts-app -- --template vue-ts
# yarn
yarn create vite my-vue-ts-app --template vue-ts
# pnpm
pnpm create vite my-vue-ts-app --template vue-ts
TypeScript 核心
1. 类型注解
变量后面约定类型的语法,就是类型注解
: number
就是类型注解,它为变量提供类型约束。- 约定了什么类型,就只能给该变量赋值什么类型的值,否则报错。
示例代码:
// 约定变量 age 的类型为 number 类型
let age: number = 18;
age = 19;
2. 原始类型
- JS 已有类型
- 简单类型,
number
string
boolean
null
undefined
- 复杂类型,对象 数组 函数
- TS 新增类型
- 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any、泛型 等
原始类型:
- 使用简单,完全按照 JS 的类型来书写即可
let age: number = 18;
let myName: string = '黑马程序员';
let isLoading: boolean = false;
let nullValue: null = null;
let undefinedValue: undefined = undefined;
3. 数组类型
推荐使用:
number[]
写法
- 写法 1
let numbers: number[] = [1, 3, 5];
- 写法 2
let strings: Array<string> = ['a', 'b', 'c'];
4. 联合类型
- 类型与类型之间使用
|
连接,代表类型可以是它们当中的其中一种,这种类型叫:联合类型
代码示例:
数组中有 number
和 string
类型
let arr: (number | string)[] = [1, 'a', 3, 'b'];
定时器添加类型:
let timer: number | null = null;
timer = setInterval(() => {}, 1000);
5. 类型别名
type 类型别名 = 具体类型
基本语法- 定义类型别名,遵循大驼峰命名规范,类似于变量
- 使用类型别名,与类型注解的写法一样即可
示例代码:
// let arr: ( number | string )[] = [ 1, 'a', 4]
// 类型别名: type 类型别名 = 具体类型
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
使用场景:
- 当同一类型(复杂)被多次使用时,可以通过类型别名,
简化
该类型的使用
type CustomArr = (number | string)[];
let arr: CustomArr = [1, 'a', 4];
let arr2: CustomArr = [2, 'b', 8];
6. 函数类型
- 给函数指定类型,其实是给
参数
和返回值
指定类型。- 两种写法:
- 在函数基础上
分别指定
参数和返回值类型- 使用类型别名
同时指定
参数和返回值类型- 通过类似箭头函数形式的语法来为函数添加类型,只适用于
函数表达式
示例代码 1:分别指定
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2;
}
// 箭头函数
const add = (num1: number, num2: number): number => {
return num1 + num2;
};
示例代码 2:同时指定
type AddFn = (num1: number, num2: number) => number;
const add: AddFn = (num1, num2) => {
return num1 + num2;
};
7. void 类型
如果函数没有返回值,定义函数类型时返回值类型为
void
- 如果函数没有返回值,且没有定义函数返回值类型的时候,默认是
void
- 在
JS
中如果没有返回值,默认返回的是undefined
,但是void
和undefined
在TypeScript
中并不是一回事,如果指定返回值类型是undefined
那返回值必须是undefined
代码示例:
const say = (): void => {
console.log('hi');
};
const say = () => {
console.log('hi');
};
**注意事项:**在 JS
中如果没有返回值,默认返回的是 undefined
,但是 void
和 undefined
在 TypeScript
中并不是一回事,如果指定返回值类型是 undefined
那返回值必须是 undefined
const add = (): undefined => {
return undefined;
};
8. 可选参数
使用
?
将参数标记为可选如果函数的参数,可以传也可以不传,这种情况就可以使用
可选参数
语法,参数后加?
即可
- 必选参数必须在可选参数的前面
- (start?: number, end: number) 错误的写法
代码示例:
const fn = (n?: number) => {
// ..
};
fn();
fn(10);
const mySlice = (start?: number, end?: number) => {
console.log('起始Index:', start, '结束Index:', end);
};
mySlice();
mySlice(1);
mySlice(1, 2);
9. 对象类型
基本使用:
- TS 的对象类型,其实就是描述对象中的
属性
方法
的类型,因为对象是由属性和方法组成的。
// 空对象
let person: {} = {};
// 有属性的对象
let person: { name: string } = {
name: '同学',
};
// 有属性和方法,一行书写多个属性 ; 分隔
let person: { name: string; sayHi(): void } = {
name: 'jack',
sayHi() {},
};
// 换行写可以省略 ; 符号
let person: {
name: string;
sayHi(): void;
} = {
name: 'jack',
sayHi() {},
};
扩展用法:
- 函数使用箭头函数类型
let person: {
name: string
sayHi: () => void
} = {
name: 'jack',
sayHi() {},
};
- 对象属性可选
// 例如:axios({url,method}) 如果是 get 请求 method 可以省略
const axios = (config: { url: string; method?: string }) => {};
- 使用类型别名
// {} 会降低代码可阅读性,建议对象使用类型别名
// const axios = (config: { url: string; method?: string }) => {};
type Config = {
url: string;
method?: string;
};
const axios = (config: Config) => {};
- 使用声明描述对象结构?
{}
- 属性怎么写类型?
属性名: 类型
- 方法怎么写类型?
方法名(): 返回值类型
- 对象的方法使用箭头函数类型怎么写?
{sayHi:()=>void}
- 对象的可选参数怎么设置?
{name?: string}
- 对象类型会使用
{}
如何提供可阅读性?类型别名
10. 接口 interface
接口声明是命名对象类型的另一种方式
定义:interface A {} 继承:interface A extends B {}
继承后
接口A
拥有接口B
的所有属性和函数的类型声明
继承:
- 相同的属性或展示可以抽离出来,然后使用
extends
实现继承复用
interface Point2D {
x: number;
y: number;
}
// 继承 Point2D
interface Point3D extends Point2D {
z: number;
}
// 继承后 Point3D 的结构:{ x: number; y: number; z: number }
type 交叉类型:
使用 &
可以合并连接的对象类型,也叫:交叉类型
- 实现
Point2D
与{z: number}
类型合并得到Ponit3D
类型
// 使用 type 来定义 Point2D 和 Point3D
type Point2D = {
x: number;
y: number;
};
// 使用 交叉类型 来实现接口继承的功能:
// 使用 交叉类型 后,Point3D === { x: number; y: number; z: number }
type Point3D = Point2D & {
z: number;
};
let o: Point3D = {
x: 1,
y: 2,
z: 3,
};
11. 类型推断
在 TS 中存在类型推断机制,在没有指定类型的情况下,TS 也会给变量提供类型。
- 能省略类型注解的地方就省略,
充分利用TS推断
的能力,提高开发效率- 不知道类型怎么写,可以把鼠标放至变量上,可以通过
Vscode
提示看到类型
发生类型推断的几个场景场景:
- 声明变量并初始化时
// 变量 age 的类型被自动推断为:number
let age = 18;
- 决定函数返回值时
// 函数返回值的类型被自动推断为:number
const add = (num1: number, num2: number) => {
return num1 + num2;
};
12. 字面量类型
使用
js字面量
作为变量类型,这种类型就是字面量类型js 字面量如:
18
'jack'
['a']
{age: 10}
等
代码示例:
// : 'jack' 是字面量类型
let name: 'jack' = 'jack';
// : 18 是字面量类型
let age: 18 = 18;
// 报错:不能将类型“19”分配给类型“18”
age = 19;
let str1 = 'Hello TS'; // 类型 string
const str2 = 'Hello TS'; // 类型 Hello TS
// 原因:str2 是 const 声明的,值只能是 Hello TS,所以类型只能是 Hello TS
类型应用:
例如:性别只能是 男 和 女,不会出现其他值。
// let gender = '男'
// gender = '女'
// ------------------------
type Gender = '男' | '女'
let gender: Gender = '男'
gender = '女'
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')
13. any 类型
any 类型的作用是逃避 TS 的类型检查
any
的使用越多,程序可能出现的漏洞越多,因此不推荐使用any
类型,尽量避免使用。
- 显式any情况:当变量的类型指定为 any 的时候,不会有任何错误,也不会有代码提示,TS会忽略类型检查
let obj: any = { age: 18 }
obj.bar = 100
obj()
const n: number = obj
// 以上的代码虽然没有报错提示,但是将来是可能出现错误的
- 隐式any的情况:声明变量不给类型或初始值,函数参数不给类型或初始值
// 声明变量不给类型或初始值
let a;
// 函数参数不给类型或初始值
const fn = (n) => {}
14. 类型断言
你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型
使用
as
关键字实现类型断言
// aLink 的类型 HTMLElement,该类型只包含所有标签公共的属性或方法
// 这个类型太宽泛,没包含 a 元素特有的属性或方法,如 href
const aLink = document.getElementById('link')
- 但是我们明确知道获取的是一个
A
元素,可以通过类型断言
给它指定一个更具体的类型。
const aLink = document.getElementById('link') as HTMLAnchorElement
- 解释:
- 使用
as
关键字实现类型断言 - 关键字
as
后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型) - 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
- 使用
例如:
const img = document.getElementById('img') as HTMLImageElement
// 如果不知道标签的类型:document.querySelector('div') 鼠标摸上去就可以看见
15. 泛型
在TypeScript中,泛型是一种创建可复用代码组件的工具。这种组件不只能被一种类型使用,而是能被多种类型复用。类似于参数的作用,泛型是一种用以增强类型(types)、接口(interfaces)、函数类型等能力的非常可靠的手段。
15.1 泛型别名
- 泛型:定义类型别名后加上
<类型参数>
就是泛型语法, 使用的时候传入具体的类型即可<T>
是一个变量,可以随意命名,建议遵循大驼峰即可。- 和类型别名配合,在类型别名后加上泛型语法,然后类型别名内就可以使用这个类型参数
- 泛型可以提高类型的
复用性
和灵活性
代码示例:
// 对后台返回的数据进行类型定义
type User = {
name: string;
age: number;
}
type Goods = {
id: number;
goodsName: string;
}
type Data<T> = {
msg: string;
code: number;
data: T
}
// 使用类型
type UserData = Data<User>
type GoodsData = Data<Goods>
15.2 泛型接口
在接口名称的后面添加
<类型变量>
,那么,这个接口就变成了泛型接口,接口中所有成员都可以使用类型变量
代码示例:
// 对象,获取单个ID函数,获取所有ID函数,ID的类型肯定是一致的,但是可能是数字可能是字符串
interface IdFn<T> {
id: () => T;
ids: () => T[];
}
const idObj: IdFn<number> = {
id() { return 1 },
ids() { return [1, 2] },
};
内置的泛型接口:
- 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键) 去查看内置的泛型接口
const arr = [1, 2, 3];
// TS有自动类型推断,其实可以看做:const arr: Array<number> = [1, 2, 3]
arr.push(4);
arr.forEach((item) => console.log(item));
15.3 泛型函数
- 泛型函数语法?
- 函数名称后加上
<T>
,T
是类型参数,是个类型变量,命名建议遵循大驼峰即可。T
什么时候确定?
- 当你调用函数的时候,传入具体的类型,T 或捕获到这个类型,函数任何位置均可使用。
- 泛型函数好处?
- 让函数可以支持不同类型(复用),且保证类型是安全的。
- 调用函数,什么时候可以省略泛型?
- 传入的数据可以推断出你想要的类型,就可以省略。
代码示例:
// 函数的参数是什么类型,返回值就是什么类型
function getId<T>(id: T): T {
return id
}
let id1 = getId<number>(1)
let id2 = getId('2')
// TS会进行类型推断,参数的类型作为泛型的类型 getId<string>('2')
// 我需要的类型 { name: string, age?: number } 但是推断出来是 { name: string}
let id2 = getId({name:'jack'})
TypeScript 与 组合式API
typescript 配合 Vue3 composition-api 使用
https://staging-cn.vuejs.org/guide/typescript/composition-api.html
script 加上
lang="ts"
才能写ts代码
<script setup lang="ts"></script>
1. defineProps的TS写法
1 defineProps 的基本使用:
const props = defineProps({
money: {
type: Number,
required: true
},
car: {
type: String,
required: false,
default: '宝马车'
}
})
console.log(props.money) // number
console.log(props.car) // string | undefined
2 defineProps 通过泛型参数来定义 props 的类型通常更直接:
const props = defineProps<{
money: number
car?: string
}>()
3 如果需要给 props 设置默认值,需要使用 withDefaults
函数:
const props = withDefaults(defineProps<{
money: number;
car?: string;
}>(),{
car: '宝马车'
})
4 上面写法太笨拙,可以使用 响应式语法糖 解构 + defineProps 就行:
const { money, car = "宝马车" } = defineProps<{
money: number
car?: string
}>();
注意:目前需要 显式地选择开启 ,因为它还是一个实验性特性。
// vite.config.ts
export default defineConfig({
plugins: [
vue({
reactivityTransform: true,
}),
],
});
响应式语法糖 即将移除,建议使用 withDefaults 过度
2. defineEmits的TS写法
1. defineEmits 的基本用法:
const emit = defineEmits(['changeMoney', 'changeCar'])
2. defineEmits 通过泛型参数来定义,可以实现更细粒度的校验:
const emit = defineEmits<{
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
了解:扩展TS语法 调用签名
3. ref的TS写法
ref()
会隐式的依据数据推导类型
1. 如果是简单类型,推荐使用类型推导:
// const money = ref<number>(10)
const money = ref(10)
2. 如果是复杂类型,推荐指定泛型:
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)
复杂数据一般是后台返回数据,默认值是空,无法进行类型推导。
4. reactive的TS写法
reactive()
也会隐式的依据数据推导类型
- 官方:不推荐使用
reactive()
的泛型参数,因为底层和ref()
实现不一样。
1. 默认值属性是固定的,推荐使用类型推导:
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3' })
2. 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:
// 我们想要的类型:{ title: string, year?: number }
type Book = {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue3' })
book.year = 2022
5. computed和TS
1. computed()
会从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'
const count = ref(100);
const doubleCount = computed(() => count.value * 2);
2. 可以通过泛型参数显式指定类型:
const doubleMoney = computed<string>(() => (count.value * 2).toFixed(2));
6. 事件处理与TS
1. 不加类型,event默认是any,类型不安全:
<script setup lang="ts">
// 提示:参数“event”隐式具有“any”类型。
const handleChange = (event) => {
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
2. 处理类型:
// `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)
}
7. Template Ref与TS
模板
ref
需要通过一个显式指定的泛型参数,建议默认值null
- 注意为了严格的类型安全,有必要在访问
el.value
时使用可选链或类型守卫。- 这是因为直到组件被挂载前,这个
ref
的值都是初始的null
,并且在由于v-if
的行为将引用的元素卸载时也可以被设置为null
。
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement| null>(null)
onMounted(() => {
el.value?.focus()
})
</script>
<template>
<input ref="el" />
</template>
8. 非空断言
处理类型可能是 null 或 undefined 的值,下面的属性或函数的访问赋值:
1. 可选链
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const input = ref< HTMLInputElement | null >(null)
onMounted(()=>{
// 只能访问
console.log(input.value?.value);
})
</script>
<template>
<div>App组件</div>
<input type="text" ref="input" value="abc">
</template>
2. 逻辑判断
if (input.value) {
console.log(input.value.value)
input.value.value = '123'
}
3. 非空断言
// 一定要确定不为空!!!
console.log(input.value!.value)
input.value!.value = '123'
9. TypeScript类型声明文件
- 在第三方库中的JS代码都有对应的
TS类型声明文件
什么是类型什么文件?
- 在 TypeScript 中以 .d.ts 为后缀的文件,我们称之为 TypeScript 类型声明文件。它的主要作用是描述 JavaScript 模块内所有导出成员的类型信息。
TS 中有两种文件类型:
.ts
文件.d.ts
文件作用是啥?
- .ts 文件:
既包含类型信息又可执行代码
- 可以被编译为 .js 文件,然后,执行代码
- 用途:编写程序代码的地方
- .d.ts 文件:
只包含类型信息
的类型声明文件- 不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型
- 用途:为 JS 提供类型信息
9.1 内置类型声明文件
TypeScript 给 JS 运行时可用的所有标准化内置 API 都提供了声明文件,这个声明文件就是
内置类型声明文件
- 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
- 查看 forEach 的类型声明,在 VSCode 中会自动跳转到
lib.es5.d.ts
类型声明文件中- 像 window、document 等 BOM、DOM API 也都有相应的类型声明文件
lib.dom.d.ts
9.2 第三方库类型声明文件
首先,常用的第三方库都有相应的类型声明文件,只是使用的方式不同而已。
情况1:库本身自带类型声明文件
- 比如:axios,安装后可查看
node_modules/axios
可发现对应的类型声明文件。- 导入 axios 后就会加载对应的类型文件,提供该库的类型声明。
情况2:由 DefinitelyTyped 提供
- 比如:jquery,安装后导入,提示:需要安装
@types/jquery
类型声明包- DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
- 当安装
@types/*
类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明https://www.typescriptlang.org/dt/search 可以搜索是否有对应的
@types/*
9.3 自定义类型声明文件 【重要】
9.3.1 共享类型(重要)
- 如果多个
.ts
文件中都用到同一个类型,此时可以创建.d.ts
文件提供该类型,实现类型共享。- 操作步骤:
- 创建
index.d.ts
类型声明文件。- 创建需要共享的类型,并使用
export
导出(TS 中的类型也可以使用import/export
实现模块化功能)。- 在需要使用共享类型的
.ts
文件中,通过import
导入即可(.d.ts
后缀导入时,直接省略)。
src/types/data.d.ts
export type Person = {
id: number;
name: string;
age: number;
};
App.vue
<script lang="ts" setup>
import { Person } from './types/data'
const p: Person = {
id: 100,
name: 'jack',
age: 19
}
</script>
9.3.2 给JS文件提供类型
在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
declare 关键字:
- 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
- 对于
type
interface
等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略
declare 关键字。- 其他 JS 变量,应该使用
declare
关键字,明确指定此处用于类型声明。
add/index.js
const add = (a, b) => {
return a + b;
};
const point = (p) => {
console.log('坐标:', p.x, p.y);
};
export { add, point }
add/index.d.ts
declare const add: (a: number, b: number) => number;
type Position = {
x: number;
y: number;
};
declare const point: (p: Position) => void;
export { add , point};
main.ts
import { add , point} from './add';
add(3, 10)
point({x: 100, y: 200})
Pinia
Pinia
是一个状态管理工具,它和Vuex
一样为Vue
应用程序提供共享状态管理能力。- 语法和
Vue3
一样,它实现状态管理有两种语法:选项式API
与组合式API
,我们学习组合式API语法。- 它也支持
Vue2
也支持devtools
,当然它也是类型安全的,支持TypeScript
- 可以创建多个全局仓库,不用像 Vuex 一个仓库嵌套模块,结构复杂。
- 管理数据简单,提供数据和修改数据的逻辑即可,不像Vuex需要记忆太多的API。
知识点
1. 使用步骤:
1.1 安装
yarn add pinia
# or
npm i pinia
1.2 导入
导入实例化,当做插件使用,和其他插件使用套路相同
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
1.4 创建仓库&使用仓库
import { defineStore } from "pinia"
import { computed, ref } from "vue"
export const useCounterStore = defineStore("counter", () => {
return {}
})
<script setup lang="ts">
import { useCounterStore } from "./store/counter"
// store中有状态和函数
const store = useCounterStore()
</script>
1.5 进行状态管理
// state
const count = ref(100)
// getters
const doubleCount = computed(() => count.value * 2)
// mutations
const update = () => count.value++
// actions
const asyncUpdate = () => {
setTimeout(() => {
count.value++
}, 1000)
}
return { count, doubleCount, update, asyncUpdate }
<template>
APP {{ store.count }} {{ store.doubleCount }}
<button @click="store.update()">count++</button>
<button @click="store.asyncUpdate()">async update</button>
</template>
1.6 总结
- 通过
const useXxxStore = defineStore('id',函数)
创建仓库得到使用仓库的函数
Vuex | Pinia |
---|---|
state | ref 和 reactive 创建的响应式数据 |
getters | computed 创建的计算属性 |
mutations 和 actions | 普通函数,同步异步均可 |
- 使用Pinia与在组件中维护数据大体相同,这就是
Pinia
的状态管理基本使用
2. storeToRefs的使用
问题:
- 当我们想解构 store 提供的数据时候,发现数据是没有响应式的。
回忆:
- 在学习 vue 组合式API创建的响应式数据的时候,使用
toRefs
保持结构后数据的响应式
方案:
- 使用
storeToRefs
解决解构仓库状态丢失响应式的问题
代码:
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
小结:
- 当你想从 store 中解构对应的状态使用,需要使用
storeToRefs