##vue-tsc 是对 vue 3 的一个类型检测工具
基本语法:创建响应式数据对象,等价于 vue 2.x 中的 Vue.observable() 函数
reactive 和 ref 都是用来定义响应式数据的 reactive更推荐去定义复杂的数据类型 ref 更推荐定义基本类型
ref 和 reactive 本质我们可以简单地理解为ref是对reactive的二次包装, ref定义的数据访问的时候要多一个.value
使用ref定义基本数据类型,ref也可以定义数组和对象。
1.isRef() 用来判断某个值是否为 ref() 创建出来的对象。
2.toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据。
provide() 和 inject() 可以实现嵌套组件之间的数据传递。这两个函数只能在 setup() 函数中使用。父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
用provide和inject可以很方便的在父子组件之间通讯,即使是多层子组件,也能获取到父组件的值。但是会遇上类型上的问题,导致父组件传给子组件的方法无法调用,这时候我们可以使用vue3提供的InjectionKey解决这一问题
在父组件中定义对象和函数 ,用provide暴露
const user = ref({ name: 'Karen', age: 20 })
provide('user', user)
const setUser = ({ name, age }: { name: string; age: number }) => {
user.value.name = name
user.value.age = age
}
provide('setUser', setUser)
子组件用inject接收
const user = inject('user')
const setUser = inject('setUser') // 报错 const setUser: unknown
可以看到接收到的对象和函数都是unknown,导致函数无法调用
这时候就有用到InjectionKey来定义类型,确保父传出去的值和子接收到的值类型是一样的
定义InjectionKey
import { InjectionKey, Ref } from 'vue'
export interface User {
name: string
age: number
}
export type SetUser = (newUser: User) => void
// 对象的InjectionKey
export const userKey: InjectionKey<Ref<User>> = Symbol('')
// 函数的的InjectionKey
export const setUserKey: InjectionKey<SetUser> = Symbol('')
接下来provide和inject就不要用字符串来定义名称了,用定义好的InjectionKey
父组件
import { userKey, setUserKey } from './type'
const user = ref({ name: 'Karen', age: 20 })
provide(userKey, user)
const setUser = ({ name, age }: { name: string; age: number }) => {
user.value.name = name
user.value.age = age
}
provide(setUserKey, setUser)
子组件
const user = inject('user')
const setUser = inject('setUser')
setUser?.(...)
可以看到,子组件这里接收到的值和函数都拥有了类型,函数也能正常调用了
即数据双向绑定,改变Model时,View自动更新。改变View时,Model也会自动更新。
数据响应式原理
在Vue2.x版本中,利用Object.defineProperty()重新定义对象获取属性值(get)和设置属性值的操作实现数据响应。
在Vue3.0版本中,采用ES6的Proxy对象来实现。
可以理解为有一堆描述dom的数据需要整理(虚拟dom,其实就是一堆对象储存了一堆数据),然后这些数据用js的API,比如createElement,创造出dom,这个过程是挂载前的动作。 创造出的dom还没有被使用,仅仅在内存中,需要“挂载”,然后你最外层是不是有一个叫app的id的dom?然后挂载就是document.querySelector(‘app’).appendChild(dom),就完成挂载啦
扩展语法。对数组和对象而言,就是将运算符后面的变量里东西每一项拆下来。
这个东西可以在函数定义的地方使用,比如使用func(…args)将函数传入的参数都放到args数组里。
&& 表示为真时 执行
|| 表示为假时 执行
过滤器函数接受表达式的值作为第一个参数。
以下实例对输入的字符串第一个字母转为大写:
{{ message | capitalize }}
过滤器可以串联:
{{ message | filterA | filterB }}
过滤器是 JavaScript 函数,因此可以接受参数:
{{ message | filterA('arg1', arg2) }}
?. 可选链操作符
const obj = {}
// console.log(obj.msg.info) // 这句会报错,不信你试试
console.log(obj && obj.msg && obj.msg.info)
console.log(obj ?. msg ?. info)
// 读取对象深处的属性的值,而不必明确验证链中的每个引用是否有效, 如果深层没有该值你还使用.读取会报错
?? 空值合并操作符
// 使用?? 运算符
console.log(null ?? 1) // 1
console.log(undefined ?? 2) // 2
console.log(0 ?? 3) // 0
console.log(false ?? 4) // false
console.log("" ?? 1) // ""
// 使用|| 运算符
console.log(null || 1) // 1
console.log(undefined || 2) // 2
console.log(0 || 3) // 3
console.log(false || 4) // 4
console.log("" || 5) // 5
// 使用?? 当左侧的操作数为 null 或者 undefined,使用的是后面的数据,其他的皆使用运算符前面的数据
// 使用|| 当左侧的操作数为真就使用的是前面的数据,为假就使用的是后面的数据
10.箭头函数=>
var f = a =>a
//等同于
var f=function(a){
return a;
}
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
// 无行参
var f=()=>5;
var f =function(){
return 5;
}
//多个行参
var sum = (num1,num2)=>num1+num2;
var sum = function (num1,num2){
return num1+num2;
}
// 一个参数
var f = a =>a
//等同于
var f=function(a){
return a;
}
使用箭头函数注意事项
1.函数体内的this对象,就是定义时所在的对象,而不是使用时的对象。
在 TS 中,类似数组、字符串、数组、接口这些常见的类型都非常常见,但是如果要定义一个对象的 key 和 value 类型该怎么做呢?这时候就需要用到 TS 的 Record 了
很好理解,Record 后面的泛型就是对象键和值的类型。
比如我需要一个对象,有 ABC 三个属性,属性的值必须是数字,那么就这么写:
type keys = 'A' | 'B' | 'C'
const result: Record = {
A: 1,
B: 2,
C: 3
}
ts数据类型 分原始类型对象类型
原始类型 7种
boolean
Null
Undefined
Number
BigInt
string
Symbol (符合类型)
symbol
1.新增的第7种数据类型,表示独一无二,用来做属性名,能保证不会与其他的属性名冲突
2.他是通过Symbol函数生成的
3.他的前面不能用new,因为它生成的是一个原始类型的数据,不是对象
4.它可以接受一个参数,为了便于区别,即使长得一样也不相同
5.它不能与其他的值进行运算,没有隐式转换
6.它的值可以转换成布尔值或者字符串,不能转化为数字
any 类型
假如说现在有这样一个需求, 我们可能要一个第三方系统, 系统输入的值是什么类型,我们不知道,不输入我门无法知道它们是什么类型 ,这个时候我们可以使用 Any 类型 ,意思是 允许赋值为任意类型 ,代码如下
// 允许赋值为任意类型
let notSure:any = 4
// 赋值成 string 类型
notSure = 'maybe a string'
// 赋值成 boolean 类型
notSure = true
// 调用属性以及方法也不会报错
notSure.myName
notSure.getName()
在 Typescript 中,我们可以这样定义 值都为 number 的数组 ,如下:
// number 类型数组,数组项不可以出现非 number 类型之外类型的数据
let arrOfNumbers:number[] = [1, 2, 3]
// 数组的方法对数组进行添加数据时也必须为 number 类型
arrOfNumbers.push(3)
function test(){
// arguments 是一个类数组
console.log(arguments)
// 不可以像下面这样写,因为类数组跟数组是不一样的,它缺少数组的方法
let arr:any[] = arguments
}
如果是 数组对象 的数据结构,可以使用 接口(interface) 来 约束数组对象 ,代码如下:
interface IDataType {
name: string,
age?: number
}
let data:IDataType[] = [
{
name: '小明'
},
{
name: '小芳',
age: 18
},
]
数组 将 同一个类型的数据集合 到一起,例如我们上面的例子,数组的值 ,都是 number类型 的,那么 加入不同类型的数组的值 怎么办,大家可能会想到用 array:any[] ,但是 any[] 就会 丧失具体的类型 ,这不是我们想要的,我有很明确的目标,比如:我数组的第 1 个值是 number,第二个值是 string ,这时候我们可以使用 元祖 ,元祖合并了不同类型的对象 , 元祖 起源于 函数式编程 ,如果对 python 有过了解的人可能会有了解
// 定义元祖
let user: [string, number] = ['viking', 20]
// 可以向元祖中push数据
user.push('true')
Interface 可以定义 object 类型数据 ,Interface 也称之为 Duck Typing(鸭子类型), Interface 非常灵活, 可以描述编程语言的各种类型 ,Interface 不存在 javascript 的概念,所以 ts 在编译以后,Interface 是不会被转换到 js 文件中的,所以 它(Interface)只能用来做类型的静态检查
接口用法
// 定义接口
interface Iperson {
name: string;
age: number;
}
// 定义变量
let viking: Iperson = {
name: 'viking',
age: 18
}
有的 编程语言建议 interface 名称定义时,第 1 个字母用大写 I , 意思是告诉大家这是一个 interface ,上面的意思是 定义一个接口,然后 viking 这个变量用定义好的 Iperson 这个接口的规则,来约束这个变量,需要注意:如果 viking 变量中少属性或多属性会报错,给变量赋值时形状保持与接口一致 这种情况该怎么办呢,可以使用可选属性
可选属性
可选属性: 该属性可以是不存在的 ,像下面这样写 age 就是可选可不选的
// 定义一个接口
interface Iperson {
name: string;
age?: number;
}
// 定义一个变量
let viking: Iperson = {
name: 'viking',
// age: 20
}
自读属性
有时候我们希望 对象中的一些属性只能被赋值,后期只能读取,不能修改 ,我们可以用 readonly , 这样写
// 定义一个接口
interface Iperson {
readonly id:number;
name: string;
age?: number;
}
// 定义一个变量
let viking: Iperson = {
id: 1,
name: 'viking',
age: 20
}
// 修改 id 就会报错
viking.id = 9527
readonly 与 const关键字 有点相似,它们之间的区别是 readonly 是用在属性上的 ,而 const 是用在变量上的。
let name: string='张三'
↑ ↑ ↑
声明变量 变量 变量类型
声明变量:要想使用变量,你需要做的第一步就是创建它 – 更准确的说,是声明一个变量。声明一个变量的语法是在 var
或 let
关键字之后加上这个变量的名字
变量: 变量是用来存储数值的,那么有一个重要的概念需要区分。变量不是数值本身,它们仅仅是一个用于存储数值的容器。你可以把变量想象成一个个用来装东西的纸箱子
变量类型:string、number、boolean…
var和let的区别
全局环境声明的区别
在全局环境中声明变量,var和let的主要区别在于,var声明的变量会成为window的属性或方法,而let则不会
var num1 = 1;
let num2 = 2;
console.log(window.num1,window.num2);
输出:1 undefined
变量提示
使用var 声明变量时变量会提升,let不会
console.log(num);
var num = 1;
输出:undefined
等价于:
var num;
console.log(num);
num = 1;
变量声明提升
console.log(test);
let test = "js";
报错:Uncaught ReferenceError: test is not defined
at <anonymous>:1:13
var声明的变量是函数作用域,而let声明的时块级作用域
function fun(){
for(var i = 0;i < 10; i++){
}
console.log(i);
}
fun();
输出:10
function fun(){
for(let j = 0;j < 10; j++){
}
console.log(j);
}
fun();
报错:VM808:4 Uncaught ReferenceError: j is not defined
at fun (<anonymous>:4:17)
at <anonymous>:6:1
第二个之所以会报错,是因为let 声明的变量是块级作用域,只在相应的区块内有效,离开就没有访问权限,可以简单的理解{}就是一个区块,第二个函数的for循环是一个区块,j在下面输出的时候,离开for循环这个块级作用域,所以会报错。
const
const几乎和let差不多,唯一的区别是const声明的是常量,即一但赋值,值就不可以改变,否则会报错。它同样具有的是块级作用域
const num = 1;
num = 2;
报错:VM159:2 Uncaught TypeError: Assignment to constant variable.
at <anonymous>:2:5
``ts
function fun(){
for(let j = 0;j < 10; j++){
}
console.log(j);
}
fun();
报错:VM808:4 Uncaught ReferenceError: j is not defined
at fun (:4:17)
at :6:1
第二个之所以会报错,是因为let 声明的变量是块级作用域,只在相应的区块内有效,离开就没有访问权限,可以简单的理解{}就是一个区块,第二个函数的for循环是一个区块,j在下面输出的时候,离开for循环这个块级作用域,所以会报错。
const
const几乎和let差不多,唯一的区别是const声明的是常量,即一但赋值,值就不可以改变,否则会报错。它同样具有的是块级作用域
```ts
const num = 1;
num = 2;
报错:VM159:2 Uncaught TypeError: Assignment to constant variable.
at :2:5
h函数就是vue中的createElement方法,这个函数作用就是创建虚拟dom,追踪dom变化的