创建项目
创建项目
- 创建配置
vue create presspro
- 选择vue的版本
- 选择是否使用历史模式( n)
- 选择css的编译语法
- 选择语法检查的规范,三选一即可,(prettier)
- 何时触发语法检查(save)
- 定义配置文件(独立配置) 回车项目创建完成
项目初始配置
eslintrc的配置
- 打开.eslintrc配置文件,参考eslint官网修改配置
在rules配置项中,对相关规则进行配置 - 配置语法格式化工具prettierrc配置,在项目根目录创建
.prettierrc
配置文件 - 修改prettier文件 将单双引号配置的值进行修改
- 使用eslint检查代码的时候,
npm run serve
的时候会报错,会发现一堆的语法问题,
可以直接通过命令npm lint
直接修复所有的问题 - 但是每次运行都需要去敲命令来修复,很不方便 ,所以 通过扩展工具,直接显示所有的语法错误
安装ESlint插件,将会直接在编写代码的时候将语法错误标红 - 通过配置文件
.eslintrc.js
中可以通过配置规则rules 配置项,将语法的问题进行修改,
例如:quotes: [0, 'double']
;将所有的双引号规范定义为通过,即当使用双引号的时候不会产生语法报错和警告
语法检查的最终解决方案
详细说明问题
1.通过格式化工具
vetur
插件,这个插件内置了prettier
;
2.当我们安装并使用这个插件vuter
进行代码格式化和修复的时候,又使用Enlist
语法检查,就会造成冲突,因为Enlist
和vuter
内置的prettier
是两套不一样的语法检查标准;
3.所以需要手动对prettier
进行配置,使其满足enlist
的规范,然而有部分prettier
配置并不能满足enlist的规范(比如enlist
中会让函数左边括号前必须要有一个空格,prettier
并不能配置这个效果),所以也需要定义一个enlist
的配置文件
注意点:
并不是指enlist
就无法修复代码的报错,enlist
也有修复的效果,但是由于eslint
无法解析.vue
文件所以无法执行修复,vuter
主要修复的是vue
格式的文件,而原生js和其他文件还是通过eslint
进行修复的
通过eslint修复原生js文件的格式报错
在vscode的配置文件的根节点中配置如下代码
// 配置 vscode 保存时 使用 eslint扩展插件 来 检查和修复 js文件的语法规范错误 "editor.codeActionsOnSave": { "source.fixAll.eslint": true },
总结:
即整个配置vue项目eslin的过程,需要安装vuter插件,并且修改项目的
.eslintrc.js
再添加一个.prettierrc.js
文件,通过互相配置,达到语法检查,修复vue语法问题的效果
创建项目忽略文件
通过gitignore.io
网站,可以快速生成忽略文件;脚手架创建的项目会自动生成忽略文件
git仓库管理
- 仓库两种关联方式
第一种 方式 使用HTTPS进行关联
在初次提交的时候,将会提示输入远端git仓库的账号和密码,在第二次提交的时候就不会再提示输入,而保存用户账号密码的地方,在window系统的设置的凭据管理器中,无论你初次输入的账号密码是否正确,都会保存在这里,如果想重新输入正确的信息,可以删除凭据管理器中的数据,再次提交的时候就会提示输入密码,也可以直接修改凭据管理器中错误的密码
第二种方式 通过公钥和私钥进行关联
首先需要获取主机的公钥,教程在gitee码云账号-设置-安全设置-SSH公钥的页面中有生成公钥的教程
拿到电脑的公钥以后,将其配置在码云的SSH公钥选项中
即可在推送分支的时候,会携带上私钥的凭证,而在远端仓库,通过公钥对推送过来的分支进行验证
-
git仓库简介
git仓库的本质是在项目本地根目录生成一个仓库的文件夹,这个仓库独立于整个项目之外,在创建这个本地仓库以后,通过和远端仓库关联的方式,实现代码保护和云端同步 -
创建git仓库
在项目根目录通过git init
命令创建一个本地仓库.git
文件夹,
然而依托脚手架创建的项目会自动生成一个本地的仓库,默认是隐藏的
-
仓库解析
仓库分为工作区和暂存区,当修改代码以后,在项目根目录使用命令git status -s
查看本地的文件变化,会发现更改过的文件都已经标红
当通过git add .
命令将更改提交到暂存区以后
再次使用git status -s
命令会发现文件的状态标绿
使用git commit -m "完成了登录模块的开发"
命令,将会把暂存区的代码提交到本地仓库的工作区 -
整个过程流程
在远端未配置好仓库公钥的情况下使用https进行关联
a- 进入仓库的详情页面,拿到仓库的https的链接
b- 在项目根目录命令行,使用命令git remote
命令查看当前仓库使用连接远端仓库的链接的所有别名
使用git remote add origin 远端仓库地址
就可以添加一个别名,并且让别名origin和远端仓库进行关联,在推送的时候直接使用这个别名即可
使用gitt remote get-url origin
命令可以查看origin这个别名所对应的远端地址
c- 同时,别名所定义的远端地址也是可以进行修改的,例如在保证不修改和添加别名的情况下,将origin对应的链接修改为公钥链接
首先拿到公钥形式的仓库地址
d- 使用git remote set-url origin 公钥形式的远端地址
,这样就将origin对应的地址修改成功
使用git remote get-url origin
查看别名origin所对应的地址会发现已经和之前的不同
e- 为了后续需要,先地址改回https
目前仓库已经关联完成, 现在开始提交代码流程
查看当前状态git status -s
f- 将所有修改加入到暂存区git add .
再次git status
查看文件状态
g - 将暂存区的文件提交到本地仓库工作区git commit -m '提示信息'
再次使用git status
会发现已经没有任何更改注意命令后的-s代表简要信息
查看当前的所在分支
git branch
h - 将代码该分支从本地仓库推送到远端仓库git push -u origin main
此时就会弹出输入远端码云仓库的账号和密码
输入账号和密码,验证正确后就会开始推送,推送完成后远端的仓库就会出现推送过去的内容,
此时查看window的凭据管理器就会发现记录了刚才输入的账号信息
在远端配置好仓库公钥的情况下使用公钥进行关联
整个过程和https方式的过程一致,只不过在定义origin别名关联的地址的时候使用公钥的配置形式,这就就可以省略输入登录的远端账号密码这一步
git 相关知识补充
main这个分支是主分支,一般在开发中往往需要创建多个子分支
创建子分支home
git checkout -b home
开发home分支完成后提交分支,先检查当前所处的分支
git branch
检查文件状态
git status
将更改过的文件加入暂存区(这里是添加所有文件,如果要个别就将.
号换为改文件名即可)
git add .
提交所有暂存区的文件到本地仓库
git commit -m "完成了 home 首页的开发"
然后将home这个分支推送到远程仓库码云
git push -u origin home
此时远程仓库就有新的分支home,然后将分支合并到主分支main,先切换到主分支
git checkout main
合并home分支
git merge home
合并以后分支以后,主分支已经发生了变化,所以重新提交主分支
git push -u origin main
最好删掉本地的子分支home即可(非必须)
git branch -d home
vue细节补充
- vue.use()
这个方法可以用于注册过滤器和中间件
还调用了传入对象内部的install方法 - import缓存机制
import后面跟的是一个常量,是无法更改的
当第一次导入vue之后,vue会存到缓存中,在第二次导入vue之前对vue进行了修改,例如添加了一个新的方法是say(),在第二次再次使用import导入vue的时候,就会直接导入缓存中的vue,此时第二次导入的vue上就已经有say方法 - 项目初始化建立相关文件
在src文件夹中创建api文件夹、style文件夹、utils文件夹,可以放置接口文件、less样式文件、公共的工具文件(request等) - style中的公共样式less文件
通过import关键字
在main.js中直接引入import 'src/style/less.css'
less文件将会被编译成js代码,内部利用dom语法,将样式添加到style标签中 - 字体图标库
阿里的字体图标库iconfont字体图标库
https://www.iconfont.cn/
资源管理–我的项目 定义自己的icon
可以在公共样式index.css中使用@import './iconfont.less';
这种方式将字体图标的文件导入到公共样式中去 - 安装组件库
Vue 3 项目,安装最新版 Vant
npm i vant
Vue 2 项目,安装 Vant 2
npm i vant@latest-v2
详细过程参考说明文档https://gitee.com/vant-contrib/vant?_from=gitee_search
vant2的官方说明文档https://youzan.github.io/vant/v2/#/zh-CN/quickstart
通过一个插件,将vant组件按需引入babel-plugin-import
(官网文档有说明)
#安装插件
npm i babel-plugin-import -D
// 在.babelrc 中添加配置
// 注意:webpack 1 无需设置 libraryDirectory
{
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}]
]
}
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
// 接着你可以在代码中直接引入 Vant 组件
// 插件会自动将代码转化为方式二中的按需引入形式
import { Button } from 'vant';
还有手动按需引入和全局引入两种方式,具体可参考官方文档
- rem适配
一共需要两步
(安装插件postcss-pxtorem
实现单位转换和amfe-flexible
实现基准值自动适配 )
第一步 安装插件
在创建项目的时候,项目会内置一个工具postcss,这个工具会通过javascript工具和部分插件,实现转换css代码
要实现rem适配
就可以通过这个工具的内置插件postcss-pxtorem
通过命令npm install postcss-pxtorem --save-dev
安装
在安装的时候如果报错,如果是版本问题就需要安装匹配postcss工具的版本npm install postcss-pxtorem@6 --save-dev
这个插件的作用:
会通过配置文件的内容,自动将页面的px转换为rem单位,实现适配效果
在项目的根目录创建.postcssrc.js
文件,内部配置这个插件的相关属性
module.exports = {
plugins: {
'postcss-pxtorem': {
// rootValue: 37.5, // 将 多少 像素 转成 1rem
rootValue({file}){
// 如果是 vant 组件库的样式,则使用 37.5 的基准值,否则使用 75的基准值
return file.indexOf('vant') > -1 ? 37.5 : 75
},
// propList 配置 css中需要将px转成rem的 样式属性名称
// propList: ['width','height','font-size'],
propList: ['*']
// 不被编译的 选择器,凡是符合的选择器中的px 都不会被转成 rem
selectorBlackList:['markdown-body'],
// 不被编译的 css文件
exclude:'github-markdown'
}
}
}
也可以在vue.config.js中配置这个插件
module.exports = {
//...其他配置
css: {
loaderOptions: {
postcss: {
plugins: [
require('postcss-pxtorem')({
rootValue: 37.5,
propList: ['*']
})
]
}
}
},
第二步实现基准值的自动设置
安装库
npm install amfe-flexible --save
这是个运行依赖
这个库的作用是动态根据窗口计算rem的基准值
安装完成这个库以后通过在main.js中引入这个库
直接使用import 'amfe-flexible'
导入这个库,就会自动设置html根标签的font-size属性值,并且会根据设备宽度自动设置
此时两个工具都已经配置,当设备的宽高发生改变的时候,通过amfe-flexible会自动改变根节点font-size属性的值,而因为配置了postcss-pxtorem这个插件,所以页面中需要的地方,都将px修改为了rem,此时这些使用rem单位的元素,就通过根节点font-size的变化而变化,但由于表面看起来就是1rem,数值并不会发生变化,因为rem是一种比例,看似1rem并没有改变,但是其真是的px单位,已经发生改变了
最后的注意点:
当使用某一个ui组件库的时候,组件库内置的样式的单位,极可能不是rem,此时就需要进行适配,在进行开发的时候,由于设计稿和ui组件库的基准值不同,就需要采用不同的适配形式,动态的使用某一个基准值
根据文件路径判断是否额外定义ui组件的基准值,转换工具就根据判断结果进行针对的转换
总的来说可以理解为,postcss-pxtorem负责在编译的时候(可以理解为开发的时候)将单位都转成rem,而amfe-flexible负责在运行的时候,动态改变rem的基准值,
axios操作
- 在创建的utils文件夹内创建用于发送请求的js代码文件
request.js
在内部导出一个request - 在main.js中导入这个文件,导入这个request文件后,将axios绑定到vue的原型对象上,也就是
vue.prototype
通过原型对象的特性,可以在vue的实例和子类中使用这个axios,这是通过原型链的原理
代码片段
- 是一个方便开发的方法,在编辑工具VS code中,点击文件-首选项-用户片段
- 新建一个全局的代码片段文件
- 在这个文件内编辑代码片段,
- 代码片段的的详细代码
{
"Print to console": {
// "scope": "javascript,typescript",
"prefix": "v2",
"body": [
"<template>",
" <div class=\"\">",
" </div>",
"</template>",
"<script>",
"export default {",
" name: \"\"",
" data() {",
" return {",
" }",
" },",
" methods: {",
" }",
"}",
"</script>",
"<style lang=\"less\" scoped>",
"</style>"
],
"description": "Log output to console"
}
}
路由组件
-
第一种是直接加载所有组件到内存中
- 如下代码
import Login from '../views/login'
- 然后直接将其注册到路由表中
- 如下代码
-
第二种 是利用函数的方式,实现按需加载
// 路由表(配置 hash值 和 组件的 映射关系 和 附带参数) const routes = [ { path: '/login', // localhost:80/#/login component: function(){ return import('@/views/login/index.vue') } } ]; //简写为 // 路由表(配置 hash值 和 组件的 映射关系 和 附带参数) const routes = [ { path: '/login', // localhost:80/#/login component: () => import('@/views/login/index.vue') } ];```
-
路由站位符
<router-view>
表单事件
- buttom标签(自带type属性就是submit)和input标签(type的值定义为submit)都会触发表单内所有绑定了submit事件的函数
登录接口(组件)
- 万能验证码 246810
- 在点击按钮触发submit事件的时候调用请求方法
- vant的表单组件自带的ui组件自带了 rules属性进行表单验证
- 简单描述一下登录组件的过程
首先准备接口文档,在request内 导入axios,对基地址进行封装,再按需导出
然后再接口模块中的用户接口模块user.js中,按需导入需要的基地址的请求对象,在user中对导入的请求对象进行二次封装,在vue文件中按需导入user所需要的请求方法
使用vant组件库,对登录的页面进行布局,利用组件库中的表单组件的某些属性,对用户输入的内容进行校验,会在失去焦点和点击提交这两个时机触发
对表单输入框的内容进行双向数据绑定
通过按钮特性,触发表单内的所有submit上绑定的事件,来实现发送请求
触发表单的事件,将表单的内容通过请求的参数传递给服务端接口,服务端返回token字符串和refresh token ,将这两个token保存到vuex的仓库中
在vuex的mutations中配置一个保存token到state中的方法,并且再保存到本地
然后再登录组件中引入辅助函数,调用这个vuex的方法,调用完毕这个方法以后,再调用路由方法将页面跳转开登录组件转到主页组件
以上操作完成以后,在仓库中的state中就会保存用户的token字符串
Git拉去远端项目
- npm i 初始化拉取到的项目
- 如果报错参考文档解决
四种检测数据类型的方法
- 第一种 typeof
typeof 在typeof的后面直接跟数据,会直接检查检测出数据的类型,对于基本类型的数据可以直接返回结果,而对于引用类型的数据则均返回object
typeof 适合判断除了null以外的任何基础类型,判断引用类型除了函数function都会返回object类型
返回值是具体你的类型名称
- 第二种 instanceof
这个方式主要是判断变量的原型链上,是否有构造函数的原型对象(prototype)
例如一个空数组const arr=[]; 通过instanceof判断的时候,arr instanceof Array 返回true,因为 arr.proto===Array.prototype
但是arr instanceof Object; 也会返回true,这是因为构造函数Array的原型也有原型对象,也就是说Array.prototype.proto===object.prototype
返回值是一个布尔值
- 第三种 constructor
每一个原型对象上都有一个属性,就是constructor,举个例子,比如Array.prototype这是一个原型对象,这个对象上就有一个constructor属性,这个属性指向构造函数本身,
即 Array.prototype.constructor===Array
就可以根据这个特点去判断数据类型,例如有一个数组arr,利用arr.__proto__
拿到构造它的函数的原型对象,再通过arr.__proto__.constructor
就可以得到Array
返回值是具体的类型名称
- 第四种 tostring
Object.prototype.toString.call([5]) // [object Array]
call方法是将toString函数的this指向这个[5],并立刻调用这个函数,并且避免了传入的参上也有toString这种情况而导致没有正确使用这个object原型对象上的toString方法
Object.prototype.toString.call()这种方式可以判断所有的数据类型,很精准
返回值是一个表示该对象的字符串 :[object Array]
- 总结
typeof 适合判断基本类型和函数类型
instanceof 适合判断自定义的对象
constructor 适合判断所有类型,但是null和undefined不行,因为没有原型属性
Object.prototype.toString.call()能判断精准所有类型,但是对于自定义的对象 比如class、new 出来的自定对象就并不精准,这种情况比较稀少,届时可以额外封装,所以多数情况都能用这个方法进行判断
存放服务器返回的数据(token令牌)
通过window.localStorage自带的方法,对将数据存到 localstorage中
倒计时的效果
vant组件库的countDown组件,应用到组件中,并进行调整
- 由于这个ui组件右方自带的插槽只能放置一个标签,所以在展示“点击发送验证码”的按钮以后,还需要倒计时就没有了地方放置,就需要进行调整,如何调整?
- 调整的时候因为发送验证码和倒计时两个组件需要互斥,当用户点击发送验证码以后,需要隐藏这个点击发送验证码的组件,然后展示倒计时的组件,并开始给倒计时的时间变量进行赋值,开始倒计时
- 即给两个组件绑定v-if和v-else即可解决,每次只会传递一个标签给一个插槽
- 倒计时结束的时候触发这个倒计时器组件的回调函数,把v-if中的变量重新变为false
发送短信验证码按钮的弊端
- 提出问题 由于ui组件自带的发送验证码的按钮的默认会触发表单的提交事件,这不是我们想要的
- 解决1,直接使用click点击事件的修饰符
.prevent
,让点击不会触发默认事件 - 解决2(推荐),直接前往组件库的官网去查这个ui标签的说明,找到该组件的对于事件触发的属性,这里是native-type属性,就可以将它变为普通按钮
登录按钮和发送验证码按钮的优化
- 登录按钮点击的时候会自动触发表单事件,然后去校验表单内的信息是否符合规范,不符合就不会触发请求的接口函数
- 而发送验证码的按钮由于在之前已经改为了一个常规按钮,不会触发表单事件了,但是会触发自身绑定的事件,这就涉及另一个问题,
- 发送验证码按钮也应该检测手机号是否符合规范,但是现在并没有进行验证
- 所以需要调用表单组件自带的校验函数
在发送短信验证码的事件处理函数中拿到 表单的dom对象,通过在标签内
ref="loginFrom"
然后在事件处理函数中通过this.$refs.loginFrom
就拿到了整个from的dom,这个时候就可参照组件文档,通过validata('需要校验的表单元素的name属性值')
,这样就会自动进行校验
- 通过async 和await 优化这个校验,并使用
try{}catch(){}
包裹,在catch中return出去,成功则继续执行下面的代码
GET请求时的动态参数和查询参数
- 动态参数要参考接口的定义规则
- 查询参数则是通过
路径?id=1&age=18
的方式
路由的方式定义tabber
- 目前有两个一级路由组件 login和layout
- layout用于登录成功后展示的页面效果
- layout就会分为两个部分定定义
一部分是用于展示信息主体的区域
一部分是用于底部的tabber,有四个按钮
- layout组件内,点击底部的按钮实现将不同的路由组件展示到主体区域
- 怎么做?
- tabber底部按钮区先做出来,通过ui组件库官网的组件库直接拉过来
- 当底部有了四个按钮以后,通过点击按钮触发二级组件
- 所以就需要在定义四个二级路由组件,并配置到路由器中
- 将tabber的组件开启路由模式,点击按钮的时候,就会加载二级路由组件放到layout的主体区域,
- 点击不同的按钮,就加载对应的二级路由组件到一级路由组件layout的主体区域
二级路由组件和一级路由组件的路径问题
- 二级路由组件的path值如果没有斜线
/
,那么就会相对于一级路由组件的路径/#/layout/home
这种情况就是二级路由组件path:"home"
- 当给子路由组件的path使用的是添加斜杠
path:"/home"
,此时访问/#/home
就可以直接省去layout,直接跳转到home组件的显示,同时由于这个子组件依赖layout组件,虽然我们在路径中没有写layout,但是同样会展示layout组件的内容 - 这里还有一个技巧,在定义一级组件的path的时候,直接定义为
/
,在二级组件的其中一个组件的path定义为""
空字符串这个二级组件就是默认组件,此时如果直接访问这个项目的一级组件内容,就会直接展示默认的二级组件,实现默认展示
store注入
- 当导入仓库的时候,会在new vue 的配置项中进行注入,这个注入其实就是在vue的$store属性上,放了一个值,这个值就是我们导入的store
- 这样vue上就有一个属性时$store ,通过这个属性,就可访问到通过vuex所创建的index.js,中定义的仓库store
- 当然还有辅助方法,直接映射vuex仓库中的内容,这里不做叙述
局部样式属性scoped
- 为了避免多个组件之间的样式重名等问题,会造成样式冲突,使用这个局部样式就可以解决这个问题,
- scoped这个属性,当前所有组件标签的上都会有一个属性,这个属性是一个自定义的属性,这个属性组成中有一个哈希值,这个哈希值会根据当前组件的文件名字生成的,例如是
data-86464684dg44gd
- 并且在style标签中的样式,例如
.nav {}
中的nav后面也要加入这个自定义属性.nav [data-86464684dg44gd] {}
- 总的来说就是为所有的标签都打上一个标识,标识用于在编译后区分其他组件的样式
退出登录需要实现的东西
- 删除仓库里的token
- 清空本地localstorage里的token
- 并给一个提示,退出提示的“确人是否选择退出” vant组件库中就有dialog方法可以实现这个效果,使用
this.$dialog
登录成功跳转
- 在登录的事件处理函数的时候,在登录成功以后跳转会主页的组件,在事件处理函数的内部,通过
this.$router.back();
返回到原来的页面 - 同时在登录页面的左上角也会有一个尖括号,点击这个会触发取消登录,就返回到之前的页面,同样应该调用这个方法
back跳转的问题
- 没有前一页,以至于跳转偏差
- 首先对历史记录的数量进行判断,并封装为一个方法
// 定义一个方法,对历史记录是否为小于等于1 进行判断
Vue.prototype.$setgoindex = function () {
if (window.history.length <= 1) {
if (location.href.indexOf('?') === -1) {
window.location.href = location.href + '?goindex=true'
} else if (location.href.indexOf('?') !== -1 && location.href.indexOf('goindex') === -1) {
window.location.href = location.href + '&goindex=true'
}
}
}
- 然后在通过
keep-alive
组件所产生的针对于路由组件的生命周期钩子activated
中进行调用
//在组件被激活的时候进行判断
activated: function () {
this.$setgoindex()
}
- 最后再根据情况的不同,点击事件的回调中使用的方式就不同
onLeft(){
if (this.$route.query.goindex === 'true') {
this.$router.push('/')
} else {
this.$router.back(-1)
}
},
相当于,在页面最开始加载的时候,对这个页面进行判断,历史记录有没有超过1条
如果小于等于1条记录,就在路径的最后添加一个query参数,
相当于一个标识在添加这个query的时候也要判断是否路径上已经有了query参数,如果有了就用&直接加在后面;如果没有就用?号后面添加
添加好了以后,在调用按钮回调函数的内部,判断这个当前组件的路由上的query是否有标识的这个参数,如果有,就直接push到 初始页,如果没有就调用back(-1)
end
token的携带
- 请求的时候token是放在请求的authorzation:beare token
- 即在authorzation字段后面跟一个beare,后面加一个空格,再跟token
- 通过自定义请求头进行定义:
export const getUserInfo=function (token){
return axios({
url:'/v1_0/user',
method:'GET',
headers:{
"Authorization":"Bearer "+token
}
})
}
created钩子函数【入口函数】
- 一般就在created这个钩子函数中发送异步请求
updated 数据改变,更新视图之后才会调用
- 例如有个数据name,并没有在模板中使用,那么这个数据的变化并不会触发updated这个钩子的调用
携带token一般封装在请求拦截器中
- 由于登录后的用户操作需要经常发送请求,请求需要携带token,每次发请求都需要在heade属性写字段,很麻烦
- 所以直接将请求拦截器中封装拦截器
- 在封装请求拦截器的js代码中导入存放token的vuex仓库
- 然后再use方法中将配置对象的header对象用
.
的方式添加一个属性,Authorization。 - 代码如下
import axios from 'axios'
impor store from '@/store/index.js'
axios1.inerceptors.request.use(function(config){
//判断是否存在token,有了token才携带
if(store.state.user && store.state.user.token){
config.header.Authorization = 'bearer '+store.state.token
}
//返回配置对象
return config
})
- 请求拦截器中的use中可以接受两个函数作为形参,第一个函数的形参是配置对象,第二个是请求的错误对象
- 下方是官方代码示例
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
组件加载后二次加载
- 组件首次加载以后,切开再次进入该组件就不会发送请求了,它会缓存到内存中
上拉触底
- 通过上拉触底事件触发load事件, 页面加载的时候发送请求将最新的时间戳传过去
- 下次触发这个事件的时候,就是用上一次 请求过去的时间戳就使用上一次放回的事件戳
下拉刷新
- 请求的时候,将最新的时间传过去即可
- 获取数据的时候,需要将数据放在前方,使用
unshift()
方法 - 但是会出现另一个问题:
- 请求的数据需要10条,但是服务器只有最后的5条,那么返回的5条数据就会重复
- 此时进行渲染,由于会有重复的问题,使用渲染的for循环使用的是服务器返回数据的id作为key
- 那么就会发生渲染错误,因为有5个数据的id重复了
- 解决办法:
//拿到返回的数据以后,对返回的数据进行比对
//返回的新数据
let newData = res.data
//定义一个新数组用来装筛选后的数据
let newArr=[]
//遍历返回的数据,检查和本地已经存在的数据是否有重复
newData.forEach((e)=>{
//定义一个布尔变量
let flag = this.list.some((x)=>x.id===e.id)
//如果有不一样的数据,flag就会为false,
if(!flag){
//就添加到新的数组去
newArr.push(e)
}
})
//最后再将数据添加到本地去
list.push(...newArr)
props接收参数
- 使用数组接收的时候,其中每个元素一定要写成字符串
props:['newArr']
- 使用对象的形式
// 父组件 为 子组件传参的 属性
keyword: {
type: String, // 类型约束
required: true, // 属性必须传值
// 校验 父组件传递的值 的规则,如果 不合规,则返回false;否则,返回true
validator: function (value) {
return value.length > 0;
},
},
},
浏览器盗链问题
- 当浏览器访问资源服务器的时候,需要向另外一个服务器获取资源,
- 此时另外的服务器会判断出该请求并不属于自己服务器的资源
- 此时就可以将所有请求头部的Referer字段都取消掉,服务器就无法直观看出请求方是谁
- 如何取消?
- 直接在index.html中加一个meta标签
<meta name="referrer" content="no-referrer">
处理时间格式的库
两个库
- moment.js
- Day.js
- vue中可以直接运行安装
npm install dayjs --save
- Dayjs本身并不能完成相对时间,所以需要在官网,找到Dayjs的插件Relative,当安装好Dayjs以后,需要按顺序导入dayjs和Relative
- 引用语言包
require('dayjs/locale/zh-cn')
- 加载中文语言包
dayjs.locale('de') // 全局使用
- 导入Relative的时候需要直接写
dayjs/.../../relative
,这样写就会自动到module中去找dayjs - 然后跟着官方文档注册这个插件
dayjs.extend(relativeTime)
即可,最后参照文档进行使用 - 这里注意需要加载中文语言包才可以显示中文,因为dayjs默认是English
通过过滤器对文字和时间进行格式处理
- 全局注册过滤器 直接在src下新建filters文件夹,下面新建index.js文件
- 在js文件中导入vue和dayjs
- 以及加载语言包,注册中文语言包
- 导入相对时间的插件函数relative
- 注册这个插件relative
- 然后开始全局这次过滤器
Vue.filter('relativeTime',(v)=>{
return dayjs().from(dayjs(v))
})
-
之后在main.js中导入这个过滤器
import '@/filter/index.js'
-
最后直接在.vue文件中的模板使用管道符即可
{{ time | relativeTime }}
-
过滤器函数中的this定义都为undefined, 因为作用已经定格在为视图中的变量做格式化,不需要访问this,类似于包装
弹出层
- 通过点击按钮改变变量,使vant组件库中的弹出层(Popup 弹出层)中的v-model属性的值为true,就会自动弹出这个弹出层,需要一个按钮绑定触发
- 将这个组件写为双标签即可在弹出层中编辑内容
<van-popup v-model="show">内容</van-popup>
- 可以直接在标签中放一个自定义的组件
按需导出
- 按需导出
import const getRequest.....
- 按需导入的时候使用解构语法
import { getRequest } from "../.../.."
标签内部的class属性
- 可以书写多次class属性,
- 也可以动态绑定class属性,即
:calss="{ redfont:i===index }"
-
redfont就是属性名,而后面跟的表达式会返回一个布尔值,为true则该类名对应的样式会生效
未登录状态下保存数据
- 通过 之前封装的保存到本地的方法
- 在.vue中 通过util文件中按需导入这个方法
{ setItem }
- 将键值传入这个方法,对本地进行保存
- 然而 这个方法传入的键需要额外开一个文件进行存储,方便后续查找
- 即在src下新建一个文件夹config,新建文件index.js
- 在js文件中默认导出一个对象,而这个对象内就放置所有键的键名
- 代码如下:
export default {
// a.本地保存 用户频道数组 的 key
USER_CHNA: '1008mychan',
// b.本地保存 搜索历史的 key
SEARCH_HISTORY: '1008search'
}
- 需要的时候,就导入这个对象,通过
. USER_CHNA
这种方式,将键名传入保存本地的方法etItem()
中
对请求接口的代码优化
- 原代码
async created(){
//1.请求接口,获取所有的列表
const { data: resbody } = await getAllChannels();
//2.把拿到的所有列表装入到全部列表 数组中 allChanne
const allChannel = resbody.data.channels
//3.对列表进行遍历,除开用户列表(userArr)中有的数据,其他的数据装入推荐列表recommendArr
//遍历整个数组,
allChannel.forEach((a)=>{
//判断当前项中的id,在用户列表中是否存在,不存在就进行取反
if(!this.user.some(b=> b.id === a.id )){
//取反后则为真,执行下面的方法,将该项添加到推荐列表中去
this.recommendArr.push(a)
}
})
}
- 上方代码进行优化
async created(){
//1.请求接口,获取所有的列表
const { data: resbody } = await getAllChannels();
//2.把拿到的所有列表装入到全部列表 数组中 allChanne
const allChannel = resbody.data.channels
//3.对列表进行遍历,除开用户列表(userArr)中有的数据,其他的数据装入推荐列表recommendArr
//通过过滤方法,遍历整个数组,这个方法返回一个新数组,直接用推荐列表接收即可
this.recommendArr = allChannel.filter(
(a)=> !this.userArr.some(b=>{a.id===b.id})
)
}
- 通过过滤,将所有满足条件的元素返回到一个新数组,
点击按钮更改其他dom的样式(简略)
- 首先点击按钮的时候,改变自身的文字变化
- 在data中定义一个状态flag,默认是false
<botton @click="flag = !flag"> {{flag ? "编辑" : "完成"}} </button>
- 其次是点击按钮,改变其他dom的样式,例如添加一种样式属性
- 例如有个列表,通过循环出所有列表,循环
<div :class="setClass()"></div>
- 在方法中对动态绑定的函数进行配置:
setClass(){ if(flag){return "icon"}else { return ""} }
- 例如有个列表,通过循环出所有列表,循环
- 也可通过简单的方式
<div :class="flag ? "icon" : ""></div>
但是这种方式做不了太多的事 - 如果想整个列表中,部分的item不应用该样式,即应用该样式的只有一部分,此时使用函数的方式就可以实现:
例如我有一个白名单数组whiteArr,这个数组中的元素就不需要添加icon样式
此时就可将循环的时候的数组的元素 传入进这个setClass中
setClass(item){
//在处于编辑状态的同时,也不在白名单数组内 .some将不满足的值返回一个false,取反则为真
if(this.flag && !this.white.some(a=> a.id === item.id ) ){
//满足的时候,就返回类名
return "icon"
}eles{
return ""
}
}
end
子向父传 实现点击切换父组件的显示
- 在组件中定义方法
- 将方法通过
@change="changeindex"
传递给子组件 - 在子组件的点击事件中,通过
this.$emit("change", i )
触发父组件中的方法,并传值 - 在父组件中的方法中,就可以将传过来的值,切换到制定的页面显示
针对历史记录的存取和渲染
- 首先是渲染布局
- 针对搜索功能,一般为一个主组件和三个次组件
- 三个次组件用于搜索历史、搜索建议、搜索结果
- 对于三个次级组件,可以通过v-if的方式按需展示
<!-- 搜索结果 --> <SearchResult v-if="isShowResult" :searchKey="searchKey" /> <!-- 搜索建议 --> <SearchSuggestion @setKeyword="setKeyWord" :keyword="searchKey" v-else-if="searchKey" /> <!-- 搜索历史 --> <SearchHistory v-else @sonSearch="onSearch" :historyList="historyList" />
- 正常情况下需要直接展示搜索历史,通过变量的值,对展示进行控制,比如当点击搜索框以后,就会进行输入,而当用户输入第一个值以后,就会让搜索框绑定的值
v-model="searchKey"
,即searchKey会发生变化,一旦这个有值以后,在搜索建议的v-else-if
就会激活,进行展示,其他同理
- 对于搜索历史的处理
- 在本地定义了一个专用于放置搜索历史的空数组
- 当用于点击搜索按钮以后,触发点击事件的同时,即事件处理函数内部,会对
v-model="searchKey"
的值进行添加到数组中(unshift)- 这里也会进行判断,在之前的历史记录中如果有同名的记录,删除原来的那个关键字,把新的关键词添加到数组开头
通过 findindex方式,会查找满足条件的第一个元素的下标,没有找到就返回-1
// 1.搜索框回车触发事件-------------- onSearch(kw) { this.searchKey = kw; // a.检查 当前关键字 是否 在 数组中存在,如果存在,则删除之前的,重新添加新的 const delIndex = this.historyList.findIndex((x) => x === kw); // 如果 存在,则 先删除 数组中原来的关键字 if (delIndex > -1) { this.historyList.splice(delIndex, 1); } // b.将关键字 添加到数组中 this.historyList.unshift(kw); // 显示 搜索结果组件 this.isShowResult = true; },
- 这里也会进行判断,在之前的历史记录中如果有同名的记录,删除原来的那个关键字,把新的关键词添加到数组开头
- 调用方法,将历史记录的数组保存到本地
- 所以在页面每次加载的时候,就应当对历史记录的数组进行读取,
- 即
historyList: getItem(this.$conf.SEARCH_HISTORY) || [],
- 直接读取本地的记录,如果有就直接用,没有就用空数组
- 拿到记录以后会对结构渲染
- 渲染的时候直接通过上方data中的历史记录数组
historyList
进行遍历
- 渲染的时候直接通过上方data中的历史记录数组
- 由于
historyList
会经常发生变化,所以可以直接对这个data的属性进行监视- 通过watch监视这个
historyList
数组,每次变化的时候,都对本地进行存储,就不用每次都去调用保存本地的方法
- 通过watch监视这个
- 在渲染历史记录结构的时候
- 可以对历史记录进行编辑,通过一个值是布尔类型的变量对结构的展示进行控制
- 点击编辑按钮的时候,将会在每个搜索记录的尾部展示一个x号icon的按钮,点击icon触发点击事件
- 在点击事件的内部,接收一个下标,通过splice方法,对下标对应的历史记录进行删除
- 全部删除,直接使用
splice(0)
即直接删除从下标为0后的所有元素
- 搜索组件和搜索记录组件的优化
- 由于点击搜索进行输入回车触发的事件处理在父组件中,所以好的方式是将历史记录定义在父组件中去
historyList: getItem(this.$conf.SEARCH_HISTORY) || [],
直接在父组件的data函数中进行,统建watch也是- 通过父传子的方式,有props传给子组件
- 但是通过props接收的参数是不能覆盖的,即全部删除不能使用空数组直接赋值覆盖,所以采用
splice(0)
这种巧妙的方式(当然也可以直接在父组件中对原数组进行删除)
通过正则和字符串替换实现搜索建议高亮
- 描述原因:
- 在搜过的时候,输入框的值进行变化时,下方会实时显示不同的搜索建议 ,此时并未回车确认搜索
- 如何处理:
- 首先要对输入框v-model绑定的属性,进行数据监视
- 在监视的时候使用watch不可用函数
- 因为使用函数初次不会执行,即输入第一个值的时候不会触发watch定义的回调函数
- 就需要将watch写为对象的形式,通过handler属性对应回调函数,对象名对应监视的属性,immediate为true
- 在回调函数内部发起请求,并对返回的结果进行处理,通过正则的方式,在返回的结果内查找输入框的内容,替换为有样式的标签
- 拿到返回全部建议的内容,利用输入框的值,通过正则表达式在 请求返回的数组中去匹配,replace方法去替换
- map方法会遍历数组,并对数组内的元素进行修改,然后返回新的数组
async getSugList2(k) { // 1.立即 查询 建议关键字 列表 const { data: resBody } = await getSugList(k); // 2.正则表达式 -> 用来 (全局/忽略大小写)的 查找 关键字字符串 const reg = new RegExp(k, 'ig'); //-> /a/ ig // 3.新字符串:span标签 包裹 关键字,方便高亮显示 const newStr = `<span class="highlight">${k}</span>`; // 4.遍历 建议列表,将 里面的 字符串 都 根据正则替换 成新字符串 // 并装入 数组 返回 给 sugList if (resBody.data.options[0] === null) { return; } // 搜索建议的列表 this.sugList = resBody.data.options; // 搜索建议高亮后的列表 this.sugListHighLight = resBody.data.options.map((v) => v.replace(reg, newStr) ); },
- 注:由于直接在标签中定义的class类highlight不会生效,所以需要使用深度选择器
<div class="search-suggestion"> <van-cell v-for="(item, index) in sugList" :key="index" icon="search"> <div v-html="item" @click="clickKeyWord(index)"></div> </van-cell> </div>
- 直接在标签css前添加即可
/deep/ .highlight { color: red; font-weight: bolder; }
- 首先要对输入框v-model绑定的属性,进行数据监视
在本组件中访问自己的路由参数(路由传参)
- 通过路由信息对象传参
- 配置路由时在path路径的后方
./home/:id
- 此时可以直接在本组件中通过
this.$route.params.id
访问到路由 接收的数据
- 配置路由时在path路径的后方
- 路由传参本质是通过路由管理器
- 路由管理器会监听触发,内部通过wendow.onhashChange这个api监控哈希值的变化,从而路由管理器将这个值传递给由路由表中生成的路由信息对象上的params属性中,所以才可以在组件中通过
$route.params.id
获取到路由信息对象上的参数
- 路由管理器会监听触发,内部通过wendow.onhashChange这个api监控哈希值的变化,从而路由管理器将这个值传递给由路由表中生成的路由信息对象上的params属性中,所以才可以在组件中通过
- 动态传参 props
- 动态传参是在路由表的配置对象中开启这个属性
path: '/article/:aid', component: () => import('@/views/article/index.vue'), // 将路由动态参数映射到组件的 props 中,更推荐这种做法 props: true },
- 此时参数就会传入到组件配置对象,即和data平级的props配置中,在页面结构中就可以直接使用,在methods中也可以通过
this.aid
拿到
对服务器返回的文章内容进行额外的优化
- 通过包github-markdown-css对文章进行优化
- 通过npm安装
npm i github-markdown-css
- 安装以后导入这个文件
import './github-markdown.css'
- 直接在标签的class属性 添加该样式即可
<div class="markdown-body">
- 如果使用了postcss转换基准值,那么就要在postcss的配置文件中使用属性
exclude:'github-markdown'
,将这个库的样式排除在外
移动端图片点击预览
- 使用vant组件库中的图片预览组件
- 按需导入这个组件
import { ImagePreview } from 'vant'
- 调用方法,将图片地址作为参数传入
ImagePreview( ['https://img01.sggdfg.cn/gs/gsfg.jpeg'] )
- 方法有了,但是参数是个问题
- 因为每个文章的图片数量不一定是一致的
- 需要将文章所有的img的src放入一个数组,而这个数组怎么拿到是个关键
-
图片的src是由请求回来的数组拿到的,请求拿到数据以后将数组放到dom节点中,也就是渲染视图以后,无论是在created或者是mounted中都不合理,前者没有渲染视图拿不到dom节点、后者渲染了dom但是dom中的数据是异步请求回来的,渲染dom完成的时候异步请求还未完成
- 所以就需要另一个特殊的生命钩子
this.$nextTick( ()=>{} )
- 在这个生命周期函数中去拿dom节点.可以用原生拿,也可以用refs
- 拿到img节点数组以后,遍历节点列表,将内部的src属性的值遍历,push到事先准备好的data中的srclist数组中,
- 同时在使用forEach遍历节点数组的时候,给所有的节点都添加点击事件,点击的时候,配置阻止事件冒泡
e.stopPropagation()
和默认行为e.preventDefault()
,这里是原生的方法 - 在点击事件处理函数(箭头函数)的内部,调用
ImagePreview
方法,将src列表作为参数,传给这个方法ImagePreview( this.srclist )
- 由于点击之后下次再次点击,还是会显示第一次那个图片,如何解决?可以在forEach循环的时候除了传递item项外,再传入一个下标
i
,此时就需要对ImagePreview方法重新配置,ImagePreview( { image:this.srclist, startPosition: i } )
- 按需导入这个组件
vue.use做了什么事
- 调用传入对象内的install方法,并将vue实例作为参数,传给这个install方法
- 在install方法内部通过遍历,批量注册组件库的所有的组件这就是ui组件库为什么可以直接写的原因
websocket
refreshToken
- refreshToken是针对于token过期,影响用户体验存在的
- 主要用于无感刷新页面
- 当用户在登录状态,token过期的时候,无法进行操作
- 对此在响应拦截器中,针对token过期时间的问题,是比较难以界定的
- 长了安全性受到很大影响,太短又影响用户体验
- 所以refreshToken的存在就是一个长的token,专用来生成新的业务token,而refresh可以理解为安全token
- 具体流程
- 在用户登录的时候,将业务token和refresh token都返回到浏览器
- 通过在请求拦截器中加入业务的token进行业务操作
- 而如果业务token过期,就用过refresh token去重新拉取新的业务token
- 即在响应拦截器中,判断业务token失效,就通过refreshtoken发送请求到认证业务的服务器,拿到新的token
- 响应拦截器拿到新的token,加入到config,再次通过这个config发起请求
- 在响应拦截器中再次发起业务请求
通过vue ui
- 通过可视化的界面创建项目
- 在命令行输入
vue ui
即可打开
依赖注入 provide/inject
- 直白解释,就是在父组件中的数组,其所有的子组件,包括子组件的子组件,以此类推,都可使用该数据
- 如何使用?
- 在父组件中定义一个provide函数(也可以是一个对象),返回值是一个对象或者对象的函数,将数据放在对象的一个自定义的属性中
- 声明
//跟props和data同级,在父组件中声明 provide(){ return { aid: this.aid } }
- 在子组件中接收
//还是和data平级,子组件中声明inject inject: ["aid"]
- 这样即避免使用父传多个子组件的时候的冗长代码
注意 传入的数据并不是响应式的,但是如果传入的是一个可监听的对象,那么其属性还是响应式的
dayjs进行日期格式化
- 安装dayjs以后,通过import导入dayjs函数到vue组件
- 通过
let day = dayjs(日期).format(YYYY-MM-DD)
文件上传-头像上传
- a 在点击cell项的时候触发隐藏的input点击事件,弹出选择文件的窗口
- 首先在结构中定义一个input框,定义类型为一个上传文件的类型,然后隐藏input框
<input ref= "myFile" type = "file" style = "display:none">
- 在vant组件库中的cell组件(单元格)中定义点击事件,当点击的时候,触发input上的点击事件,这里就引出一个问题,如何在当前组件标签的点击事件的时候,触发另外一个标签的点击事件 通过vue的ref就可
<input ref= "myFile" type = "file" style = "display:none"> <van-cell title:"头像" @click = "$refs.myFile.click()"> </van-cell>
- 通过ref机制,在点击单元格的时候,触发input上的点击事件,就会弹出选择文件的窗口
- b 在弹出选择文件的窗口以后。选中文件点击确定,就应当弹出裁剪的窗口,此时就应当拿到用户选择的文件,如何拿到?
- 通过input标签上的 files属性
this.$refs.myFile.files
可以拿到一个FileList 对象,这个对象保存了用户选择的文件,并且是以数组的方式进行保存 - 所以通过change事件,在用选择完成以后,点击确定之后,change事件就会自动触发,在change事件所绑定的函数中,通过
this.$refs.myFile.files
拿到用户选择的文件
<input ref= "myFile" type = "file" style = "display:none" @change = "chooseImg">
- 在组件的配置对象的methods中,配置相应的方法
- 在这个方法中修改值,将弹出层的显示的属性打开,将弹出层显示出来
- 并且将用户选择的数据传给弹出层组件
-end<!-- 弹出层标签 将用户选择的文件数据,在弹出层弹出的时候,将这个头像数据传递给弹出层组件--> <UpdateAvatar :imgData="imgData"> data(){ return { imgData:null } } methods{ chooseImg(){ let fileUser = this.$refs.myFile.files //判断用户是否选择了文件数据 if(fileUser.length=== 0 ) return //将用户上传的数据传给imgData这个属性 this.imgData= fileUser[0] //打开 修改头像的 弹出层 this.isShowAvatar = true } }
- 然后传给
<UpdateAvatar :immg="imgData">
组件中通过props接收
props: { immg:{ required :true, type :file } }
- 这里就会暴露一个问题,接收到的immg是一个file格式的数据,原型是继承子Blob对象,这个对象是一个通过二进制存放文件数据的类型
- 因为img标签的src属性只认识 三种格式:文件路径/文件二进制数据/base64 所以就需要对文件进行转换,因为immg的数据类型是file
- 即可以在父组件传数据的时候,就提前转换好数据拿过来
- 通过在父组件
this.imgData= fileUser[0]
给值的时候就进行转换:this.imgData= URl.createObjectURL(fileUser[0])
,会转换成一个虚拟地址,这样子组件弹出层就可以展示出这个图片
- 通过input标签上的 files属性
- c 完成裁剪操作,将弹出层改造成为一个裁剪层
- 通过安装 并在弹出层组件中导入 第三方的包:
cropperjs
具体对象上的方法可以参考这篇文章- 在子组件中引入
// 引入 裁剪模块的 css和js函数 import 'cropperjs/dist/cropper.css'; import Cropper from 'cropperjs';
- 用于裁剪包的使用,需要一个传入dom节点进行改造,那么就需要先拿到dom节点
<img ref= "imgObj" :src="imgData"> <!-- 点击下方两个不同按钮触发不同事件 --> <div class="toolbar"> <span @click="$emit('onClose')">取消</span> <span @click="onConfirm">完成</span> </div> //定义裁剪对象 data(){ return { cropObj: { required: true } } } //在组件的生命周期钩子中完成转换 mounted(){ //创建裁剪对象并传入dom this.cropObj = new Cropper(this.$refs,imgObj , { viewMode:1, //只能在裁剪的图片范围内移动 dragMode:'move', //画布和图片都可以移动 aspectRatio: 1 , //裁剪区默认正方形 autoCropArea:1 ,//自动调整裁剪图片 cropBoxMovable: false, //禁止裁剪区移动 cropBoxResizable:false, //禁止裁剪区缩放 background:false, //关闭默认背景 }) }
- 点击确定按钮以后触发methods中的事件,并执行回调
- 调用getcroppedCanvas()方法,这个方法是裁剪对象上的,会返回一个HTMLCanvasElement对象,而执行toBlob方法则会创建一个blob对象,这个方法接收三个参数,第一个参数必选,为回调函数,这个回调函数固定接收一个参数就是裁剪对象的blob数据
- 具体的toBlob可以参考mdn文档
onConfirm(){ this.cropObj.getCroppedCanvas().toBlob( //执行回调函数 async (blob)=> { //创建一个formData盒子,用于存放要发送给服务器的图片 let fd = new FormData() //调用FormData对象自带的方法,将数据添加到fs这个盒子中 fd.append('photo',blob) //发送请求 try{ const { data: resbody } = await uploadPhoto(fd) //弹出提示 this,$toast.success("修改成功") //通过父组件 关闭弹窗、修改头像把新的地址给父组件 this.$emit('closePop',resBody.data.photo) }catch{ //提示失败 this.$toast.fail("修改失败") } } ) }
- 通过安装 并在弹出层组件中导入 第三方的包:
组件缓存
- 为什么要是用组件缓存
组件缓存是用于增加用户体验,减少浏览器运行压力,不做缓存会降低浏览器性能
为什么会有这种影响?因为在路由组件切换的时候,都是销毁了整个组件对象,在切换回来的时候,又重新创建了组件对象,来回的创建和销毁,除了严重影响浏览器性能外,在也会影响用户的体验,因为销毁和重新建立组件会清空用户的操作记录,让组件回归到最初始的状态,然而在某些地方用户并不希望会有这种情况
- 组件缓存是什么
- 组件缓存的目的
- 是为了记录用户操作,提高浏览器性能
- 具体原理
- 组件缓存即是将路由组件对象在切换的时候,并不销毁上一次的组件对象,而是将其存在内存中,在下切换回该路由组件的时候,直接将上一次在内存中缓存的路由组件对象拿出来直接渲染视图,而不需要重新解析代码去创建组件对象
- 组件缓存的目的
- 如何实施
- 通过KeepAlive标签,将路由的出口标签
<router-view>
包裹起来<keep-alive> <router-view> </keep-alive>
,此时所有通过该出口显示的路由组件就会进行缓存 - 在切换时就不会再创建组件对象,而是从内存中直接读取然后渲染视图
- 通过KeepAlive标签,将路由的出口标签
- 缓存组件的存在的问题一
- 当有多级路由的时候,例如有三个组件嵌套:App根组件(router-view)=>大窗口组件(router-view)=>三个路由组件
- 如下图
- 在如果只对大窗口组件 中的子组件即 home user 短视频做组件缓存,这样虽然可以达到切换这三个子组件时有缓存效果,但是如果直接将主视图组件切换走,那么主视图组件就会被销毁,这有给主视图的子组件做的缓存就会失效,
- 解决办法:即在主视图组件中也加入组件缓存,这样即时组件切换开主视图组件,其内部记录的子组件缓存也会一同被存起来
- App.vue
<template> <div id="app123"> <!-- 一级路由占位符:只有layout需要被缓存 --> <keep-alive include="layout"> <router-view /> </keep-alive> </div> </template>
- layout.vue
<keep-alive include="home"> <router-view /> </keep-alive>
- 缓存组件的问题二(缓存滚动条高度)
- 问题说明:在缓存组件从内存中取出的时候是一个组件对象,它并没有记录用户在离开该组件的时候所看到的dom显示位置,这会给用户造成体验差
- 如何解决:
- 首先需要拿到滚动条的高度值,也就是内容dom相对于父节点的位置
- 通过拿到产生滚动条的父盒子的dom节点,(例如有50个li节点,显示的时候在一个div盒子内显示不完,会产生滚动条,而此时就要拿到div这个dom节点,因为滚动 是因为div盒子被li太远的原因而撑大了,由于div的父级或者是屏幕有固定的高度有限,无法显示完div,所以就会滑动div盒子);可以通过ref去拿这个节点
- 拿到节点以后给这个节点添加一个监听事件,这个事件是原生的事件监听:onscroll事件,值是一个函数,每次dom元素scroll值发生改变,(scroll是dom元素自带的,有两种相对于父级元素的位置,一个是scrollTop 相对于顶部,一个是scrollLeft 相对于左边)就会触发该事件,然后执行一次该函数
- 在组件页面加载完成渲染所有dom节点后,就通过 mounted钩子 去给拿到这个节点,并且添加这个事件,在dom元素位置发生变化以后,就触发回调函数
- 在回调函数的内部,将scrollTop值记录下来,放到localstorage中去存着,用户滑动的时候,会改变scrollTop的值,就会触发这个事件监听,继而执行函数,在函数执行的时候,就会存一次高度值scrollTop,
- 用户操作导致位置发生改变的值已经拿到,之后就是显示的时候重新给值的问题
- 由于之前对组件进行了缓存,所以通过组件缓存的两个钩子函数就可以完成页面渲染时给高度的操作
- 这里有个点,缓存的路由组件,除了初次渲染的时候,在后续的缓存切换过程中不会触发mounted生命周期
- 只会触发渲染的缓存组件特有的两个函数,分别是 deactivated和 activated
- 而此时我们就可以通过 activated函数对dom节点的srcollTop进行赋值,在该函数中需要再取一次dom节点,然后该dom节点的 srcollTop属性进行赋值,这样就达到效果
- 部分关键代码:
- 被缓存的组件.vue
- 结构
<div ref="artListDiv" class="artList"> <!-- 下拉更新组件 --> ---------------- <!-- 文章列表组件 --> <van-list :error.sync="isError" error-text="请求失败,点击重新加载" v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" > <!-- 文章项组件 --> <ArtItem v-for="(art, i) in list" :key="i" :article="art" /> </van-list> </van-pull-refresh> </div>
- 逻辑
- 存
mounted() { this.$refs.artListDiv.onscroll = (e) => { // a.先读取 本地保存的 高度数据 let topDataList = getItem('1008scrollTop'); !topDataList && (topDataList = {}); // b.再 设置 当前频道的 高度数据 topDataList.1 = 100 topDataList[this.channel.id] = e.target.scrollTop; // c.最后 将 新的 高度数据对象 重新保存到 本地 setItem('1008scrollTop', topDataList); }; },
- 取
// 当 组件 从 keepalive缓存中 重新渲染出来,就会执行 activated() { // 从本地 读取 高度数据 let topDataList = getItem('1008scrollTop'); // 如果存在,则 把本频道的 高度取出,设置给本频道的div if (topDataList) { this.$refs.artListDiv.scrollTop = topDataList[this.channel.id]; } },
- 缓存组件的问题三(多缓存组件高度的scrollTop值需要不一致)
- 这个问题在上面的代码中已经解决
- 解决办法
- 在滑动触发这个事件的时候,把存高度数据的定义为一个对象,根据每个项的下标的不同,将id为键,当前高度为值,组成键值,存在一个对象中
this.$refs.artListDiv.onscroll = (e) => { // a.先读取 本地保存的 高度数据 let topDataList = getItem('1008scrollTop'); !topDataList && (topDataList = {}); // b.再 设置 当前频道的 高度数据 topDataList.1 = 100 topDataList[this.channel.id] = e.target.scrollTop; // c.最后 将 新的 高度数据对象 重新保存到 本地 setItem('1008scrollTop', topDataList); };
- 所以在页面加载后,触发滑动事件的时候会先读取本地存数据的那个对象,把数据放到定义的对象
topDataList
中,判断有没有数据,保证这个对象,通过对象[属性名]
的方式,添加,或者覆盖之前的数据,最后重新把本地当前组件对应的高度存起来 - 而在读取的时候,就可以直接用当前组件的id,去匹配本地的存好的数据所对应的id
// 当 组件 从 keepalive缓存中 重新渲染出来,就会执行 activated() { // 从本地 读取 高度数据 let topDataList = getItem('1008scrollTop'); // 如果存在,则 把本频道的 高度取出,设置给本频道的div if (topDataList) { this.$refs.artListDiv.scrollTop = topDataList[this.channel.id]; } },
- 缓存组件的问题四(加缓存的时候,部分组件无需缓存)
- 问题说明:在某些时候,部分组件不需要进行缓存,由于定义,会将子路由都在路由出口显示的时候,都会被缓存,但是个别组件不需要进行缓存
- 缓存本质也是加大了内存的开销 不必要的组件就不用缓存
- 解决办法
- 通过keep-alive标签内的属性,进行配置
- include - string | RegExp | Array。只有名称匹配的组件会被缓存。
- exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。
- max - number | string。最多可以缓存多少组件实例。
<keep-alive include="home"> <router-view /> </keep-alive>
其中的home即是组件名,意思就是只有组件名为home的组件才会被缓存
- 问题说明:在某些时候,部分组件不需要进行缓存,由于定义,会将子路由都在路由出口显示的时候,都会被缓存,但是个别组件不需要进行缓存
v-model 的修饰符
- input框上v-mode的修饰符
- v-model本质是在input框上绑定了input事件,每当值发生变化,就会触发input事件,然后将值传给绑定的变量
- 而input事件 会在输入框的值发生变化的时候 就会触发,每次变化一次都会立即触发一次
- 而修饰符就是跟在model后的,例如
.lazy
、.number
、.trim
.lazy
会将原本input框上的通过 v-model 绑定的input监听事件,修改为change事件, change事件是输入框失去焦点以后触发.number
会将输入框获取的数据,自动转换为数字number类型.trim
则会自动过滤input框输入的内容的前后空格
- 子组件上的v-model
- 例如:在组件
Test
中<Home v-model="className">
等同于<Home :value="className" @input= "username = $event ">
- 在子组件内部,通过props接收
props:{ value:{ required:true }, title:{type:String}, placeholder:{ type:String, default:'请输入文本' } }, methods:{ inputChanges(e){ // console.log(e.target.value); this.$emit('input',e.target.value) } }
- 例如:在组件
按键修饰符
- 主要是针对键盘事件所定义比如
@keydown.enter="keydonw"
- 主要的键盘事件
@keydown \ @keyup \ @keypress
其中@keydown和@keypress,@keydown拿到的触发键值大写小写都是同一个按键,而keypress大写和小写两种方式触发后的数字不同 - 主要的修饰符Vue 为最常用的键提供了别名:
-
.enter 回车
-
.tab tab键
-
.delete (捕获“删除”和“退格”键)
-
.esc 返回
-
.space 退格
-
.up 上键
-
.down 下键
-
.left 左
-
.right 右
- 目前已经不推荐通过
KeyboardEvent.keyCode
即按键事件中的e.keyCode
;e 是 获得按键触发原, - 而是推荐使用
e.code
事件修饰符
- 事件修饰符是针对事件触发使用的,比如click,mouse等
-
.stop 阻止事件冒泡
-
.prevent 阻止事件默认行为,即提交按钮submit会触发表单默认的提交事件,加了以后默认不触发
-
.capture 捕获模式,先执行自身,然后往内部触发
-
.self 只有自身是事件源的时候才触发回调
-
.once 只触发一次
-
.passive 绑定的dom元素的滚动事件将会立即触发,而不会等待onscroll完成 ,简单将就是不用去看有没有用组织默认行为的,直接执行
-
.left 鼠标点击左键触发
-
.right 鼠标点击右键才触发
-
.middle 鼠标点击中键触发
- .native事件,这个东西在3.0已经废弃,在2.x版本中,是父组件中,用在自定义的组件标签中的,比如
<home click.native = "delname">
,这个点击事件就是直接加到home组件的根标签上,并且是原生click事件
属性修饰符
.sync
- 在2.x的vue版本中,对于组件中使用v-model只能使用一次,并且子组件的props固定接收value作为接收参数
- 对于需要对子组件进行多个数据双向绑定来说,就显得不足
- 父组件App.vue
<Home v-model = "useName" :value2.sync = "userAge"> <Home> data{ return { useName:"bob", userAge: 20 } }
- 子组件 home.vue
//这里省略v-model传过来的流程,上文有 <input :value="value2" @input="$emit('update:value2' , age)" /> { props: { value2{ type:number } } }
- 其实区别就是触发的时候,要通过在绑定的属性前面加一个
update:
,去触发,区别于v-model的情况就是,v-model用的是input事件触发,而加了.sync
则是通过在update: 绑定的属性名
- 在vue3.x的版本中,其实已经不推荐使用
.sync
修饰符,因为已经可以使用多个v-model去绑定多个属性值
- 第二个属性修饰符
.prop
- 作用就是 将 自定义属性 隐藏起来,增加数据的隐秘性
- 第三个属性修饰符
.camel
- 将bind绑定的属性名进行驼峰化
其他补充笔记
路由守卫
- 路由守卫用来权限校验,如果不符合某一种条件,就不允许某些组件显示
- 全局前置守卫 beforeEach (()=>{ log() })
- 在路由配置文件index.js中,创建VueRouter实例以后,
const router = new VueRouter({ routes // 路由表 })
- 通过
router.beforeEach (()=>{ log() } )
每当路由表中的任意一个路由发生跳转,会在切换之前去调用beforeEac中接收的函数 - 这个函数会接收三个参数,分别是to,from ,next
router.beforeEach (( to, from, next)=>{ next() })
- 只有在函数中执行了调用了next函数,才会继续跳转
- 在路由配置文件index.js中,创建VueRouter实例以后,
let 暂时性死区
websocket
- a 为什么要websocket ,因为http只能让客户端向服务器主动发起通信,并且只能由客户端发起,而websocket协议就解决了这个问题,可以让服务器主动向浏览器发送通信
- 浏览器发送请求到服务器,实质是发送到服务器电脑,由服务器的监听套接字,发送到服务器的软件
- 正是由于服务器软件内的监听套接字,所以才可以接收到请求
- b 因为服务端的监听套接字
先启动
、会监听端口,所以浏览器才能给服务器发送请求- 但是这种模式只能让服务器被动的响应数据,而无法主动向浏览器推送数据
- c 而websocket就是完成在浏览器端放置一个套接字,监听服务器向浏览器发送的消息,
- 即前端(浏览器端)是websocket,而服务端则就是socket,实现了服务器可以主动向浏览器发送请求
网站搭建
- DNS服务器会进行域名解析
vue2和vue3响应式原理
- vue2
- 通过defineProperty对数据进行劫持,然后进行操作
- 但是无法监听到对象的属性删除或是添加
- 对数组也是通过重写改变原数组的七个方法进行重写,push,pop,shift,unshift,sort,reverse,splice
- 但是无法监听到数组长度的变化
- vue3
- 通多代理对象proxy,对目标对象进行数据代理,从而完成数据的响应式
- 使用的时候是一个构造函数,通过new 创建一个代理对象,参数分别是有两个:目标对象,handler对象
const obj = new Proxy( olderObj , { } )
- 使用的时候是一个构造函数,通过new 创建一个代理对象,参数分别是有两个:目标对象,handler对象
- 通多代理对象proxy,对目标对象进行数据代理,从而完成数据的响应式
vue3中的生命周期
- 3.x中的声明周期和2.x的生命周期
- 3.x中的生命周期,
-
setup(),
-
onbeforemount(), 挂载前
-
onmounted(), 挂载后
-
onbeforeUpdate(), 更新前
-
onUpdated(), 更新后
-
onbeforeUnmount 销毁前
-
onUnmounted 销毁后
-
- 2.x 中的生命周期
-
beforeCreate
-
created
-
beforemount
-
mounted
-
beforeUpdate
-
updated
-
beforeDestory
-
destoryed
-
- 3.x中的生命周期,
vue3中的ref两个用法
- 第一种,获取元素节点
<template> <div> <div ref="box">我是div</div> </div> </template> <script> import { ref,onMounted } from "vue"; export default { setup() { let box = ref(null); //本质是reactive({value:null}) // 需要在生命周期获取 onMounted(()=>{ // 当界面挂载出来后就会自动执行 console.log(box.value); //null }) //接受的是null,原因是setup执行时机比mounted早,dom还没形成 console.log(box.value); //"我是div" return { box }; }, }; </script>
- 第二种,将数据变为响应式
-
ref 接受一个原始值,返回一个具有响应式的对象,对象有一个value属性,其值就是所传递的原始值
- 在vue3中将数据变为响应式,可以使用reactive(),传入一个对象,将这个对象变为响应式
- 而ref和reactive一样,也可以实现数据响应式,但是reactive中实现必须传入一个对象,而ref可以对单独的某一个属性进行监听
- 例如:
let state = ref(888)
本质是将state转成一个proxy对象,而其有一个属性value,值是888,在模板中使用的时候,直接{{ state}}
即可,不需要通过state.value
-
ref、toRef、toRefs
- ref、toRef、toRefs 都可以将某个对象中的属性变成响应式数据
- ref的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
- toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
- toRef 一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
- toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行
VScode中让canvas有代码提示
- 只需要一行代码即可,必须要加在
let canvas = document.querySelector("#tuto");
,即获取canvas实例之前 - 详细代码
/** @type {HTMLCanvasElement} */ let canvas = document.querySelector("#tuto");
canvas实现弹幕效果
- 使用requestAnimationFrame