vue3-learn是vue3的基础学习课,内容包含:
vue是目前最流行的前端框架之一, 提到vue为什么会流行起来,它具有什么优势?
我们就不得不提到一个话题, 前后端分离;
为什么需要前后端分离呢?
说到前后端分离, 就必须先理解web项目中分为前端渲染与后端渲染;
所谓
后端渲染
指的是, 界面的呈现, 是是通过后端拼接html代码, 得到的;
而前端渲染
指的是, 界面的是由js代码操作dom后, 得到的;
随机互联网技术的发展, 开发人员越来越偏向于前端渲染
, 而后端渲染
慢慢退出了历史舞台;
我们来看看后端渲染
有哪些劣势:
前端渲染
的主要优势, 传输数据量小, 专业度提升, 渲染流畅;
同样是前端渲染
框架的react, angular, 为什么vue更受欢迎?
当然其他框架也有自身的优劣势:
目前vue主流版本是vue3, 其核心类库有: ts4, less4, webpack4, esnext; 后续章节会介绍
vue的开发建立在nodejs, vue@cli脚手架
下载地址: https://nodejs.org/en
按照提示下载相应平台的安装包, windows/macOs/linux等 完成安装后, 打开命令行:
npm -v
看到显示版本号则安装成功
nvm是nodejs的版本控制, 有时我们需要随时切换nodejs的版本 下载地址: https://github.com/coreybutler/nvm-windows/releases
nvm -v
看到版本号则安装成功
安装指定nodejs版本
# 安装16.x.x版本nodejs
nvm install 16
# 切换16.x.x版本
nvm use 16
# 查看切换后的版本号
node -v
# 查看已安装(可切换)的版本
nvm list
该脚手架提供了命令行级的快捷创建vue项目骨架 安装命令如下:
npm install --global @vue/cli
# 查看vue帮助, 以确保安装成功
vue --help
创建骨架项目:
# 创建名为vue3-learn骨架项目
vue create vue3-learn
# 脚手架会出现大量菜单供选择, 按照目前主流去选取: ts+less+eslint-standard
还可以给已创建出来的骨架项目添加安装项:
# 进入骨架项目
cd vue3-learn
# 为当前项目添加less支持
vue add less
进入已生成的vue项目中, 执行命令:
npm run serve
此时按照启动提示的链接地址,复制到浏览器即可访问, 默认是 http://localhost:8080/
在
serve
开发模式下, 修改任何代码, 浏览器会自动的发生改变而无需手动刷新.
如果要发布则执行打包命令:
npm run build
打包生成的文件目录是dist, 发布只需要将dist拷贝到, web服务器(如nginx)可访问的目录之下即可, 访问时, 只需要像访问静态页面一样输入web服务器对应的访问路径即可;
几乎所有IDE都提供了vue的开发插件, 如Idea Intellij, HBuilder, vscode, eclipse等; 只需要到对应的插件超市搜索"vue", 进行安装即可. vue开发插件提供了, 语法高亮, 检查类型, 追踪定义与调用关系等非常实用的功能;
vue的定义组件采用SFC方式, 也就是单文件组件. 每个.vue文件就定义了一个组件类型
<!-- 来自骨架项目生成的.vue -->
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
@Options({
components: {
HelloWorld
}
})
export default class HomeView extends Vue {}
</script>
<style lang="less">
</style>
一个vue组件组件主要包含三个部分: template, script, style
- template定义的是类似于html5格式的vmdom, 大致理解成视图层即可;
- script定义的是vue组件的生命周期函数, 官方翻译叫钩子函数, 是主要的model模型层;
- style是样式层, 与html的样式是一样的作用;
这里script使用的是ts, style使用的是less, 这些在后续的章节会重点进行说明
ts全称typescript, 目前主流版本为4.5+. js全程javascript, 它是一切web代码的基础, ts最终也会翻译成js运行.
既然最终运行的都是js, 那么为什么不直接编写js, ts存在的意义是什么?
在讨论这个问题之前我们先要知道一系列的ECMAScript规范, 常见有es6, es2015, es2020, 以及esnext;
ECMAScript是一个非盈利的js语法规范制定组织, 我们都知道js诞生于1995年, 但相较于现在编程语言其语法便捷性已经远跟不上, 现在语言了, 所以才出现了ECMAScript来扩充更多的语法.
js天然的有以下不足:
ts正是为了补充js不足而产生的, ts其实最大的特点是让编写代码变得可读性高, 易维护;
ts常以.ts文件命名, 如果vue中需要指明<script lang="ts">
, 写法上多了类型声明:
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
上例中: routes被声明了Array<RouteRecordRaw>
类型, 这是显式声明; router则通过createRouter()返回值的赋值自动推断类型;
es6+ 的esm规范, import与export写法如下: esm是目前主流的js/ts代码模块格式, 此外还有common, 这里不做扩展;
// default导出
const a = 123;
export default a;
// import default, as是重新命令另一个变量来接收的意思, 以下两种写法是等效的
import a from 'a.ts';
import { default as a1 } from 'a.ts';
// export { default } from 等同于import了再export
export { default as a2 } from 'a.ts';
非default的import/export
// 输出a, b 非default
export const a = 1;
export const b = 2;
// 获得非default的所有组成a3集合, 这里是{a,b}
import * as a3 from 'a.ts';
// 单项获取a
import { a as a4 } from 'a.ts';
刚使用ts的开发者特别不习惯, ts的类型声明文件, 也就是以.d.ts后缀的一系列文件. 前面我们讲到, ts是一个强类型的语言, 声明时要么显式的指出变量的类型, 要么通过复制自动推断类型. 总之变量类型一旦被确定后, 就不能被其他类型赋值.
.d.ts文件就是对跨类库调用时, 获得ts的类型.
一般来说, 系统类型都会在node_modules/@types/
目录下有对应的.d.ts类型声明, 如jest, 会有对应的@types/jest
.
但如果是自定义的包, 则需要由tsconfig.json
指定declaration: true
, 并且outDir: './@types'
指定生成.d.ts目录.
declare var // 声明全局变量
declare function // 声明全局方法
declare class // 声明全局类
declare enum // 声明全局枚举类型
declare namespace //声明全局对象(含有子属性)
interface // 声明全局接口
type //声明全局类型
declare module // 与namespace一样, 只不过可以路径, 而namespace是单个词
interface vs type: interface只能声明对象类型(需要new出来), 多个同名定义自动合并, 原始类型不可; type则不能二次定义.
虽说绝大多数时候,只要不作为npm库打包发布给外部安装使用,基本是不会出现手写d.ts文件,就算有d.ts声明文件的需要,typescript内置tsc工具也可以自动去生成。
但是有些特殊情况下,还是需要掌握少量的d.ts类型声明的语法,我们来看下下面的使用场景。
/// 原声明类型: 可能是tsc生成的
interface OSS {
name: string
pathConfig: {
[peth: string]: {
enable: boolean
}
}
}
/// PS: OSS是一个TS声明的类型,拥有name和pathConfig两个属性,其中pathConfig是一个键值对对象类型,key的部分代号为path类型为string,value的也是个对象类型
// 此数据结构,大致是表示某些路径对应的文件是否被启用
下面是调用方:
// 页面调用时可能会,只会对pathConfig进行修改,而不需要总是传递整个OSS对象
// 当需要传递一个pathConfig类型是,ts会要求你必须指定pathConfig是什么类型;
// 如下所示,思考一下,??的部分,我们怎么表示这个类型?
function changePathConfig(path: string, pathConfigValue: ?? ):void {
///.....
}
/// 下面我们介绍两种办法:
/// 办法一:要求OSS单独把pathConfig属性新类类型
interface OSSPathConfigValue {
enable: boolean
}
interface OSS {
name: string
pathConfig: {
[peth: string]: OSSPathConfigValue
}
}
/// 这样??的部分就是OSSPathConfig
/// 弊端:但是这么做非常困难,首先OSS如果是公用组件,原作者和调用方,很可能不是同一个人;第二作为公用组件,会因为调用方细粒不同度拆解成复用度不多,但非常细的各种属性类型,所有属性都要分别写类型,这是不现实的;
/// 办法二:调用方自己创建符合原属性接收的类型,可以这么做,是因为ts的类型校验只是检查成员的匹配,所以没有同源(必须来自OSS下的定,成为同源,java是同源的)的要求。
interface OSSPathConfigValue {
enable: boolean
}
function changePathConfig(path: string, pathConfigValue: OSSPathConfigValue ):void {
///.....
}
// 定义当前.d.ts中xxx.vue的export输出的类型
declare module 'xxx.vue' {
// 输出类型
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
interface aaa {
a: number;
}
// 当前.d.ts输出的类型
export default aaa;
推荐学习: es6中异步编程Promise then/catch, async / await;
这里说的dom指的是, 直接对原始dom进行操作.
所以本章节主要讨论vue的vmdom虚拟dom操作, 与直接对原始dom操作的区别和优略势.
对原始dom的操作通常是, 使用selector获得dom对象, 然后对其调用属性设置,样式设置,以及事件监听等完成界面的变换.
// 原始dom操作
let name = 'abc';
document.getElementById('[name="show_name"]').value = name;
document.getElementById('[name="name"]').value = name;
// vmdom操作
<template>
<div>
<!-- 核心代码: v-model捆绑变量name -->
<input type="input" name="show_name" v-model="name" />
<input type="hidden" name="name" v-model="name" />
<button type="button" @click="setName">设置值</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const name = ref('abc');
function setName() {
// 核心代码: 修改值, 内部自动去影响视图
name.value = 'abc';
}
</script>
代码解读:
- 原始dom操作会每次都会真实的设置dom的值, 即使show_name和name原本值和原来一样;
而vue的虚拟dom发现新的值与原来的值一样,则并不会产生真实的dom操作; 事实上vmdom才有了非常强大的diff算法, 既不会产生无效的dom操作, 同时也不会产生重复的dom操作, 达到性能提升;- 原始dom的写法, 是一种过程式编程, 还额外的产生许多为了selector选择器唯一节点的, 一些id或者class, 而且还需要关系selector出来的原始dom是什么类型, 而且多处使用时每次都要selector额外增加的查询, 既不美观也容易堆积屎山代码;
vue则采用mvvm方式设计, 利用v-model让两个input与变量name产生双向捆绑, 修改name就会产生这两个input的值发生变化.这样一来, 关注点不在是原始dom是什么类型了, 而是如何修改变量name, 剩下的事情vue会帮你把name的变化反应给两个input.
有关mvvm双向捆绑,将在后续章节中重点去讲
less是post-css技术, 最终运行的仍然是css代码, 但post-css可以预编译和有效的组织css.
我们来看less的优势:
// less多级样式
.parent {
font-size: 16px;
.sub1 {
color: red;
}
.sub2 {
color: green;
}
}
// 对应原始css
.parent {
font-size: 16px;
}
.parent .sub1 {
color: red;
}
.parent .sub2 {
color: green;
}
当然less还有许多强大的功能, 如css变量, 多前缀-webkit- / -ms-样式等, 这里就不展开讨论了.
前人在大量界面开发实践过程中, 发现适用于传统后端开发的MVC, 实际上不太适合前端使用, 原因是C层越来越臃肿, 就像前面范例中看到C层总是重复的selector出原始dom; 后来提出了简化MVC的MVVM与MVP两个开发模式, 准确的说MVVM才是最终形态, MVP是不那么完美的MVVM.
MVVM指的是Model层用于定义数据项, View层用于显示的html, ViewModel层用来实现Model层与View层的双向捆绑;
对照第5章中, ref()定义的name变量就是M层, <input/>标签就是V层, 而v-model="name"就是VM层
vue的组件提供常用功能有:
参照1: https://cn.vuejs.org/api/composition-api-lifecycle.html#onbeforeunmount
参照2:https://cn.vuejs.org/api/reactivity-advanced.html#triggerref
vue支持多种定义形式, 以下几种都是等效的:
// 1. setup方式: 推荐-按需加载
<script lang="ts" setup>
/// 不用提供 export default ...
</script>
// 2. 函数方式: 不推荐-代码量太大, 全部都要写
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
...
});
</script>
// 3. 装饰器方式: 推荐-声明与实现分开, 装饰器将在后续章节有介绍.
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import aComponent from 'aComponent.vue';
@Options({
components: {
aComponent,
},
})
export default class App extends Vue{}
</script>
vue的核心功能:
<!-- 父组件:调用方 -->
<template>
<div>
<sub-component propA="123" @change="someFunction" />
</div>
</template>
<script lang="ts">
function someFunction(id: number) {
/// 响应子组件上抛事件
}
</script>
<!-- 子组件:被调用方 -->
<script lang="ts">
import { defineProps, defineEmits, computed, watchEffect, withDefaults, ref } from 'vue';
// 下面是定义props属性, 两种等价写法
// 推荐写法
const props=withDefaults(
defineProps<{
propA: string
}>(),
{
propA: 'abc',
}
);
// or
const props = defineProps({
propA: {
type: String,
default: 'abc'
}
})
//==============
// 下面是上抛事件emit, 两种写法
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// or 推荐写法
const emit = defineEmits<{
change: [id: number] // 具名元组语法
update: [value: string]
}>()
//=============
// 自动计算变量title, 参与计算b发生改变而改变
const b = ref(0);
const title = computed(() => {
return '标题为:'+ b;
})
// 监控watchEffect, 参与计算c发生改变时, watchEffect函数会触发
const c = ref(1);
watchEffect(() => {
console.log(c);
});
</script>
前面说到每个.vue文件就是一个组件, 那么组件内调用另一个组件时, 怎么进行传参呢?
<script lang="ts">
// 定义aProps属性
import { defineProps } from 'vue'
defineProps({
aProps: {
type: String,
default: '',
}
});
</script>
// 调用组件时传参, "123"
<template>
<div>
<aComponent aProps="123" />
</div>
</template>
<script lang="ts">
import { Component } from 'vue-class-component';
import aComponent from 'aComponent.vue';
@Component({
components: {
aComponent,
},
})
</script>
多级调用时, 传参使用provide()和inject()这里不展开讨论.
推荐学习: vue实现的微任务与宏任务, nextTick()等
vue深度集成了webpack打包工具, webpack是目前主流的js编译打包工具, 上边提及的各种语法支持, 以及不同语言转译(ts->js)都是通过webpack来完成的. webpack主要提供了3类api:
hook
钩子函数, 提供了整个编译过程的生命周期函数, 可添加监听函数, 如run, Compile, emit, done;loader
加载器, 一个加载器就是一次编译的过程, 常见的加载器有, ts-loader, vue-loader, less-loader;plugin
插件, 上面提及的hook与loader都需要以插件的形态注册到webpack中, 此外webpack还有许多常用的功能, 如chunk切分大小和个数, 雪碧图, 自动压缩, 代码加固混淆等都是重要的优化手段, 这里就不做展开说明了.
默认配置: src/router/index.ts
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
此外vue还支持路由嵌套, 这里不做展开.
Pinia = vuex5, 它是vue的状态管理框架.
所谓的状态管理, 就是指哪些全局唯一的数据项, 但又不需要被保存下来, 只是针对当前应用内存态(关闭后消失).
全局数据可能会存在, 数据争抢. 同时对这个数据修改和查询, 这就可能出现数据不一致, 也就是事务一致性的概念(那么失败回滚, 要那么全部成功, 不能出现部分成功而未来得及修改剩下的数据) 比如说, 全局数据成功时总共需要修改2项数据, 假如修改第一项与第二项中间, 差一个毫秒, 那就有可能在这一个毫秒内被另一个调用方查询到, 这就是部分成功, 另一部分还没来得及写.
所以Pinia要做的事, 就是没有中间的一毫秒;
Pinia的使用方法如下: Pinia提供了state, getter, actions
// Pinia定义
// store/others.ts
import { defineStore } from "pinia";
import Pinia from "./index";
export const otherStore = defineStore("other", {
state: () => ({
counter: 1,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
actions: {
increment() {
this.counter++;
},
},
});
// 使用Pinia的方式
import { otherStore } from "@store/other"
const other = otherStore();
console.log(other.doubleCount);
other.increment();
指的注意的是, Pinia本身就是响应式的, 这意味着修改Pinia值, 与之双向绑定的视图也会发生变化;
我们经常利用这一点来使用vue的watch()来监听全局变量值发生变化的事件.
如第2章节中的范例, @Options就是装饰器
<script lang="ts">
import { Component, Prop } from 'vue-class-component'
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
@Component({
components: {
HelloWorld,
},
})
export default class HomeView {
@Prop(String)
msg!: string
}
</script>
装饰器是es7提供的, 其本身也是一个函数, 它的作用是拦截原函数调用的前后, 也就是AOP.
如上例中, export default ...
的调用之前, 被@Component拦截, 并追加了components HelloWorld
内容.
一般使用装饰器, 目的是把声明和实现分开, 装饰器通常充当声明的部分, 就像@Prop(String)补充了, 变量是一个props传入属性, 同时类型为String;
经过前面章节的介绍,我们都前端渲染必须需要依赖浏览器运行js脚本,这样就延申出另一个问题,搜索引擎收录网站时需要遍历网站上所有页面,此时搜索引擎并不能执行js,只能是静态的解析html标签。 这就会造成实际被收录的网页是空白的,进而在seo搜索时没有正确的关键字匹配,排名靠后;
针对seo优化,vue提供两种解决方法:sitemap.xml
和伪装SSR
。
sitemap.xml
是一种由网站管理员主动向所有引擎提供的,格式化数据,也就是告诉搜索引擎每个页面应当收录哪些关键字。
这个需要根据各家搜索引擎的规则来提供,以百度为例,它要求域名根目录下直接能访问到sitemap.xml文件;以google为例,它要求网站主自行在google账号后台上传指定域名的sitemap.xml;
本文主要从SSR方案的角度来实现seo优化。 其核心原理是,把vue预渲染生成静态的html页面,这样就能像真实的后端渲染一样支持seo;
需要使用prerender-spa-plugin
插件和vue-meta-info
npm install --save-dev prerender-spa-plugin vue-meta-info
prerender-spa-plugin
的作用是生成静态化html
vue-meta-info
的作用是动态的修改link,title,meta等检索关键字
prerender-spa-plugin
使用const path = require('path');
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
module.exports = {
//publicPath: '/', //打包路径,使用相对路径生成的dist文件夹下的index可以打开
configureWebpack: {
plugins: [
new PrerenderSPAPlugin({
// 生成文件的路径,也可以与webpakc打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(__dirname,'dist'),
// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
routes: [
'/',
'/home',
'/article',
'/download',
'/message',
],
// 这个很重要,如果没有配置这段,也不会进行预编译
renderer: new Renderer({
injectProperty: '__PRERENDER_INJECTED',
inject: {
foo: 'bar'
},
// 在 app.vue onMounted函数中 document.dispatchEvent(new Event('custom-render-trigger')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'custom-render-trigger',
renderAfterTime: 6000,
headless: false
})
}),
]
},
}
PrerenderSPAPlugin.routes
是被静态化的页面
function setup() {
onMounted(() => {
document.dispatchEvent(new Event('custom-render-trigger'))
})
}
const router = createRouter({
//history: createWebHashHistory(process.env.BASE_URL),
history: createWebHistory(process.env.BASE_URL),
routes
})
完成上述配置后,运行后将会在dist下生成静态html页面;
vue-meta-info
使用export default defineComponent({
name: "Home",
metaInfo: {
//改变当前路由的title
title: "前端小阳仔",
//改变当前路由的link
meta: [
{
name: "keyWords",
content:
"前端博客,前端技术,技术博客,前端小知识,前端作品,前端导航,前端小阳仔,前端框架",
},
{
name: "description",
content:
"原创前端技术博客,致力于分享前端学习路上的第一手资料。专注web前端开发、移动端开发、前端工程化、前端职业发展,做最有价值的前端技术学习网站。",
},
],
link: [
{
rel: "前端小阳仔",
href: "https://code-nav.top/myblog/home",
},
],
}
});
宏任务特性:自身就能提供代码的执行能力
我们看下面的执行顺序:
<button onclick="macroClick()">点击事件</button>
function macroClick() {
// todo 代码块1
new Promise(function (resolve, reject) {
// todo 代码块2
resolve();
}).then(function () {
// todo 代码块3
})
// todo 代码块4
}
/////////////
/// 顺序:1 -> 2 -> 4 -> 3
我们再看下面的执行顺序:
<button onclick="macroClick2(); macroClick1();">点击事件</button>
function macroClick1() {
// todo 代码块1
new Promise(function (resolve, reject) {
// todo 代码块2
resolve();
}).then(function () {
// todo 代码块3
})
// todo 代码块4
}
async function macroClick2() {
// todo 代码5
await new Promise(function (resolve, reject) {
// todo 代码块6
resolve();
}).then(function () {
// todo 代码块7
})
// todo 代码8
}
/////////////
/// 顺序:1 -> 2 -> 4 -> 5 -> 6 -> 7 -> 8 -> 3
由于浏览器的渲染(页面看到改变)发生在最近一次宏任务结束,但有的时候我们需要页面渲染出效果之后,再追加其他代码。
如图:点击tag -> 切换input -> input获得焦点
很明显,这一连贯操作,已经跨越了多个事件,也就是多个宏任务
绝大多数情况下,重绘与重排可能会同时发生, 重绘的性能瓶颈是渲染范围,重排的性能瓶颈是排版影响组件个数; 原则上,重绘范围越小最好,重排影响的样式范围越小越好;
vue通过对div的onChange事件监听, 可以对获得子节点的增删事件,一般被当作监听重绘重排的重要方式。
vue2中提供$forceUpdate()强制重绘,在vue3中已经取消,取而代之的时ref/reactive所捆绑的视图层级。
几个重要的参考标准:
- 2d动画低于20帧,感觉明显卡顿,分辨率4k以上帧数/3d还要要求更高;
- 考虑到低网速时5g/3g网络,单次请求小于200k为最佳,大于2M存在明显的不合理;图片,流媒体不算在内;
- 非动画普通网页,一般接收白屏时间在5s内,超过者明显感觉网速太慢; 如果手机上白屏这个数字则是500ms;
思考题6:怎样减少重绘范围,怎样减少重排?
首先我们要明白TS类型校验是怎么实现的: 我们看下面两个例子:
interface MyAttention {
"auid": string,//"10781982",
"icon": string,//"http://www.myccmtv.cn/images/default/noface.gif",
"product_name": string, //"维C使1",
"sign_info": string,//"纽崔莱(上海)医药服务有限公司",
"update_num": string,// "5",
"attent_time": string,//"2023-11-08 17:22:36",
"product_url": string,//"专区跳转链接,待定"
}
interface MyAttention1 {
"auid": string,//"10781982",
"icon": string,//"http://www.myccmtv.cn/images/default/noface.gif",
"product_name": string, //"维C使1",
"sign_info": string,//"纽崔莱(上海)医药服务有限公司",
"update_num": string,// "5",
"attent_time": string,//"2023-11-08 17:22:36",
"product_url": string,//"专区跳转链接,待定"
}
/// 声明变量a为MyAttention类型
const a: MyAttention = {
"auid": "10781982",
"icon": "http://www.myccmtv.cn/images/default/noface.gif",
"product_name": "维C使1",
"sign_info": "纽崔莱(上海)医药服务有限公司",
"update_num": "5",
"attent_time": "2023-11-08 17:22:36",
"product_url": "专区跳转链接,待定"
}
/// 使用MyAttention1类型的b来接收a
const b: MyAttention1 = a;
从上例中,我们发现MyAttention与MyAttention1被声明为两个不同的类型(既没有继承也没有混入),但是TS却可以让MyAttention1直接接收MyAttention变量来正常使用。
这说明TS的类型检查本质上是遍历属性签名的检查,类似与下面的伪代码:
function isMyAttention1 (a) {
if (typeof a['auid'] === 'string' &&
typeof a['icon'] === 'string' &&
typeof a['product_name'] === 'string' &&
typeof a['sign_info'] === 'string' &&
typeof a['update_num'] === 'string' &&
typeof a['attent_time'] === 'string' &&
typeof a['product_url'] === 'string'
) {
return true
}
return false
}
理解这一点后,就方便我们指导TS的类型推导是怎么做到的。
首先TS的类型检查是静态检查,所谓静态检查是这仅在编译时检查,运行或打包出去时不会含有任何TS痕迹的。
类型推导,又叫类型演算,他是对自定义的泛型,进行从key,value到取值范围在内的一系列约束。
一般写法为T infer
,常与extends
来联用。
TS类型推导写法上有点像三元表达式isTrue? xxx: yyy
, 以never
为结束,表示never
只是做来填充错误的情况,
大致可以理解为任何类型不能是never
,一旦满足成为never
的条件时IDE(vscode/InterllJ IDEA)就报类型错误。
所以TS类型推断书写的目的就是不让类型走向
never
// type类型别名
type aliasType = number;
// interface定义对象类型
interface ObjectType extends Number {
id: number,
avatar: string,
}
// 内置类型
// Record - 当作key,value来理解对象的属性
type StringStringMap = Record<string, string>
// 联合类型
type MyUnionType = string | number; // 任选其一,非黑即白
// 交叉类型
type MyIntersectionType = MyUnionType & StringStringMap; // 属性签名融合后,存在灰度
// Partial - 全部属性签名变成非必填
type par = Partial<{
username: string,
nickname?: string,
}>// 结果是 { username?, nicknaeme? }
// Required - 全部属性签名变成必填,与Partial相反,省略不讲
// Readonly - 全部属性变为只读,省略不讲
// Pick - 抽取指定名字的属性签名
type pi = Pick<{ a: string, b?: string, c?: string }, "a" | "b">;// { a , b }
// typeof - 把属性当作map解析取value的联合类型
type OT = { a: string, b: number }
type Tyof = typeof OT;// { string | number }
// keyof - 与typeof相似,获取的是key的联合类型,省略不讲
// 字面量类型枚举约束
type ChooseName = 'a' | 'b' | 'c' | 'd'; // 仅允许赋值
const n: ChooseName = 'f';// 错误f不在允许值范围
// 内置条件类型:
// Exclude - 从一个类型中排除另一个类型,同样也是指属性签名
type Exclude<T, U> = T extends U ? never : T;
type R3 = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R3: 'd'
// Extract - 从一个类型抽取出另一个类型,相同的部分
type Extract<T, U> = T extends U ? T : never;
type R4 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R4: 'a' | 'b' | 'c
// NonNullable - 从类型中那种必填属性
type NonNullable<T> = T extends null | undefined ? never : T;
type R5 = NonNullable<'a' | null | undefined | 'd'>; // R5: 'a' | 'd'
// ReturnType - 从函数类型中拿走返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
function getUser() {
return {
name: '张三',
age: 10
}
}
type ReturnUser = ReturnType<typeof getUser>; // type ReturnUser = {name: string;age: number;}
// Parameters - 从函数类型中拿走参数列表类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function getPerson(a: string, b: number) {
return {
name: '李四',
age: 18
}
}
type ParamsType = Parameters<typeof getPerson>; // type ParamsType = [a: string, b: number]
思考题7:type与interface定义的类型有什么区别? 思考题8:联合类型与交叉类型有什么不同?什么情况下相同什么情况下不同? 思考题9:对Record类型进行keyof和typeof会得到什么类型?
reactJS = vueJS: reactJS函数式组件, vue的SFC单页面组件, 各有优缺点;
webpack -> vite: vite/rollup更灵活配置项更少,天然的按需加载,打包体积更少;
vue2 -> vue3: vue3的proxy提升双向绑定查找数据20%以上性能,响应式ref/reactive脱离VNode实例,script-setup按需加载减少代码量;
参数配置 -> 装饰器: 装饰器的优势(也就是AOP的优势),非常干净的实现多次改写,分段配置,传统做法需要大量值传递。如react reduct.
vue在社区的贡献下, 还全面支持了web向移动端ios, android, 小程序端wx, 百度小程序, 钉钉小程序的转译支持. 桌面端也有对应的实现框架.
可以说学会vue就一统大片前端技术.
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。