TypeScript 文件以 .ts
结尾,在使用 ts
之前,需要安装 typescript
和 ts-node
:
npm i -g typescript ts-node
想要测试写的文件,请使用以下命令:
ts-node fileName.ts
其实是用 nodemon
也可以
nodemon fileName.ts
TS 可以理解为带有类型的 JS,例如下面的例子:
JS 声明类型:
let str = 'hello world'
let num = 0
let bool = true
JS 是弱类型语言,意味着 JS 会自动推断/转换类型,开发者不需要考虑变量类型不一致导致的错误,但是这样也会导致很多的问题,例如在大型项目中,很难定位到错误的地方
同时也意味着后续变量也可以赋值为不同类型:
let str = 'hello world'
str = 0
TS 声明类型:
我们可以使用 :
运算符,在变量名后面注明类型
let str: string = 'hello world'
let num: number = 0
let bool: boolean = true
当类型确定后,不可以赋值为其他类型
let str: string = 'hello world'
str = 0 // 报错
也可以省略类型,ts
会自动推断类型
let str = 'hello world' // 等同于 let str: string = 'hello world'
另外一种声明类型语法是使用
<>
运算符,不过不常用,多用于方法/数组的书写,此运算符下面章节会将
ES6 规定的 JS 原始类型: number、string、boolean、null、undefined、symbol
let age: number = 18
let uname: string = 'xj'
let isLoading: boolean = true
let a: null = null
let b: undefined = undefined
let s: symbol = Symbol()
这里简单介绍一下 symbol
,详细请看:重学JavaScript——Symbol | Plumbiuの小屋
symbol
表示独一无二的值,出现的目的是为了防止一些变量的重复,例如我们为别人提供的对象中添加属性时,很容易导致命名冲突,使用 symbol
可以解决这个问题,但是不经常用:
let s1 = Symbol('foo')
let s2 = Symbol('foo')
console.log(typeof s1) // symbol
console.log(s1) // Symbol(foo)
console.log(s2) // Symbol(foo)
console.log(s1 === s2)
应用:
let s1 = Symbol('foo')
let s2 = Symbol('foo')
let obj = {
[s1]: 'hello',
[s2]: 'world'
}
console.log(obj[s1]) // "hello"
console.log(obj[s2]) // "world"
console.log(obj[Symbol('foo')])
any 类型在开发中尽量不要使用,因为这会使得 TS 变为 JS。当然,在实际开发中,有些依赖使用的方法类型不确定,我们可以少量的使用 any
let a: any = 1
a = 'str'
a = true
// ...
只需要在对应类型后加上 []
即可
let nums: number[] // 表明是一个数字数组类型
或者使用 <>
语法(不推荐)
let nums: Array(number)
使用 |
或运算符,可以使得变量多个类型
// 表示 nums 既可以是 number 也可以是 number[]
let nums: number | number[] = 1
nums = [1, 2, 3]
type
可以让我们自定义一些类型,例如上述的 number | number[]
type TNums = number | number[]
此时,我们便可以使用 TNums
来代替 number | number[]
类型了
let nums: TNums = 1
nums = [1, 2, 3]
interface
接口interface
可以描述一个对象,用于规范对象中的属性和属性值
// 表示:类型为 IPerson 的对象必须且只能包含 name、age 属性,且两者属性值类型分别为 string、number
interface IPerson {
name: string
age: number
}
const person: IPerson = {
name: 'xj',
age: 20
}
如果希望对象中有个属性是可选的,可以在 interface
中使用 ?
可选运算符
interface IPerson {
name: string
age: number
weight?: number
}
const person: IPerson = {
name: 'xj',
age: 20
}
该类型可以指定函数中具体有什么参数(及参数类型)和函数的返回值(及返回值类型),在我们定义函数中使用
// 表名两个参数类型均为 number,且返回值也为 number
function sum(a: number, b: number): number {
return a + b
}
箭头函数写法
const sum = (a: number, b: number): number => {
return a + b
}
泛型可以让函数与多种类型一起工作,从而实现复用
// 表名用户传过来的参数是什么类型,那么我就返回什么类型
function id<T>(value: T): T {
return value
}
const id1 = id<number>(1)
const id2 = id(2) // 自动推断
注意,函数声明时的
<T>
不能省略,不能简单地认为传过来什么类型,就返回什么类型
事实上,TS 还有更多类型,这里不一一讲解,希望大家可以自行学习
typeof
在 ts 中可以推断 ts 中的类型,使用它可以简化或者扩展功能,例如以下代码:
let p = { x: 1, y: 2 }
// 以下两者等同
// function formatPoint(point: { x: number, y: number }) {}
function formatPoint(point: typeof p) {}
keyof
可以获取某个类型所有键的类型
type TProps = {
a: number
b: boolean
c: string
}
type TProp = TProps[keyof Tprops] // number | boolean | string
事实上,TS 还有更多类型,这里不一一讲解,希望大家可以自行学习
这里只讲解 vue 怎么用,关于 vue 如何实现的以及其他知识(例如 MVVM
模型、虚拟DOM等),请参考 vue 的官网:https://cn.vuejs.org/
yarn + vite
创建 vue
项目yarn create vite
记得在项目里
yarn
一下安装依赖
vite
初始化 vue
后,有许多文件夹,这里讲解各个文件的作用
.vscode
vsocde 配置目录,可以配置插件等
public
这里放的是公共资源,例如我们可以放图片一类的东西,如果要访问这里的资源,直接使用 /xxx.png
即可
<template>
<img src='/1.png' />
</template>
src
src
表示 source:资源,即我们编写代码的地方。
src/components
编写 vue 组件的地方,通常这个目录下的组件不包含功能(例如请求后端数据等),只做 UI 展示和复用
src/assets
静态资源,放在此处的文件会被打包压缩,我们可以放公共的 css 样式文件等
以下介绍的目录均包含 src/
目录前缀:
utils
定义公共的方法,例如格式化时间的方法
// utils/useFormatTime.ts
const date = new Date()
// 代码逻辑...
return `xxxx-xx-xx xx:xx:xx`
views
和 components
一样,这里是编写 vue 组件的,不同的是这里的组件包含功能(例如请求后端数据)
router
配置路由的文件夹
store
配置持久化存储的路由
__test__
编写测试的目录
每一个 .vue
文件都叫做组件,会有子组件和父组件之分,利用组件,我们可以将网页的视图和功能分开
src/components/Son.vue
<template>
<p>
Son 组件
</p>
</template>
src/App.vue
此文件是入口文件,当我们想用 Son
组件的内容时,只需要在 script
标签中导入,并当做 html 标签一样使用即可
<script setup>
import Son from './components/Son.vue'
</script>
<template>
<div>
<h1>App 组件</h1>
<Son />
</div>
</template>
Vue 中使用 {{}}
插值语法渲染 script
中定义的变量
<script setup>
let str = 'hello world'
</script>
<template>
<div>
{{str}}
</div>
</template>
{{}}
内部本身就是 js 作用域,可以编写一些简单的逻辑运算,例如
<template>
{{ 1 + 1 }}
</template>
上述的渲染模板其实也是一种渲染指令,但其实 vue
还提供了其他两种:
v-text
v-html
上述两者区别是,v-text
只会渲染文本,当文本是 html
类型时,渲染的还是字符串文本,而 v-html
则是渲染的文本
<script setup>
let str = '123'
let htmlStr = '<h1>hello world</h1>'
</script>
<template>
<div v-text='str'></div>
<div v-text='htmlStr'></div>
<div v-html='htmlStr'></div>
</template>
实际开发中,尽量避免
v-html
,因为这样会导致 xss 攻击,如果有必要使用,请转义后使用。同时
v-text
和v-html
实际开发中不常用,都是在用{{}}
vue 中的条件渲染指令有三种:
v-if
v-else-if
v-else
就像 JavaScript 中的判断语句一样,只不过这些指令都要写在标签的属性上
<script setup>
let score = 100
</script>
<template>
<div>
<span v-if="score > 90">优秀</span>
<span v-else-if="score > 80">良好</span>
<span v-else-if="score > 60">及格</span>
<span v-else>不及格</span>
</div>
</template>
注意,
v-if
的属性值已经是个 js 作用域
vue
提供了两种绑定指令,两种指令运用的场景不同
v-bind
:绑定变量,简写 :
v-on
:绑定事件,简写 @
v-bind
绑定指令可以绑定一个元素的属性,这个属性可以是变化的
<script setup>
let id = 'dynamicId'
</script>
<template>
<div v-bind:id="id"></div>
</template>
简写直接去掉 v-bind
:
<div :id="id"></div>
v-on
可以绑定按钮的点击、鼠标悬浮事件等
<script setup>
function handler() {
console.log('我被点击了')
}
</script>
<template>
<button v-on:click="handler"></button>
</template>
简写v-on:click
改为 @click
:
<button @click="handler"></button>
v-for
列表渲染用于渲染一个数组
<script setup>
let scores = [{ name: 'xm', score: 100 }, { name: 'xg', score: 59 }]
</script>
<template>
<div id="demo">
<div v-for="item in scores">
<span>name: {{item.xm}}</span>
<span>score: {{item.score}}</span>
<span v-if="item.score > 90">优秀</span>
<span v-else-if="item.score > 80">良好</span>
<span v-else-if="item.score > 60">及格</span>
<span v-else>不及格</span>
</div>
</div>
</template>
也可以这样书写:其中 index
表示索引
<div v-for="(item, index) in scores"
虽然 v-for
循环简化了数组模板的渲染,但是也会带来性能的开销,因为 vue 本身基于虚拟 DOM,也就是说 vue 并不会像普通的 document.getElementById
类似的语法获取 dom 对象,它是通过一种叫做 ast 抽象语法树的转换方法,将各个 DOM 对象转换为 JS 对象,例如以下示例(伪代码):
let virtualDOM = {
el: 'div',
id: 'demo',
class: null,
children: [
{
el: 'div',
id: null,
class: null,
children: {
// ....
}
},
// ...
]
// ...
}
对于不会更改的数组来说,这不算什么,但是如果对于可变的数组,例如我们要删除数组中的某个索引值,那么 DOM 转换后的 JS 对象如何知道我们删除的是哪个呢?比较复杂的方法是:遍历 JS 对象的所有属性来判断,但很明显这样太浪费性能,上述简单的例子都已经非常复杂,更不要说更加复杂的项目
最适合的解决方法是:传递一个唯一的 key 值,这样 vue 只需要判断 key 值就能够知道用户删除的是哪一个了,所以更改上述代码:
<script setup>
// 使用 id 作为唯一标识
let scores = [
{ id: 1, name: 'xm', score: 100 },
{ id: 2, name: 'xg', score: 59 }
]
</script>
<template>
<div id="demo">
<div v-for="item in scores" :key=item.id>
<span>name: {{item.xm}}</span>
<span>score: {{item.score}}</span>
<span v-if="item.score > 90">优秀</span>
<span v-else-if="item.score > 80">良好</span>
<span v-else-if="item.score > 60">及格</span>
<span v-else>不及格</span>
</div>
</div>
</template>
需要提前了解响应式语法,即在 script
中用普通方法定义的变量改变时,并不会影响视图
<script setup>
let count = 0
function increase() {
count ++
}
</script>
<template>
<div>
{{count}}
<button @click="increase">+1</button>
</div>
</template>
为此,vue
提供了 ref
(其实还有 reactive
、shallowRef
、shallowReactive
) 函数创建响应式语法
<script setup>
let count = ref(0)
function increase() {
count.value ++
}
</script>
<template>
<div>
{{count}}
<button @click="increase">+1</button>
</div>
</template>
操作或者访问
count
的值,需要访问count
的value
属性
双向绑定指令 v-model
多用于处理表单 <input>
之类的标签
<script setup>
let count = ref(0)
</script>
<template>
<div>
<input type="number" v-model="count" />
</div>
</template>
当我们输入在表单中输入值后,输入的值会自动赋值给 count
不能说 vue
的一些问题,这是客户端渲染框架的通病
我们用 vue
构建完的页面实际上只是一个“空壳”,只包含一条 js 请求代码和一个 html 标签,我们通过请求加载那个 js 代码渲染页面,但这样做会造成很多问题:
这些问题在以后的服务端渲染框架会讲到,但这并不说明 vue
等框架不是最终解决方案,事实上,一些后台管理内容就很适合使用 vue
编写,更不用说例如 Nuxt 等服务端框架本身是 vue
等框架的扩展
props 是父组件向子组件传递数据的一种方式,只需要在子组件的属性上添加对应 prop 即可
每一个 .vue
文件都叫做组件,会有子组件和父组件之分,利用组件,我们可以将网页的视图和功能分开
src/components/Son.vue
使用 defineProps
表示父组件传递了哪些属性
<script setup>
defineProps({
msg: String // 注意这样写和 ts 中的有所不同
})
</script>
<template>
<p>
Son 组件
{{msg}}
</p>
</template>
如果我们希望使用 ts 中的类型约束,可以这样写
defineProps<{
msg: string
}>()
src/App.vue
此文件是入口文件,当我们想用 Son
组件的内容时,只需要在 script
标签中导入,并当做 html 标签一样使用即可
<script setup>
import Son from './components/Son.vue'
let str = 'msg from App'
</script>
<template>
<div>
<h1>App 组件</h1>
<Son :msg="str" />
</div>
</template>
上述讲的 Props
虽然解决了父子组件数据传递的问题,但是如果组件嵌套的比较深,例如 父组件 -> 子组件 -> 孙组件 -> ...
,如果希望父组件向孙组件传递数据,那么我们需要先传递给子组件,再传递给孙组件。解决这个问题,vue
提供了 provide/inject
依赖注入的语法
// App.vue
<script setup>
provide('str', 'msg from App')
</script>
<template>
<div>
<h1>App 组件</h1>
<Son :msg="str" />
</div>
</template>
App.vue 的后代组件通过 inject
获得
// child.vue
<script setup>
let str = inject('str')
</script>
<template>
<div>
{{str}}
</div>
</template>
事实上,这篇文章只讲了 vue 的冰山一角,更多请查看 vue
官网 或者学习视频
pinia
:vue 的持久化管理插件vue-router
:vue 的路由插件Element-Plus
:经典的组件库