搭建步骤
一、创建项目
- 使用create-vue创建项目:npm init vue@latest
- 好处:创建项目时可以选择vue的全家桶
- 使用vite创建项目:npm init vite@latest vite-ts -- --template vue-ts
- 特点:项目里只会安装vite,不会提示安装全家桶。
二、规范项目
定义规范
比如:
- 目录、变量、组件的命名方式
- 是否统一使用分号结尾?使用单引号/双引号?
具体我们会使用以下方式进行规范
配置eslint和prettier规范项目:
- eslint:
- 运行代码前就可以发现潜在错误
- 适合用于制定团队代码规范
- 规则分为3个等级:off、warning、error
- prettier
- 代码格式化工具,用于检测代码中的格式问题。
- eslint偏向于把控代码质量,prettier偏向把控代码风格。
- 配置步骤
- 安装eslint和prettier(脚手架已经帮我们完成)。
- 安装插件
- ESLint:不符合规范的代码提示错误
- Prettier - Code formatter:保存代码时使用这个插件进行自动修复
- 在eslintrc.js文件中配置自定义规则:rules。
- 配置prettier,新建.prettierrc.js文件并添加规则。
- 注意,每次修改完.prettierrc.js文件都要reload window(ctrl+shift+p),否则规则不生效
根据规范检查代码
使用commitlint校验commit信息
- 步骤
- npm安装
- 配置(新增一个js文件)
- 执行命令,在husky中增加hooks:commit-msg
- 参考网址,github.com/conventiona…
- 为什么需要使用commitlint:commit记录了项目的开发进程,良好的commit信息可以回顾项目开发进展,当时的需求背景,动机。也方便做一些版本回退。
- 如何规范:type(scope?):subject
- type,提交的常用类型
- feat(新功能)
- fix(修复)
- perf(性能)
- refactor(重构)
- scope,影响的范围,可选。
- subjtct,提交的详细说明。
- type,提交的常用类型
- 如何起作用:commit后,commitlint借助husky使用commit-msg钩子校验填写的commit信息。
配置lint-staged来对esliint和prettier中配置的规范进行校验
- 每次对所有文件执行lint操作没必要,通过lint-staged只对提交到暂存区的内容进行格式化
- 步骤
- 安装,npm i lint-staged -D
- 在package.json文件中增加配置项
- 修改husky的pre-commit钩子的触发命令
npx lint-staged
三、安装需要的库
配置ui库
- npm install安装组件库
- 配置ui组件的引入方式
-
全局注册
- 在main.ts中import组件和样式,然后使用app.use注册组件
-
局部注册
- 直接在.vue文件中引入组件,但是样式文件还是需要全局引入的。
-
按需引入组件和样式
- 通过unplugin-vue-components插件进行按需引入
- 步骤
- 安装插件
- 在vite中配置插件
- 直接在模板中使用vant组件
- 引入函数组件的样式:有些组件是以函数形式提供的,插件无法自动引入组件以及对应样式。所以还需要在入口文件中引入样式。
-
tree-shaking
- 作用:消除死代码的性能优化理论,以减少最终生成包的大小。死代码包括js和css
- js代码是通过es6的import和export语法进行tree-shaking的
- css代码无法通过js代码那种方式进行tree-shaking,只能通过插件进行按需引入组件和样式。unplugin-vue-components
-
配置vue-router
- 步骤
- 安装(脚手架一般帮我们已经安装好了)
- 注意一定要分模块配置路由,不要所有路由都在一个index.ts文件中。
配置HTTP请求库
- 步骤
- 安装,npm install axios -s。
- 创建实例
- 在文件/api/base.ts中创建实例
- 配置响应拦截器和请求拦截器
- axios有以下特性
- 支持Promise API
- 拦截请求和响应
- 取消请求
- 自动转换JSON数据
四、对代码进行必要的封装,这里主要指的是对HTTP的请求和响应进行统一的处理
- 比如,发出请求前,统一添加cookie,或其他自定义响应头。
- 比如,请求响应时,对错误进行统一归类处理。
五、配置好用的工具提高书写代码的效率
配置husky管理Git hooks
- 步骤
- 安装husky,npx husky-init。
- 执行完命令,项目根目录下会自动创建一个.husky目录。并把husky安装到devDependencies中。
- 安装husky,npx husky-init。
- hooks:一些时机的回调
- Git hooks:是在特定的Git操作(如提交代码、推送代码等)发生时自动触发的脚本,允许开发者在这些事件发生前或发生后执行自定义操作。常见的钩子:
- pre-commit:在提交代码前执行,用于进行代码检查、格式化等操作。
- pre-push:在推送代码前执行,用于运行测试或进行其他验证。
- prepare-commit-msg:在生成提交消息(commit message)时执行,可用于自动化生成提交信息。
- post-checkout:在切换分支后执行,用于更新依赖、执行其他操作等。
- 如何使用Git hooks
- 自己写脚本
- 对git有一定了解
- husky:快速管理Git钩子的工具,Husky的作用是简化Git钩子的配置和管理过程,同时提供了一套易于使用的接口。通过Husky,开发者可以在项目中的Git仓库中定义各种钩子,例如在提交代码前运行单元测试、在推送代码前执行代码风格检查等。(让大头儿子变小头爸爸)
- 使用husky的好处:校验commit信息、运行测试代码、校验代码格式..
配置commitizen和cz git生成规范式commit信息
- 步骤
- commitizen是生成标准化提交信息的命令行工具
- cz git集成了git和commitizen,使得commitizen更方便地与git交互,直接使用git cz命令就可以触发交互式提交流程,并将生成的提交信息直接应用到git的提交中。
六、做适配(如果是移动端)
使用rem方案+postcss实现移动端适配
- 根据一个基准值,设置不同的屏幕宽度时,根字体的大小。当屏幕宽度发生变化时,自动根据基准值改变根字体大小。基准值一般通过设计稿来确认,比如设计稿为屏幕宽度为390px宽度,根字体大小为16px;这个比例就固定了,即fontSize/curDeviceWidth=16/390。再通过当前设备的实际宽度,就可以推算出根字体大小。fontSize = curDeviceWidth * (baseFontSize/baseWidth)
- postcss
- 是一个使用
JavaScript
工具和插件转换CSS
代码的工具,它的主要功能其实是可以利用JavaScript
去处理一些原生的CSS
或者预处理器(Less、Sass
)处理不了的工作。 - 它有很多插件供我们使用,比如
- autoprefixer:根据项目中指定的浏览器种类和版本,查询不同浏览器对css属性的兼容情况,自动为css属性添加不同的浏览器前缀。
- postcss-pxtorem:自动将px转换成rem,即实际书写时用px即可,但是在浏览器查看样式时,会自动转换成rem单位。
- 是一个使用
- 步骤
-
设置根字体大小
-
npm i postcss autoprefixer postcss-pxtorem -D
-
配置postcss.config.js文件,注意配置完之后需要重启项目,否则不生效
-
vite
定义
- 一种新型的前端构建工具,能够显著提升前端开发体验
组成部分
- 一个开发服务器:它基于原生ES模块,提供了丰富的内建功能,比如速度快到惊人的模块热更新(HMR)
- 一套构建指令:它使用Rollup打包代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源
为什么使用vite
- 当应用越来越大型时会出现性能问题
- 启动本地开发服务器的时间会很久
- 热更新很慢
- vite如何优化的上面两个问题
- 启动本地开发服务器的时间会很久
- 依赖:使用esbuild进行依赖项预打包,esbuild使用Go编写,会比javascript-based的打包工具快10-100倍
- 源码:使用浏览器原生es module提供源码,让浏览器接管打包工具的部分工作
- 热更新很慢
- 使用ESM不需要重新编译:一些打包工具的开发服务器在文件更改时,需要重新构建整个项目,来获取新的模块依赖关系。而vite使用原生es模块提供源码,模块的依赖和加载是由浏览器接管的,所以文件更新时,vite不需要知道依赖关系,即不需要重新构建,只需要直接更改对应模块的引用链接即可
- 使用浏览器缓存加速:因为模块的加载是由浏览器接管的,那么Vite利就用HTTP头来加速整个页面的重新加载
- 启动本地开发服务器的时间会很久
- 开发服务器对比
基于打包的开发服务器返回给浏览器的是bundle,基于esm的开发服务器返回给浏览器的是模块本身,而bundle包括模块本身和模块间的依赖关系。因为之前的浏览器不支持模块加载,所以打包工具需要根据模块的依赖关系管理整个项目的模块加载,整个依赖关系需要重新构建并分析整个项目得到的。而vite使用浏览器原生es模块管理模块的加载,这样就同时将模块的依赖关系图托管给了浏览器,那么文件更新的时候就只需要替换对应的模块内容,以及其它模块引用该模块的链接即可。
可能遇到的问题
配置完eslint和prettier后,不符合规范的代码并没有提示错误。
- 需要安装插件:eslint
prettier.json文件整个不生效
- 需要在eslintrc.cjs中加配置
保存时并没有按照prettier的规范自动格式化代码
-
需要在vscode中的settings.json文件中加一些配置
-
添加最后一个配置时,提示错误。
- 未安装插件:Prettier - Code formatter
使用命令初始化项目后,发现所有引入.vue文件的地方全部飘红,提示找不到模块。
- 需要安装插件TypeScript Vue Plugin (Volar)
- 安装完后如果仍然飘红,检查使用的ts版本,有可能版本过低。
配置完lint-stage,执行git commit -m 命令时出错
npx
产生背景
- 有些时候,我们只是临时想要使用一些 cli 工具,比如
create-react-app
,我们可能只是需要生成一个 React 项目。但是npm
不能够在不将包安装到本地的情况下,使用相关的依赖,所以 npx 出现了
和npm的区别
npm
(Node Package Manager)是 JavaScript 的包管理器,用于安装、管理和发布 JavaScript 包。它是 Node.js 的默认包管理器,提供了大量的功能,例如安装依赖、管理版本、运行脚本等。通过npm
,开发者可以轻松地下载和安装其他人编写的 JavaScript 包,以便在自己的项目中使用。npx
是一个用于执行 Node.js 包的命令行工具。当你在命令行中使用npx
后面跟着包的名称和命令,它会依次检查项目
、全局
是否已经安装了该包,如果有,就执行该包对应的bin目录下的命令,如果都没有,它会自动下载并执行,执行完后就立即删除。
作用
- 执行本地
已安装
的依赖包命令,不用在scripts脚本写入命令。- npm 本身不能够执行任何包,对于本地项目的包,如果想要执行,则需要写入到
package.json
里面,然后通过 npm 来解析package.json
文件,解析到包的 bin 文件路径,在 bash 中执行。有了npx可以不用在scripts中配置命令直接通过npx ...就可以执行命令,比如npx lint-staged。
- npm 本身不能够执行任何包,对于本地项目的包,如果想要执行,则需要写入到
- 执行
未安装
的依赖包的命令,先下载,用完即删。
执行命令的几种方式
- 使用
package.json
的scripts脚本,在命令行中通过npm run * 运行 - 在命令行中直接找到模块的二进制文件运行
- 全局安装模块
开始开发项目
4-4 安装vant-ui和使用vite按需加载组件
- 使用npm install安装vant-ui
- 引入全局样式文件
- 遇到的问题
- 快速生成vue模板
- 可以使用snippet插件,但都不是自己想要的代码片段
- 自己在vue.json文件中配置用户代码片段
- .vue文件提示Delete
␍
eslintprettier/prettier错误- 在eslint配置文件中增加一条规则,忽略结尾符的检查
- 快速生成vue模板
4-5和4-6 配置vue-router
- 放置内容组件
- 配置组件和路由间的对应关系
- 触发路由跳转的地方(如果需要)
- 将路由实例挂载到全局(只有第一次时需要)
4-7和4-8 使用json-server搭建MockServer
- 自定义目录结构
- 创建服务
- 搭建路由
- 创建测试数据
- 在controller中处理测试数据(可有可无)
- 在db.js中配置路由和数据的对应关系
- 使用nodemon自动重启服务,这样不必每次修改完文件自己手动重启。
4-9 使用vite配置请求代理
- 将node版本从14..升级到18..
5-1 页面头部实现
课程目标
- 安装和引入normalize.css,处理不同浏览器的默认样式
- 我们在开发的时候会发现很多样式都自带了各自特有的默认样式,而这样样式通常会被遗忘,导致样式调整起来很繁琐。为了让样式统一,我们在开发的时候通常会对一些元素进行样式重置,已避免默认样式影响开发。Normalize.css就是一个这样的CSS样式文件,它的作用就是让HTML元素更好的实现跨浏览器一致性,是一种现代的、为HTML5准备的CSS重置替代方案。
- 特点
- 标准化的样式,适用于大部分HTML元素;
- 保留有用的浏览器默认样式,而不是全部样式“重置”;
- 修复了浏览器BUG并保证浏览器样式的一致性;
- 安装css预处理器sass
- 设置字体抗锯齿
- 封装TheTop组件
额外收获
- flex:1实现左右两侧布局,不再使用justify-content:space-between,这样减少了dom的数量。因为flex:1代表弹性盒子的子元素将平均分配剩余空间
- 给图片加样式时,类名全部使用...-icon,使得代码更规范
- 背景色渐变:backgroundcolor:linergradient(direction,color1,color2,...color)
- 改变ui库的样式时,不一定要使用样式穿透。在非scoped的
<style>
标签中直接通过类的样式名修改即可。 - 子组件如何接收父组件的传值,通过defineProps。它接受一个对象作为参数,该对象用于描述组件的属性和属性类型。
- 将所有定义的类型文件全部放到types目录下,且为了引入时不必通过引入具体的文件而使用里面的类型,直接定义一个index.d.ts文件并把每个类型文件都导出*即可。
5-2~5-4 封装基础组件Search
课程目标
了解BEM规范 - 一种css命名的规范
- 全称:Block(块) Element(元素) Modifier(修饰符)
- -中划线:某个块或某个子元素的多单词之间的连接记号
- __双下划线:连接块和块的子元素
- --双中划线:描述块或者块的子元素的一种状态
如何设计一个组件
- 布局分析
- 功能分析(有哪些属性,有哪些事件)
- 可以用slot定义一个自定义区域
如何给子组件传递v-model
- 背景:vue是单向数据流,子组件可以接收父组件的props,但是不能修改,否则会导致数据的应用流向变得难以理解。但是有时候我们希望子组件数据的修改,父组件的数据会随之变化。在vue2中提供了.sync修饰符,但是在vue3中不再支持.sync,取而代之的是v-model。
- 父组件给子组件传递单个v-model:默认情况下,子组件和父组件都使用v-model属性,子组件接收时使用modelValue来接收父组件的v-model。子组件修改modelValue值时,需要调用update:modelValue事件,父组件的v-model就会随之改变。
- 自定义modelValue的名字
- 如果不想使用默认的modelValue名字,想自定义名字,需要在父组件的v-model中进行声明:v-model:show,子组件就可以用show来接收父组件的v-model,事件也自然变成了update:show。
- 自定义modelValue的名字
- 父组件给子组件传递多个v-model。
<my-component v-model:hello="hello" v-model:word="word"></my-component>
- PS:自己走的弯路:我把子组件需要给v-model绑定的值,也是通过defineProps传递的,导致报错,因为默认子组件不允许修改父组件传递的值。所以我给父组件传递了一个对象,里面定义了所要传给子组件v-model的属性。子组件接收到这些属性以后,在定义一个新的对象,也包含这些属性。然后把这些属性绑定给v-model。然后再定义一系列事件,去通知父组件这些属性的变化,父组件去手动更新每个属性。
使用defineEmits定义组件的事件
defineEmits
用于在setup
中注册自定义事件,是一个宏函数,使用时无需导入defineEmit
可以使用运行时声明,此时接收一个数组为参数,数组元素为自定义事件的名字;也可以使用类型声明,此时参数为一个对象,里面是一系列函数的声明。- 运行时声明:代码运行后,给出相关ts提示。
- 类型声明:在代码运行之前,即编写代码时(编译阶段)就会结合IDE给出相关ts提示。
defineEmit
返回一个触发器,用于触发事件,第一个参数是具体事件,第二个是传递的值。
如何定义css变量
- 首先css变量要定义在伪类选择器:root内,它一般情况下是表示
<html>
元素,定义 在:root
后,所有变量都将被保存在:root
中,并且可以在整个页面的任何位置使用。 - sass中使用变量:var(变量名字)
额外收获
- & 表示嵌套的上一级。这是sass和less中的语法,代表上一级选择器。
- 插槽
<slot>
不能绑定事件,要在插槽外层包裹一层标签才可以绑定事件- 如何给插槽设置默认值:把默认值放在标签中间即可。
- 在子组件中如何拿到父组件传过来的slot?
- $slots['slot-name']
5-5~5-6 自定义hooks - useToggle,实现搜索页展示切换
课程目标
- hooks介绍
- hooks就是函数的一种写法,以函数形式将可复用性内容提取出来,和vue2中的mixin类似。
- 和utils的区别,如果涉及到要和组件的业务进行交互,要用到页面中的状态,或者要把一个ref变量返回出去供页面使用,即要在hooks里面使用ref这些,就使用hooks,反之使用utils。hooks是有状态的,utils是无状态的纯函数。
- 如何实现一个hooks
- 所有的hooks都定义在use或hooks文件夹下
- 文件命名方式为useXX.ts
- 事件传递实现跨组件通信
- 子组件如何和爷组件进行通信?
- 子传父,父传父。
//useToggle import { ref } from 'vue'; import type { Ref } from 'vue'; export function useToggle(initState: boolean): [Ref<boolean>, () => void] { const state = ref(initState); const toggle = function () { state.value = !state.value; }; return [state, toggle]; }
- 注意上面定义useToggle的返回值时使用了数组而不是对象,这样做的好处:
- 使用数组,更加简洁地表明返回值由两部分组成,使用这个函数时,可以直接通过解构赋值获取每一部分,而且可以在不同的组件中使用不同的命名。如果使用对象的话,想要在解构的时候重命名的话,较繁琐。
- 子组件如何和爷组件进行通信?
额外收获
- 调用函数的时候,什么时候需要加(),什么时候不需要加()?
5-7~5-9 业务组件SearchView开发
课程目标
- 使用
<transition>
和<transition-group>
组件实现切换到搜索页时有一个渐变的动画效果<transition>
和<transition-group>
组件是vue提供的用于给任意元素或组件添加进入或离开时的过渡效果的组件<transition>
适用于单个元素,<transition-group>
适用于多个元素- 使用时要给过渡组件添加name属性,然后定义class时要以name属性值为前缀,这样就能够自动应用过渡样式了。
- Search组件复用
- computed计算属性
- watch监听属性
- 使用axios实例发送业务请求
- mock请求
- 实现utils版的防抖和hooks版的防抖
需求分析
额外收获
- 定义接口返回数据的类型
- 用await去接收请求的接口数据,如果发生了错误,如何接收?
- 用try catch 或 try finally
- 数组解构
- 数组解构就是能快速提取数组中的指定成员(某一个值/全部值)
const [INIT, DONE, DOING] = [-1, 0, 1] //提取全部值 // INIT = -1; DONE = 0; DOING = 1; const [,,DOING] = [-1, 0, 1]//提取一个值 // DOIING = 1; const [INIT,...ALL] = [-1, 0, 1]//用...提取剩余全部值 // ALL = [0,1]
- 定义泛型函数
- 泛型函数:在函数定义中使用泛型类型参数的函数。
function transform<T, U>(arg: T): U { } //参数类型是T,返回值类型是U
- 如何接收一个响应式类型的参数?
value:Ref<T>
- 组件销毁时要停止监听,那么如何在组件销毁时停止watch的监听?
const unwawtch = watch(...) onUnmounted(()=>{ unwawtch() })
5-10~5-11 自定义hook - useDebounce
//utils
//定义
interface IDebounceFn<T> {
(...args: T[]): void | Promise<void>;
}
export function useDebounce<T>(fn: IDebounceFn<T>, delay: number) {
let timer: number | null = null;
return function f(this: void, ...args: T[]) {
if (timer) {
clearTimeout(timer);
} else {
timer = setTimeout(() => {
fn.call(this, ...args);
}, delay);
}
};
}
//使用
watch(
searchValue,
useDebounce((nv) => {
if (!nv) {
searchResult.value = [];
return;
}
onSearch(nv as string);
}, 1000)
);
```
```js
//hooks
//定义
import type { Ref, UnwrapRef } from 'vue';
import { ref, watch, onUnmounted } from 'vue';
export function useDebounce<T>(value: Ref<T>, delay: number) {
const debounceValue = ref(value.value);
let timer: number | null = null;
const unwatch = watch(value, (nv) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
debounceValue.value = nv as UnwrapRef<T>;
}, delay);
});
onUnmounted(() => {
unwatch();
});
return debounceValue;
}
//使用
const debounceValue = useDebounce(searchValue, 1000);
watch(debounceValue, (nv) => {
if (!nv) {
searchResult.value = [];
return;
}
onSearch(nv as string);
});
5-12~5-13 自定义hook - useAsync
课程知识点
- Promise then 和 catch的处理
- TS声明复杂的类型结构
- 使用jsonserver中间件延时返回数据
需求分析
- 新建一个fetchHomePageData的api
- 实现useAsync,将api包裹一层,处理Promise的状态
额外知识点
- 类型UnwrapRef的作用
UnwrapRef
是一个用于取消响应式对象封装的函数,在某些情况下,我们可能需要获取响应式对象的原始值,而不是封装后的响应式代理。这时候就可以使用UnwrapRef
函数。UnwrapRef
函数会返回一个非响应式的对象,这样我们就可以直接获取到原始的值,而非代理对象。
- vue3中的响应式
- vue3.0中的响应式原理是基于proxy做的,而使用proxy的前提是,我们要代理的是对象而不是基本类型数据。所以就需要用ref将基本类型的数据包装成{value:基本类型数据},然后再对这个包装对象进行响应式处理
5-14~5-15 封装LoadingView组件
课程知识点
- 实现加载loading和骨架屏
需求分析
- loading=true时展示loading样式
- loading样式有默认的,也可以自定义(通过插槽实现)
- loading=false时展示正常样式(也要通过插槽实现,因为每个页面的样式都不一样)
5-16 业务组件Grid布局实现Transformer金刚区
课程知识点
- Grid布局
5-17 业务组件ScrollBar 滚动提示栏组件开发
课程知识点
- 封装hooks:useInterval & useTimeout
- useTimeout钩子有bug待解决
- ref获取DOM
//template
<div ref="containerRef" style="border: 1px solid red"></div>
// script
const containerRef = ref();
const container = containerRef.value;//container即拿到了dom
//container.children可以拿到它的子元素
- withDefaults设置props默认值
const props = withDefaults(defineProps<IProps>(), { intervalTime: 3000, transition: 1000, height: 40 });
<style>
使用<script setup>
里的js变量- 通过v-bind指令
- v-html渲染html模板
- transform:scale实现小于1px的边线
- 如果直接在元素上使用 border 的话,因为会使用 transform: scale(0.2) 把边框线变细,但这样也会把整个元素也缩小,所以这里使用伪元素 :before 来画边框线,这样缩小边框线就不会影响到整个元素。
- ScrollBar轮播组件的原理
- 动态设置container的高度
- 每次轮播完所有元素时,要把第一个元素接到后面,否则动画就不连续了
- 第一个元素的动画执行完后,要把整个container重置位置。并且是要等一个transition时间后即等本次动画结束后,再重置,不能立即重置。不然会导致最后一次动画没有执行。
- 注意:把第一个元素接到后面之后,其实整个container的位置并没有变,还是在一轮动画轮播后的那个位置。只是第一个元素的位置空了。所以要把container的位置重置。这样动画就能连续了。
- 我的疑问是为什么不能直接把container接到后面呢,而非要把第一个元素接到后面呢?实践之后发现根本没办法把container接到后面,因为container在执行动画,你不能让它既干这又干那。
需求分析
5-21-5~24-自定义hooks-useCountDown实现倒计时逻辑
课程知识点
- 使用requestAnimationFrame(或setTimeout)计时
- requestAnimationFrame是浏览器提供的一个动画帧回调函数,浏览器1秒内刷新60帧。每刷新完一帧就会触发requestAnimationFrame函数。帧动画回调函数的执行次数取决于「屏幕刷新率」,以 60Hz(表示每秒钟图像刷新的次数)的屏幕来说,约 16.7ms 会刷新一次。
- 为什么不使用setTimeout?
- 因为setTimeout有时不准
- useCountDown的设计
- start方法:开始计时
- pause方法:暂停计时
- reset方法:重置时间
- current变量:当前时间
- remain变量:倒计时的剩余时间(用于暂停计时后重新计时)
- 性能优化:毫秒级/非毫秒级更新
- 判断是否需要毫秒级的计时
- 当前剩余时间currentRemain是否与remain为同一秒,这样可以大大减少页面渲染的次数(不懂这里是为什么)
额外知识点
- 获取当前时间戳:
new Date().getTime()
或Date.now()
- 在执行一万次时,Date.now()的性能优于new Date().getTime(),会快2ms。所以基本可以忽略不计。
- Math.max(a,b)
5-25~5-31 基础组件swipe轮播图
课程知识点
- useParent和useChildren使用provide/inject实现跨组件通信
- getCurrentInstance获取组件实例
- useExpose实现暴露组件方法
- useTouch实现touch封装
- useEventListener实现事件监听
- onMountedorActivated封装生命周期
额外知识点
- 书写vue组件的另一种方式:tsx
import { defineComponent } from 'vue';
export default defineComponent({
name,
props: {},
setup() {
return () => {
<div></div>;
};
}
});
- 作类型断言时,const可以作为类型。as const。意思是它为只读的字面量类型。指定为字面量类型后,ts不会再做额外的类型断言从而触发类型错误了。
export function createBEM(name: string) { return (el?: string, mods?: Record<string, boolean>) => { let result = `${name}${el ? `__${el}` : ''}`; if (mods) { const modsStr = Object.keys(mods) .filter((mod) => mods[mod]) .map((mod) => ` ${result}--${mod}`) .join(''); result += modsStr; } }; } export function createNamespace(name: string) { const prefixedName = `op-${name}`; return [prefixedName, createBEM(prefixedName)] as const; } // 如果不加as const,则使用createBEM时ts会推断出它可能为一个字符串。而我们不能对字符串进行函数调用,加上as const后,ts就不会再推断它为一个字符串了。
Record<string, boolean>
是 TypeScript 中的一种类型表示,它表示一个键为字符串、值为布尔值的对象。- 在 Vue 3 中,使用
computed
函数创建的计算属性返回一个带有value
属性的响应式对象。 - setup函数
- 泛型语法
export function useExpose<T=Record<string,any>>(apis:T){}
onActivated
和onDeactivated
生命周期- 这两个生命周期只有在keep-alive模式下才可以使用。
- onActivated:进入页面时会触发。当组件初次加载时会执行onMounted与onActivated,当从别的页面跳转到指定页面时,只有onActivated会被触发。
- onDeactivated:离开页面时会触发。当组件销毁时会执行onUnmounted与onDeactivated,当从别的页面切回指定页面时,只有onDeactivated会被触发。(但还是不明白它俩的区别,怎么区分什么时候是组件销毁,什么时候是切页面呢?)
Ref
Ref
是用来包装响应式数据的对象。Ref
类型通常用于对一个数据进行响应式包装,使其在数据发生变化时能够触发视图的重新渲染。
EventTarget
- 用来表示可以成为事件目标的对象,比如 DOM 元素
5-32 使用<component>
实现标签页动态展示内容
课程知识点
<component>
内置元素的介绍和使用- 内置特殊元素
- Vue默认提供的一些特殊的模板语法
- 具有类似组件的特性,但它们并非真正的组件,例如
<component><slot><temolate>
,在模板编译期间就会被编译掉。
<component>
- 用于渲染组件或元素的“元组件”
- 渲染的实际组件由is属性决定
- 当is是字符串,它既可以是HTML标签名,也可以是组件的注册名。
- is也可以直接绑定到组件的定义。
- 内置特殊元素
- VantTab的使用
- 粘性布局和VantSticky的使用
- sticky组件默认隐藏,通过滚动监听sticky组件是否滚动到了原本位置,如果是,就将sticky组件设置成fixed布局。
5-35~5-37 基础组件list组件实现滚动加载
课程知识点
useRect.ts
useScrollParent.ts
额外知识点
- onUpdated生命周期
- 当响应式状态发生变化而更新其DOM树之后调用。
5-38~5-39 商家列表项组件开发
课程知识点
- 阻止事件冒泡
- 使用.stop修饰符
5-40~5-41 性能优化-使用IntersectionObserver实现图片懒加载directive
课程知识点
- 图片懒加载的介绍以及实现原理
- 一般跟分页加载、整屏加载有关
- 图片在屏幕不可见区域
- 图片被滚动到可见区域的时候,才去加载图片资源
- IntersectionObserver API介绍
- 什么是IntersectionObserver
- 一种异步检测目标元素与祖先元素或viewport相交情况变化的方法
- viewport就是视口,一般就是指可见区域
- 除了图片懒加载可以使用,还有内容无限滚动
- 如何使用IntersectionObserver * 创建一个intersection observer对象 * 给定一个目标元素进行观察
- 什么是IntersectionObserver
- Vue3 指令Direction以及插件机制的介绍
- Vue3指令Direction
- 指令指的是v-model、v-if...
- 重用涉及普通元素的底层DOM访问的逻辑
- 由一个包含类似组件生命周期钩子的对象来定义
- 如果我们想定义一个全局指令,可以借助插件机制来完成。这样是为了让代码看起来更简洁,清晰。也可以通过app.directive()直接注册到全局组件上,但是这样逻辑就要写在main.ts中了。以下的实例分别是通过插件和直接注册的方式来实现的懒加载指令。
//插件机制 import type { App, DirectiveBinding } from 'vue' const vLazy = (observer: IntersectionObserver) => { return { beforeMount: (el: HTMLImageElement, binding: DirectiveBinding) => { el.classList.add('op-lazyload') const { value } = binding // <img data-origin="" /> if (value) { el.dataset.origin = value observer.observe(el) } }, beforeUpdate: (el: HTMLImageElement) => { observer.unobserve(el) }, updated: (el: HTMLImageElement, binding: DirectiveBinding) => { el.dataset.origin = binding.value observer.observe(el) }, unmounted: (el: HTMLImageElement) => { observer.unobserve(el) }, } } const lazyPlugin = { install(app: App) { const observer = new IntersectionObserver( (entries) => { entries.forEach((item) => { if (item.isIntersecting) { // 开始加载图片,把 data-origin 的值放到 src const el = item.target as HTMLImageElement el.src = el.dataset.origin as string el.classList.remove('op-lazyload') // 停止监听 observer.unobserve(el) } }) }, { // 交叉视图的 100ps,才开始派发事件 rootMargin: '0px 0px -100px 0px', } ) app.directive('lazy', vLazy(observer)) }, } export default lazyPlugin
//全局注册 import { createApp } from 'vue' const vLazy = (observer) => { return { beforeMount(el, binding) { el.classList.add('op-lazyload') const { value } = binding if (value) { el.dataset.origin = value observer.observe(el) } }, beforeUpdate(el) { observer.unobserve(el) }, updated(el, binding) { el.dataset.origin = binding.value observer.observe(el) }, unmounted(el) { observer.unobserve(el) }, } } const observer = new IntersectionObserver( (entries) => { entries.forEach((item) => { if (item.isIntersecting) { const el = item.target el.src = el.dataset.origin el.classList.remove('op-lazyload') observer.unobserve(el) } }) }, { rootMargin: '0px 0px -100px 0px', } ) const app = createApp({}) app.directive('lazy', vLazy(observer)) // 其他的组件注册、挂载等操作 app.component('your-component', {}) app.mount('#app')
- Vue3指令Direction
- Vue3插件Plugin机制
- 一种能为vue添加全局功能的工具代码
- 一个拥有install()方法的对象,也可以是一个安装函数
- 插件发挥作用的常用场景
- app.component()和app.directive()
- app.provide()
- app.config.globalProperties
- 以上三种都包含

6-1 我的页面
课程知识点
无,只是搭建了页面的样式。
额外知识点
- css布局:如果想指定一行有4个元素,可以使用grid布局。
display:grid;
grid-template-column:repeat(4,1fr);
- css全屏样式:让div充满全屏。以前我都是指定height,但是因为手机有导航栏,且高度都不一样,所以很难准确的计算出height。如果盲目的使用height:100vh,还会导致出现不必要的纵向滚动条。现在可以使用定位来解决这个问题。
background:#ccc;
position:absolute;
top:0;
right:0;
bottom:0;
left:0;
6-2~6-5 自定义hooks-useAuth实现登录页面逻辑
课程知识点
- 介绍整体登录的逻辑
- 登录页实现
- 输入账号密码信息
- 使用vant-ui的vanForm组件来实现
- useAuth的实现
- 调用useAuth返回的方法请求登录接口
- server后端项目里实现token的生成和校验逻辑
6-6~6-9 自定义hooks-useUserStore实现用户状态管理
课程知识点
- Pinia以及状态管理的介绍
- 什么是Pinia
- vue官方提供的一个拥有组合式API的Vue状态管理库
- 允许跨组件或页面共享状态
- 更强的团队协作规定
- 与Vue DevTools集成,包括时间轴、组件内部审查和时间旅行调试
- 模块热更新
- 服务端渲染支持
- 更简洁直接的API
- 组合式风格的API
- 更完善的类型推导
- 什么是状态管理
-
每个vue组件实例都已经在管理自己的响应式状态
- 状态:驱动整个应用的数据源
- 视图:对状态的一种声明式映射
- 交互:状态根据用户在视图中的输入而作出相应变更的可能方式
-
多个组件共享一个共同的状态
- 多个视图可能都依赖于同一份状态
- 来自不同的视图的交互可能需要更改同一份状态
-
如何实现共享同一个状态
- 将共享属性提取到公共组件里面,再通过props透传下来。但是会导致透传问题,不够理想。
- 抽取共享状态全局单例
-
- 什么是Pinia
- 如何实现useUserStore
- 如何实现一个store
defineStore(id,optionData/setUpFunc)
* state是store的数据(data)=> ref * getters是store的计算属性(computed) => computed * actions是方法(methods)=> function
- store的缺点
- 因为store是存在浏览器内存里的,刷新页面时,重新加载页面并重新初始化应用程序,导致浏览器的内存中存储的页面数据、js对象、样式都会被清空,所以当页面刷新时,所有的状态都会丢失。
- 所以需要对store进行持久化存储,避免刷新页面状态丢失问题。
额外知识点
- 当箭头函数只有一条返回语句时,可以省略return关键字来声明返回值。
const getDefaultUserInfo: () => IUserInfo = () => ({
id: '',
avatar: '<https://b.yzcdn.cn/vant/icon-demo-1126.png>',
nickname: '请登录',
})
- 将函数的入参直接解构,方便使用。
const setInfo = ({ token, userInfo }: IUserState) => {
state.value.userInfo = userInfo
state.value.token = token
}
6-10 自定义hooks-useLocalStorage保存用户状态信息(store的持久化)
课程知识点
- 为什么需要做store的持久化
- 因为store是存储在浏览器内存中的,页面刷新时,内存清空,导致store丢失。
- localStorag的介绍
- 是浏览器的API,将数据保存到浏览器本地
- 同源限制
- useLocalStorage的实现
额外知识点
-
??空值合并运算符
- ?? 是 JavaScript 中的空值合并运算符,也被称为 nullish 合并运算符。它是 ECMAScript 2020 (ES11) 中引入的一种新的语法。当左侧的操作数为 null 或 undefined 时,它返回右侧的操作数,否则返回左侧的操作数。
-
使用json.parse时,一定要对参数进行判断,因为对undefined进行json.parse时会报错。
-
对可能出现的错误,进行try catch包裹
- 解构时对变量进行重命名
const {
value: $userInfo,
setValue: $setUserInfoValue,
removeItem: $removeUserInfoItem,
} = useLocalStorage('userInfo', getDefaultUserInfo())
- 为什么不直接读取localStorage的值,而是先读取store呢?
- 因为读取localStorage是非常耗时的操作,所以先读store。而且当store中没有数据时,如果localStorage中有,也要重新设置store的值。
7-1 业务组件-ShopView商家详情页
额外知识点
- interface也可以像class一样使用extends关键字
7-2~7-3 业务组件 - 商家头部组件开发
7-4~7-5 商品列表组件开发
额外知识点
- 动态组件的使用,如果每个tab签是展示不一样的内容。可以使用
<component :is="">
来动态渲染组件。 - 注意定义在发起网络请求时,定义返回的数组类型以及期待返回的数据类型。
- 获取路由参数时,通过解构的方式。
{id} = useRoute().params
defineProps<IProps>()
。这句代码中<IProps>
是用来指定函数的参数类型的。.shop-list[@click="handleClick"]
,快捷代码。
7-6~7-10 购物车控件组件开发
课程知识点
额外知识点
- 如果发现zIndex比较小的覆盖在zIndex较大的上面,说明是层级上下文的原因。
- 当zIndex!=0时,会有一个单独的层级上下文,会脱离原本的层级上下文。
7-11 自定义hooks-useTransition实现加入购物车效果
课程知识点
- 实现了抛物线效果
额外知识点
beforeEnter
beforeEnter
是 Vue.js 中的过渡钩子函数,它是在元素进行过渡动画之前被调用的。具体来说,beforeEnter
钩子函数在元素被插入到 DOM 之前立即触发,在此时可以通过一些操作来准备元素的过渡。- 这个钩子函数通常用于设置初始状态,例如将元素的初始位置或样式进行一些调整,以便在过渡动画开始时有一个良好的起点。比如,你可以在
beforeEnter
钩子函数中设置一些初始的 CSS 属性,然后在enter
状态执行过渡动画,最终在afterEnter
钩子函数中实现过渡动画的结束状态。 - 总体来说,使用
beforeEnter
钩子函数可以让你在元素过渡动画开始之前进行一些准备工作,以确保过渡动画的流畅性和准确性。
7-12 自定义hooks-useEventBus 使用事件机制实现跨组件通信
// use/useEventBus.ts
import { EventEmitter } from '@/utils/event'
let eventBus: EventEmitter
export function useEventBus() {
// 单例模式
if (!eventBus) {
eventBus = new EventEmitter()
}
return eventBus
}
// utils/event.ts
type Fn = (...args: any[]) => void
interface Events {
[name: string]: Fn[]
}
export class EventEmitter {
events: Events
constructor() {
this.events = {}
}
on(type: string, fn: Fn) {
if (!this.events[type]) {
this.events[type] = []
}
this.events[type].push(fn)
}
off(type?: string, fn?: Fn) {
if (!type && !fn) {
this.events = {}
return this
}
if (type) {
if (!fn) {
this.events[type] = []
return this
}
const events = this.events[type]
if (!events) {
return this
}
let count = events.length
while (count--) {
if (events[count] === fn) {
events.splice(count, 1)
}
}
return this
}
}
emit(type: string, ...args: any[]) {
const events = this.events[type]
if (!events) {
return
}
let ret
for (let i = 0; i < events.length; i++) {
const fn = events[i]
if (fn) {
ret = fn.apply(this, args) as unknown
if (ret === true) {
return ret
}
}
}
}
destroy() {
this.events = {}
}
}