目录

---------

TypeScript

TypeScript 文件以 .ts 结尾,在使用 ts 之前,需要安装 typescriptts-node

Bash
npm i -g typescript ts-node

想要测试写的文件,请使用以下命令:

Bash
ts-node fileName.ts

其实是用 nodemon 也可以

Bash
nodemon fileName.ts

简单的例子

TS 可以理解为带有类型的 JS,例如下面的例子:

JS 声明类型:

Javascript
let str = 'hello world'
let num = 0
let bool = true

JS 是弱类型语言,意味着 JS 会自动推断/转换类型,开发者不需要考虑变量类型不一致导致的错误,但是这样也会导致很多的问题,例如在大型项目中,很难定位到错误的地方

同时也意味着后续变量也可以赋值为不同类型:

Javascript
let str = 'hello world'
str = 0

TS 声明类型:

我们可以使用 : 运算符,在变量名后面注明类型

Typscript
let str: string = 'hello world'
let num: number = 0
let bool: boolean = true

当类型确定后,不可以赋值为其他类型

Typscript
let str: string = 'hello world'
str = 0 // 报错

也可以省略类型,ts 会自动推断类型

Typscript
let str = 'hello world' // 等同于 let str: string = 'hello world'

另外一种声明类型语法是使用 <> 运算符,不过不常用,多用于方法/数组的书写,此运算符下面章节会将

原始类型

ES6 规定的 JS 原始类型: number、string、boolean、null、undefined、symbol

Typscript
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 可以解决这个问题,但是不经常用:

Typscript
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)

应用:

Typscript
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 类型

any 类型在开发中尽量不要使用,因为这会使得 TS 变为 JS。当然,在实际开发中,有些依赖使用的方法类型不确定,我们可以少量的使用 any

Typscript
let a: any = 1
a = 'str'
a = true
// ...

数组类型

只需要在对应类型后加上 [] 即可

Typscript
let nums: number[] // 表明是一个数字数组类型

或者使用 <> 语法(不推荐)

Typscript
let nums: Array(number)

多个类型描述

使用 | 或运算符,可以使得变量多个类型

Typscript
// 表示 nums 既可以是 number 也可以是 number[]
let nums: number | number[] = 1
nums = [1, 2, 3]

type

type 可以让我们自定义一些类型,例如上述的 number | number[]

Typscript
type TNums = number | number[]

此时,我们便可以使用 TNums 来代替 number | number[] 类型了

Typscript
let nums: TNums = 1
nums = [1, 2, 3]

interface 接口

interface 可以描述一个对象,用于规范对象中的属性和属性值

Typscript
// 表示:类型为 IPerson 的对象必须且只能包含 name、age 属性,且两者属性值类型分别为 string、number
interface IPerson {
  name: string
  age: number
}
const person: IPerson = {
  name: 'xj',
  age: 20
}

如果希望对象中有个属性是可选的,可以在 interface 中使用 ? 可选运算符

Typscript
interface IPerson {
  name: string
  age: number
  weight?: number
}
const person: IPerson = {
  name: 'xj',
  age: 20
}

函数类型

该类型可以指定函数中具体有什么参数(及参数类型)和函数的返回值(及返回值类型),在我们定义函数中使用

Typscript
// 表名两个参数类型均为 number,且返回值也为 number
function sum(a: number, b: number): number {
  return a + b
}

箭头函数写法

Typscript
const sum = (a: number, b: number): number => {
  return a + b
}

泛型

泛型可以让函数与多种类型一起工作,从而实现复用

Typscript
// 表名用户传过来的参数是什么类型,那么我就返回什么类型
function id<T>(value: T): T {
  return value
}
const id1 = id<number>(1)
const id2 = id(2) // 自动推断

注意,函数声明时的 <T> 不能省略,不能简单地认为传过来什么类型,就返回什么类型

事实上,TS 还有更多类型,这里不一一讲解,希望大家可以自行学习

方法

typeof

typeof 在 ts 中可以推断 ts 中的类型,使用它可以简化或者扩展功能,例如以下代码:

Typscript
let p = { x: 1, y: 2 }
// 以下两者等同
// function formatPoint(point: { x: number, y: number }) {}
function formatPoint(point: typeof p) {}

keyof

keyof 可以获取某个类型所有键的类型

Typscript
type TProps = {
  a: number
  b: boolean
  c: string
}
type TProp = TProps[keyof Tprops] // number | boolean | string

事实上,TS 还有更多类型,这里不一一讲解,希望大家可以自行学习

Vue

这里只讲解 vue 怎么用,关于 vue 如何实现的以及其他知识(例如 MVVM 模型、虚拟DOM等),请参考 vue 的官网:https://cn.vuejs.org/

初始化项目

  1. 使用 yarn + vite 创建 vue 项目
Bash
yarn create vite
  1. 会有对应 GUI 提示如何安装

记得在项目里 yarn 一下安装依赖

目录介绍

vite 初始化 vue 后,有许多文件夹,这里讲解各个文件的作用

已有的目录

  1. .vscode

vsocde 配置目录,可以配置插件等

  1. public

这里放的是公共资源,例如我们可以放图片一类的东西,如果要访问这里的资源,直接使用 /xxx.png 即可

Vue
<template>
	<img src='/1.png' />
</template>
  1. src

src 表示 source:资源,即我们编写代码的地方。

  1. src/components

编写 vue 组件的地方,通常这个目录下的组件不包含功能(例如请求后端数据等),只做 UI 展示和复用

  1. src/assets

静态资源,放在此处的文件会被打包压缩,我们可以放公共的 css 样式文件等

以后会碰到的目录

以下介绍的目录均包含 src/ 目录前缀:

  1. utils

定义公共的方法,例如格式化时间的方法

Typscript
// utils/useFormatTime.ts
const date = new Date()
// 代码逻辑...
return `xxxx-xx-xx xx:xx:xx`
  1. views

components 一样,这里是编写 vue 组件的,不同的是这里的组件包含功能(例如请求后端数据)

  1. router

配置路由的文件夹

  1. store

配置持久化存储的路由

  1. __test__

编写测试的目录

组件

每一个 .vue 文件都叫做组件,会有子组件和父组件之分,利用组件,我们可以将网页的视图和功能分开

Vue
<template>
	<p>
    Son 组件
  </p>
</template>

此文件是入口文件,当我们想用 Son 组件的内容时,只需要在 script 标签中导入,并当做 html 标签一样使用即可

Vue
<script setup>
import Son from './components/Son.vue'
</script>
<template>
	<div>
    <h1>App 组件</h1>
    <Son />
  </div>
</template>

渲染模板

Vue 中使用 {{}} 插值语法渲染 script 中定义的变量

Vue
<script setup>
	let str = 'hello world'
</script>
<template>
	<div>
    {{str}}
  </div>
</template>

{{}} 内部本身就是 js 作用域,可以编写一些简单的逻辑运算,例如

Vue
<template>
	{{ 1 + 1 }}
</template>

vue 指令

渲染指令

上述的渲染模板其实也是一种渲染指令,但其实 vue 还提供了其他两种:

  1. v-text
  2. v-html

上述两者区别是,v-text 只会渲染文本,当文本是 html 类型时,渲染的还是字符串文本,而 v-html 则是渲染的文本

Vue
<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-textv-html 实际开发中不常用,都是在用 {{}}

条件渲染指令

vue 中的条件渲染指令有三种:

  1. v-if
  2. v-else-if
  3. v-else

就像 JavaScript 中的判断语句一样,只不过这些指令都要写在标签的属性上

Vue
<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 提供了两种绑定指令,两种指令运用的场景不同

  1. v-bind:绑定变量,简写 :
  2. v-on:绑定事件,简写 @

v-bind 绑定指令可以绑定一个元素的属性,这个属性可以是变化的

Vue
<script setup>
let id = 'dynamicId'
</script>
<template>
	<div v-bind:id="id"></div>
</template>

简写直接去掉 v-bind

html
PlainText
<div :id="id"></div>

v-on 可以绑定按钮的点击、鼠标悬浮事件等

Vue
<script setup>
function handler() {
  console.log('我被点击了')
}
</script>
<template>
	<button v-on:click="handler"></button>
</template>

简写v-on:click 改为 @click

html
PlainText
<button @click="handler"></button>

列表渲染指令

v-for 列表渲染用于渲染一个数组

Vue
<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 表示索引

html
PlainText
<div v-for="(item, index) in scores"

虽然 v-for 循环简化了数组模板的渲染,但是也会带来性能的开销,因为 vue 本身基于虚拟 DOM,也就是说 vue 并不会像普通的 document.getElementById 类似的语法获取 dom 对象,它是通过一种叫做 ast 抽象语法树的转换方法,将各个 DOM 对象转换为 JS 对象,例如以下示例(伪代码):

Javascript
let virtualDOM = {
  el: 'div',
  id: 'demo',
  class: null,
  children: [
    {
      el: 'div',
      id: null,
      class: null,
      children: {
        // ....
      }
    },
    // ...
  ]
  // ...
}

对于不会更改的数组来说,这不算什么,但是如果对于可变的数组,例如我们要删除数组中的某个索引值,那么 DOM 转换后的 JS 对象如何知道我们删除的是哪个呢?比较复杂的方法是:遍历 JS 对象的所有属性来判断,但很明显这样太浪费性能,上述简单的例子都已经非常复杂,更不要说更加复杂的项目

最适合的解决方法是:传递一个唯一的 key 值,这样 vue 只需要判断 key 值就能够知道用户删除的是哪一个了,所以更改上述代码:

Vue
<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 中用普通方法定义的变量改变时,并不会影响视图

Vue
<script setup>
let count = 0
function increase() {
  count ++
}
</script>
<template>
	<div>
    {{count}}
    <button @click="increase">+1</button>
  </div>
</template>

为此,vue 提供了 ref(其实还有 reactiveshallowRefshallowReactive) 函数创建响应式语法

Vue
<script setup>
let count = ref(0)
function increase() {
  count.value ++
}
</script>
<template>
	<div>
    {{count}}
    <button @click="increase">+1</button>
  </div>
</template>

操作或者访问 count 的值,需要访问 countvalue 属性

双向绑定指令 v-model 多用于处理表单 <input> 之类的标签

Vue
<script setup>
let count = ref(0)
</script>
<template>
	<div>
    <input type="number" v-model="count" />
  </div>
</template>

当我们输入在表单中输入值后,输入的值会自动赋值给 count

vue 的一些问题

不能说 vue 的一些问题,这是客户端渲染框架的通病

我们用 vue 构建完的页面实际上只是一个“空壳”,只包含一条 js 请求代码和一个 html 标签,我们通过请求加载那个 js 代码渲染页面,但这样做会造成很多问题:

  1. 不利于 seo 优化
  2. 所有的代码都在一个 js 文件中,加载白屏时间过长

这些问题在以后的服务端渲染框架会讲到,但这并不说明 vue 等框架不是最终解决方案,事实上,一些后台管理内容就很适合使用 vue 编写,更不用说例如 Nuxt 等服务端框架本身是 vue 等框架的扩展

Props

props 是父组件向子组件传递数据的一种方式,只需要在子组件的属性上添加对应 prop 即可

每一个 .vue 文件都叫做组件,会有子组件和父组件之分,利用组件,我们可以将网页的视图和功能分开

使用 defineProps 表示父组件传递了哪些属性

Vue
<script setup>
defineProps({
  msg: String // 注意这样写和 ts 中的有所不同
})
</script>
<template>
	<p>
    Son 组件
    {{msg}}
  </p>
</template>

如果我们希望使用 ts 中的类型约束,可以这样写

Typscript
defineProps<{
  msg: string
}>()

此文件是入口文件,当我们想用 Son 组件的内容时,只需要在 script 标签中导入,并当做 html 标签一样使用即可

Vue
<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 依赖注入的语法

Vue
// App.vue
<script setup>
provide('str', 'msg from App')
</script>
<template>
	<div>
    <h1>App 组件</h1>
    <Son :msg="str" />
  </div>
</template>

App.vue 的后代组件通过 inject 获得

Vue
// child.vue
<script setup>
let str = inject('str')
</script>
<template>
	<div>
 		{{str}}   
  </div>
</template>

....

事实上,这篇文章只讲了 vue 的冰山一角,更多请查看 vue 官网 或者学习视频

预告

  1. pinia:vue 的持久化管理插件
  2. vue-router:vue 的路由插件
  3. Element-Plus:经典的组件库