Vue3
day1
Vue2和Vue3比较
- Vue2
- 不需要响应式的数据也要定义在data中,造成了性能的浪费
- 选项式APi , 代码不够集中 , 逻辑点是分散的
- Vue3
- 代码量变少了
- 组合式APi , 分散式维护转为集中式维护
Vue3更多的优势
- 更容易维护
- 组合式API
- 更好的TypeScript支持
- 更快的速度
- 重写diff算法
- 模版编译优化
- 更高效的组件初始化
- 更小的体积
- 良好的TreeShaking
- 按需引入
- 更优的数据响应式
- Proxy
补充 : 数组的响应式 Object.defineProperty() 不能对数组完成响应式
- Object是对象
使用create-vue搭建Vue3项目
- create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应
使用 create-vue 搭建Vue3项目
- 在终端打开cmd
- 前提环境条件
node -v
- 已安装 16.0 或更高版本的 Node.js
- 创建一个Vue应用
npm init vue@latest
- 这一指令将会安装并执行
create-vue
-
建立一个项目名称
-
选择配置
- 只选择ESLint
-
cd vue-project
-
npm install
或者npm i
-
npm run dev
-
创建完成
-
code .
在根目录打开项目
项目目录和关键文件
关键文件:
- vite.config.js - 项目的配置文件 基于vite的配置
- package.json - 项目包文件 核心依赖项变成了 Vue3.x 和 vite(Vue2是webpack)
- Vue3 底层通过vite启动(dev)
- main.js -入文件 createApp函数创建应用实例
- app.vue - 根组件 SFC单文件组件 script - template - style
- 变化一:脚本script和模板template顺序调整
- 变化二:模板template不再要求唯一根标签
- Vue2的
template
中必须要有一个根目录 - Vue3可以有很多个根标签
- Vue2的
- 变化三:脚本script添加setup标识支持组合式API(setup标志符/语法糖)
- index.html - 单页入口 提供id为app的挂载点
- index.html在Vue2中是在public文件夹中
setup选项中写代码的特点
- Vue3可以兼容大部分Vue2的写法
- 做了一些调整 : 比如 增加了一个选项 setup
- 要变成响应式数据 ⇒ 就要调组合式API中ref方法
import { ref } from 'vue'
- 使用ref包装的数据会返回一个对象
- 返回的对象的value才是响应式数据的值(比如: count.value)
- 要拿对象的值 ⇒ 就要使用value属性
- 要拿对象的值 ⇒ 就要使用value属性
setup选项的写法和执行时机
- 先打印setup
- setup比before create还要早
- setup中访问不到this ⇒ Vue3中不建议使用this
- 在Vue2中 , created之后才能使用this
setup语法糖
- script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句
总结
- setup选项的执行时机?
- beforeCreate钩子之前 自动执行
- setup写代码的特点是什么?
- 定义数据 + 函数 然后 以对象方式
return
<script setup>
解决了什么问题?
- 经过语法糖的封装更简单的使用组合式API
- setup中的this还指向组件实例吗?
- 指向undefined
组合式API-reactive和ref函数
reactive
作用: 接受对象类型数据的参数传入并返回一个响应式的对象
- reactive : 可以让引用类型的数据变成响应式
import { reactive } from 'vue';
- reactive 包装的响应式数据可以直接使用 , 跟使用一个对象 / 数组是一样的 , 不需要 .value
<script setup>
import {
reactive } from 'vue';
// reactive : 可以让引用类型的数据变成响应式
// reactive 包装的响应式数据可以直接使用 , 跟使用一个对象 / 数组是一样的 , 不需要 .value
const obj = reactive({
count: 20 })
console.log(obj) //Proxy对象 ==> 实现数据的响应式
const addCount = () => {
obj.count++
}
</script>
<template>
<div> {
{
obj.count }}</div>
<div> <button @click="addCount">累加器count++</button></div>
</template>
<style scoped>
</style>
ref
<script setup>
import {
ref } from 'vue';
const count = ref(10)
const addCount = () => {
count.value++
}
</script>
<template>
<div> {
{
count }}</div>
<div> <button @click="addCount">累加器count++</button></div>
</template>
<style scoped>
</style>
reactive 对比 ref
-
都是用来生成响应式数据
-
既然 reactive 和 ref 都可以实现数据的响应式处理,那他们之间有什么区别。应该用哪一个?
-
1.ref 和 reactive不同点
- 简单数据类型 ⇒ ref(可以引用数据类型)
- 引用数据类型 ⇒ reactive
- 2不管是简单数据类型还是引用数据类型,ref 都支持
- 3.ref 处理后的响应式对象,要访问值的话,必须 .value
- ref函数的内部实现依赖于reactive函数
- ref的响应式数据
- 在实际工作中的推荐
- 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref
为什么要通过 .value 来访问响应式对象的值 ?
- 响应式数据在处理时只能处理对象或者引用数据类型
- 在处理简单数据类型时就要转换为对象处理
- ref在处理时 , 会先把入参包装成对象 {value: 入参}
组合式API - computed
computed计算属性函数
计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法
核心步骤:
- 导入computed函数
- 执行函数 在回调参数中return基于响应式数据做计算的值,用变量接收
回调函数的返回值就是计算属性的值
总结
- 计算属性中不应该有"副作用"
- 比如异步请求/修改dom
- 避免直接修改计算属性的值
- 计算属性应该是只读的
组合式API- watch
- 侦听一个或者多个数据的变化,数据变化时执行回调函数
- 俩个俩外参数
- immediate控制立刻执行
- deep开启深度侦听
侦听单个数据
- 导入watch
- 执行watch函数传入要侦听的 响应式数据 和 回调函数
// watch
// 监听 count 的变化
// watch(建议的响应式对象,回调函数 , 怎么监听)
watch ( refCount.count, (newVal, oldVal) => {
console.log('count 变化了 ',newVal,oldVal)
})
侦听多个数据
- 侦听多个数据,第一个参数可以改写成数组的写法
- 任意一个变化就会触发回调函数
immediate
在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调
深度监听 deep
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
精确侦听对象的某个属性
- 需求:在不开启deep的前提下,侦听age的变化,只有age变化时才执行回调
- 将
obj.value.age
改为一个函数const age = () = > obj.value.age
- 深度监听 ⇒ 精确侦听对象的某个属性
// 深度监听
import {
ref } from 'vue';
const obj = ref({
name: 'hello',
count: 10,
age: 20,
child: {
play: 'ball'
}
})
const age = () => obj.value.age
watch(age, () => {
console.log('age 发生变化了')
})
setTimeout( () => obj.value.age++,3000)
- 对于 reactive 处理的响应式数据,watch 监听自动deep
import {
reactive } from 'vue';
const obj = reactive({
name: 'hello',
age: 20,
child: {
count: 19,
play: 'ball'
}
})
// console.log(obj) // 1. Proxy
// 对于 reactive 处理的响应式数据,watch 监听自动deep
watch(obj,() => {
console.log('obj发生变化了')
})
setTimeout(() => obj.child.count++,3000)// 1.打印
总结
- 作为watch函数的第一个参数,ref对象需要添加.value吗?
- 不需要,watch会自动读取
- watch只能侦听单个数据吗?
- 单个或者多个
- 不开启deep,直接修改嵌套属性能触发回调吗?
- 不能,默认是浅层侦听
- 不开启deep,想在某个层次比较深的属性变化时执行回调怎么做?
- 可以把第一个参数写成函数的写法,返回要监听的具体属性
组合式API - 生命周期函数
选项式对比组合式
- 销毁 ⇒ 卸载 vue3中有关销毁的声明周期跟改为 beforeUnmount 和 unmounted
- vue3中从 beforeMount 开始的生命周期,都有自己对应的组合式API的函数; on生命周期名
- vue3中没有 onBeforeCreate 和 onCreated 声明周期的组合式API对应的生命周期函数 ==> setup
执行多次
- 生命周期函数执行多次的时候,会按照顺序依次执行(不会覆盖)
总结
- 组合式API中生命周期函数的格式是什么?
- on + 生命周期名字
- 组合式API中可以使用onCreated吗?
- 没有这个钩子函数,直接写到setup中
- 创建前后没有组合式API函数
- 组合式API中组件卸载完毕时执行哪个函数?
- onUnmounted
- 卸载完
组合式API - 父子通信
- Vue2中父子组件传参
- 父传子
prop
自定义属性 子传父$emit
eventBus
数据广播- Vuex 全局状态共享 实现非关联组件
.sync
⇒update:
v-model
⇒:value
和@input
ref
可以访问到子组件provide
和inject
又被称为 依赖注入
父传子
- 1父组件中给子组件绑定属性
- 1子组件接受自定义属性 ⇒ 子组件定义自己有哪些属性可以传值
2. 2子组件内部通过props选项接收数据
- defineProps() ⇒ 编译器宏
defineProps({
msg: {
type: String,
default: '张三'
}
})
子传父
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
day2
组合式API - 模版引用
概念:通过ref标识获取真实的dom对象或者组件实例对象
this.$refs.form.validate()
模版引用 – 基本使用
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
- 使用模板引用的时候,注意生命周期,一般都要在 mounted 之后进行访问
<script setup>
import {
onMounted, ref } from 'vue';
const h2Node = ref(null)
onMounted(() => {
console.log(h2Node.value)
})
</script>
<template>
<div>
<h2 ref="h2Node">hello world</h2>
</div>
</template>
defineExpose()
- 默认情况下在
<script setup>
语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose
编译宏指定哪些属性和方法容许访问 - 说明:指定testMessage属性可以被访问到
- 暴露给父组件,父组件才可以调用到
总结
- 获取模板引用的时机是什么?
- 组件挂载完毕
- defineExpose编译宏的作用是什么?
- 显式暴露组件内部的属性和方法
组合式API - provide和inject
补充
关于vue2的inject和provide官方是这么说的
- provide和inject需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖(provide),不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
作用和场景
- 顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
跨层传递普通数据
实现步骤
- 顶层组件通过
provide
函数提供数据 - 底层组件通过
inject
函数提供数据
跨层传递响应式数据
在调用provide函数时,第二个参数设置为ref对象
跨层传递方法
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
总结
- provide和inject的作用是什么?
- 跨层组件通信
- 如何在传递的过程中保持数据响应式?
- 第二个参数传递ref对象
- 底层组件想要通知顶层组件做修改,如何做?
- 传递方法,底层组件调用方法
- 一颗组件树中只有一个顶层或底层组件吗?
- 相对概念,存在多个顶层和底层的关系
TypeScript
day1
认识TypeScript
-
TypeScript 是具有类型语法的 JavaScript,是一门强类型的编程语言
-
JavaScript是一门弱类型语言
TypeScript带来的好处
- 静态类型检查,提前发现代码错误
- JavaScript在运行完成后才能发现错误
- 代码提示,提升开发效率
什么时候用
以下是来自社区的一些建议:
- 你做的是一个大型的应用吗?
- 是否是团队协作开发模式?
- 是否在编写通用的代码库? SDK /(Vue3 / ElementPlus…)
结论:TypeScript不是万能的,技术的选型不能脱离具体的业务和应用场景,TS更加适合用来开发中大型的项目,或者是通用的JS代码库,再或者是团队协作开发的场景
搭建TS编译环境
为什么需要编译环境
TypeScript编写的代码是无法直接在js引擎(浏览器/NodeJs)中运行的,最终还需要经过编译变成js代码才可以正常运行
带来的好处:既可以再开发时使用TS编写代码享受类型带来的好处,同时保证实际运行的还是JS代码
搭建手动编译环境
- 全局安装 typescript 包(编译引擎)-> 注册 tsc 命令
npm install -g typescript
- 新增 hello.ts 文件, 执行 tsc hello.ts 命令生成hello.js文件
- 执行 node hello.js 运行js文件查看效果
搭建工程化下的自动编译环境
基于工程化的TS开发模式(webpack / vite),TS的编译环境已经内置了,无需手动安装配置,通过以下命令即可创建一个最基础的自动化的TS编译环境
npm create vite@latest ts-pro -- --template vanilla-ts
- npm create vite@latest 使用最新版本的vite创建项目
- ts-pro 项目名称
- – --template vanilla-ts 创建项目使用的模板为原生ts模板
总结
- 浏览器中能直接运行TypeScript代码吗?
- 不可以,需要编译为js代码再运行
- 哪个包可以负责把TS代码编译为JS代码?
- typescript
- 实际工作中需要我们手动编译代码吗?
- 不需要,由工程化工具内置,自动编译
类型注解
TS类型注解是什么
概念:类型注解指的是给变量添加类型约束,使变量只能被赋值为约定好的类型, 同时可以有相关的类型提示
说明::string 就是类型注解, 约束变量 message 只能被赋值为string 类型, 同时可以有string类型的相关提示
TS支持的常用类型注解
JS已有类型
- 简单类型(小写)
number string boolean null undefined - 复杂类型
数组 函数
TS新增类型
联合类型、类型别名、接口(interface)、字面量类型、泛型、枚举、void、any等
简单类型如何进行类型注解
- 简单类型的注解完全按照 JS的类型(全小写的格式)来书写即可
总结
约束类型能放哪种类型的值
- 类型注解的作用是什么?
- 限制变量能赋值的数据类型并给出提示
- 类型注解的语法是什么?
- 变量 :类型
数组类型注解
变量被注解为数组类型之后,有俩点好处:
-
不仅可以限制变量类型为数组而且可以限制数组成员的类型
-
编码时不仅可以提示数组的属性和方法而且可以提示成员的属性和方法
如何注解数组类型
使用数据类型对变量进行类型注解有俩种语法
- 语法一(推荐)
let arr: number[] = [1,2,3]
- 说明: 以上代码表示变量arr只能赋值数组类型并且数组的成员必须都是number类型
- 语法二 : 泛型语法
let arr4 : Array<number>=[1,2,3]
总结
- 数组类型注解之后除了限制了数组类型还限制了什么?
- 还限制了数组中每一个成员的类型
- 实际开发时常用的是哪种数组注解方式?
- 类型[]
联合类型和别名类型
联合类型
- 概念:将多个类型合并为一个类型对变量进行注解
- 需求:如何注解数组类型可以让数组中既可以存放string类型的成员也可以存放number类型的成员?
let arr3: (string | number) [ ] = [18,'Jack']
- 说明:string | number 表示arr3中的成员既可以是string类型也可以是number类型
类型别名
- 概念:通过 type关键词 给写起来较复杂的类型起一个其它的名字,用来简化和复用类型
说明:type 类型别名 = 具体类型 其中类型别名的命名采用规范的大驼峰格式
总结
- 联合类型的作用是什么?
- 把多个类型合并为一个类型
- 类型别名有什么好处?
- 简化和复用类型
思考题
有一个变量foo,要求添加类型注解,使其既可以赋值为number类型,也可以赋值为成员都是字符串的数组?
1.
type foo = string[] | number
const foo: foo = ['hello']
const fooo : string[] | number = 10
函数类型
概念:函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束
基础使用
function add(a: number, b: number): number{
return a + b
}
console.log(add(1,2)) // 1+2=3
说明:
- 函数参数注解类型之后不但限制了参数的类型还限制了参数为必填
- 函数返回值注解类型之后限制了该函数内部return出去的值必须满足类型要求
好处:
- 避免因为参数不对导致的函数内部逻辑错误
- 对函数起到说明的作用
函数表达式
函数表达式的类型注解有俩种方式,参数和返回值分开注解和函数整体注解
- 参数和返回值分开注解
const add1 = (a: number, b: number): number => {
return a + b
}
- 函数整体注解(只针对于函数表达式)
// 类型别名
type AddFn=(a: number, b: number)=>number
const add2: AddFn = (a, b) => {
return a + b
}
可选参数 ?
- 概念:可选参数表示当前参数可传可不传,一旦传递实参必须保证参数类型正确
const arr4 = [1,2,3,888,324,3432,45,888]
type FilterCallback = (item: number, index?: number) => boolean
const callback: FilterCallback = (item) => {
return item % 2 === 0
}
arr4.filter(callback)
- 说明:lastName参数表示可选参数,可传可不传,一旦传递实参必须保证类型为string类型
无返回值 - void
- 概念:JS中的有些函数只有功能没有返回值,此时使用void进行返回值注解,明确表示函数没有函数值
const arr4 = [1,2,3,888,324,3432,45,888]
// 函数的类型定义
type ForeachCb = (item: number, index?: number) => void
const callback2: ForeachCb = (item) => {
console.log(item)
}
arr4.forEach(callback2)
注意事项:在JS中如何没有返回值,默认返回的是undefined, 在TS中 void和undefined不是一回事,undefined在TS中是一种明确的简单类型,如果指定返回值为undefined,那返回值必须是undefined类型
interface接口
- 接口类型的作用
- 作用: 在TS中使用interface接口来描述对象数据的类型(常用于给对象的属性和方法添加类型约束)
- 说明:一旦注解接口类型之后对象的属性和方法类型都需要满足要求,属性不能多也不能少
典型场景
场景:在常规业务开发中比较典型的就是前后端数据通信的场景
- 前端向后端发送数据:收集表单对象数据时的类型校验
- 前端使用后端数据:渲染后端对象数组列表时的智能提示
接口的可选设置
概念: 通过?对属性进行可选标注,赋值的时候该属性可以缺失,如果有值必须保证类型满足要求
interface UserInfo{
name: string
userId: number
company?:string
avatar?:string
}
interface RseData {
code: number
success: boolean
data: UserInfo
}
接口的继承 extends
概念:接口的很多属性是可以进行类型复用的,使用 extends
实现接口继承,实现类型复用
// 接口的继承
interface Rescomm {
code: number
message: string
success:boolean
}
// 用户信息
interface UserInfo extends Rescomm {
data: {
name: string
userId: string
company?: string
avatar?: string
}
}
// 登录接口
interface LoginRes extends Rescomm {
data:string
}
// 示例
const loginData: LoginRes = {
code: 10000,
message: "成功",
success: true,
data:'fhjdsvfhvskudevschjsdvhucvk'
}
// 给角色分配权限入参类型
interface RolePermission {
id: number