Fork me on GitHub

vue响应式数据原理

vue2.x的响应式

劫持一个个属性

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取(getter)、修改(setter)进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      1
      2
      3
      4
      Object.defineProperty(data, 'count', {
      get () {},
      set () {}
      })
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。
    • 不能监视到数组索引的值的变化。

//模拟实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let data = {
name:'xxx',
address:'xx',
}

//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs)

//准备一个vm实例对象
let vm = {}
vm._data = data = obs

function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
obj[k] = val
}
})
})
}

// 解析模板,生成虚拟DOM,对比虚拟dom

Vue3.0的响应式

代理整个对象

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除、根据数组下标更改值等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

  • 不写{}的get、set、deleteProperty也可,但是无法捕获到数据的变化

uni-app笔记

uni-app

各种方法在API里找,拨打电话等…

各种组件在组件里找

js等文件配置在全局文件里找,底部导航栏等…

编译语法在教程找

  • view - div:view标签不能拷贝文字内容。

  • text - span

  • navigator - a

  • scroll-view - 区域滚动,webview渲染的页面中,区域滚动的性能不及页面滚动。

  • scroll-view - 区域滚动,webview渲染的页面中,区域滚动的性能不及页面滚动。

    • 纵向滑动:<scroll-view scroll-y> 子元素 </scroll-view>

    • 横向滑动:<scroll-view scroll-x> 子元素</scroll-view>,样式设置子元素为display: inline-block;,父元素设置white-space: nowrap;

  • swiper - 轮播图

uniapp页面生命周期

  • onLoad(e) – setup/Created,只有onLoad()能拿到页面路由参数

  • onShow()

  • onReady() – onMounted,只有onReady()才能获取到dom节点

uniapp像素单位rpx

以750为基准

全局样式

全局组件样式(直接应用):

common文件夹下创建样式文件@/common/style/common-style.scss(css/scss/less),然后在App.vue用@import导入到<style></style>

1
2
3
<style>
@import "@/common/style/common-style.scss"
</style>

uni.scss:

项目自带颜色样式,可直接使用,也可在此文件创建新颜色样式。如background: $uni-color-primary

全局颜色字体样式(需要自行使用):

common文件夹下创建样式文件@/common/style/base-style.scss,然后在uni.scss中引入

  1. 修改字体等直接用color:$变量名
  2. 修改uni-icons字体图标组件时,需要用到vue的:deep深度选择器
1
2
3
4
5
:deep(){
.uni-icons{
color:$brand-theme-color !important; // !important表优先级最高,高于默认的行内样式
}
}

插件

  • vite.config中安装插件unplugin-auto-import自动导入vue和uniapp的生命周期、函数

npm i unplugin-auto-import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//在vite.config.js中引入

import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
plugins: [
uni(),
//自动导入配置
AutoImport({
imports: [
//预设
'vue',
'uni-app'
]
})
]
})

扩展组件uni-ui

登录uniapp账号后直接按需下载相应组件,下载完项目根目录有uni_modules文件夹

网络请求的三种处理方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//1.success里对返回结果进行处理,易产生回调地狱
function request(){
uni.request({
url:"https://jsonplaceholder.typicode.com/posts",
success:res=>{
console.log(res);
arrs.value = res.data
}
})
}

//2.使用.then对返回结果进行处理
function request(){
uni.request({
url:"https://jsonplaceholder.typicode.com/posts"
}).then(res=>{
//处理返回结果
arrs.value = res.data
})
}

//3.异步操作同步化
const request = async () => {
const res = await uni.request({
url:"https://jsonplaceholder.typicode.com/posts"
})
//处理返回结果
arrs.value = res.data
}

异步同步化错误处理:

使用

1
2
3
4
5
6
7
8
9
10
11
try{
const request = async () => {
const res = await uni.request({
url:"https://jsonplaceholder.typicode.com/posts"
})
//处理返回结果
arrs.value = res.data
}
}catch(err){
console.log(err)
}

包裹异步同步化语句

微信开发者工具跳转到指定页面

  • 到编译模式里设置相关页面路径即可

下拉刷新实现

下拉刷新利用生命周期钩子onPullDownRefresh()

使用前要在pages.json开启配置:

  • 需要在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh
  • 当处理完数据刷新后,uni.stopPullDownRefresh 可以停止当前页面的下拉刷新。一般在finally里调用uni.stopPullDownRefresh

相关api:

uni.startPullDownRefresh(OBJECT):相当于手动下拉刷新

uni.stopPullDownRefresh()

加载更多字样:在列表尾部加扩展组件

<uni-load-more status="loading"></uni-load-more>

底部安全区域

env(safe-area-inset-bottom):移动端设置,可防止触摸无效等。

可设置为height:env(safe-area-inset-bottom);,padding-bottom:env(safe-area-inset-bottom);

条件编译

  • 条件编译处理多端差异,处理跨端兼容。
条件编译写法 说明
#ifdef APP-PLUS 需条件编译的代码 #endif 仅出现在 App 平台下的代码
#ifndef H5 需条件编译的代码 #endif 除了 H5 平台,其它平台均存在的代码(注意if后面有个n)
#ifdef H5 || MP-WEIXIN 需条件编译的代码 #endif 在 H5 平台或微信小程序平台存在的代码(这里只有||,不可能出现&&,因为没有交集)

适用于html和css,通过注释方式进行编译

1
2
3
4
5
6
7
8
9
//html:
<!-- #ifdef APP -->
条件编译标签
<!-- #endif -->

//css:
/* #ifdef APP */
条件编译样式
/* #endif */

例子:

1
2
3
4
5
6
7
8
9
//小程序时
<!-- #ifdef MP -->
<button open-type="contact"></button>
<!-- #endif -->

//不是小程序时
<!-- #ifndef MP -->
<button></button>
<!-- #endif -->

元素顶到状态栏问题

全屏时,元素顶到状态栏时解决:

在元素前设置一个空盒子,通过API uni.getSystemInfoSync()获取到状态栏高度statusBarHeight,然后给空盒子设置高度,使空盒子顶下占用到状态栏的元素

触底刷新

  1. 使用API onReachBottom()

  2. 插件z-padding

正在加载提示

  1. 初始进入页面的正在加载

​ 使用uni-load-more拓展组件,根据有无数据进行条件渲染v-if

1
2
3
<view class="loadingLayout" v-if="!classList.length">
<uni-load-more status="loading"></uni-load-more>
</view>
  1. 下拉触底的正在加载

​ 加载完后需要现实没有更多数据:status="noData?'noMore':'loading'"

1
2
3
<view class="loadingLayout" v-if="classList.length || noData">
<uni-load-more :status="noData?'noMore':'loading'"></uni-load-more>
</view>

本地缓存

同步地

1
uni.setStorageSync("storageList",List)

margin和padding区别及用法

padding 用于控制元素内容与边框之间的间距,是元素内部的空间;margin 用于控制元素与其相邻元素之间的间距,是元素外部的空间。

  1. 使用margin的场景:
    ①若需要在border外侧添加空白时。
    ②盒子空白处不需要背景(色)时。
    ③需要使用负值对页面布局时(margin值可以取负值,但是padding不行)
    ④上下相连得两个盒子之间的空白需要相互抵消时,如15px+20px得margin,将得到20px的空白。(margin折叠)
  2. 使用padding的场景
    ①需要在border内侧添加空白时。
    ②盒子空白处需要背景(色)时
    ③上下相连的两个盒子之间的空白,希望等于两者之和时。如15px+10px将得到25px的空白。

根据具体情况使用为妙

? 盒子与盒子间不设置margin为妙,会发生(margin折叠)。在盒子内设置padding为妙。

margin折叠是指当有两个或多个垂直边距相遇时,形成的一个外边距,这个外边距的高度等于两个元素发生叠加的外边距中高度较大的一个。

发生margin折叠的三种情况

  1. 元素自身的折叠
  2. 相邻两个元素折叠
  3. 父子元素的折叠

盒子外空白用margin居多,盒子内空白元素用padding居多

CSS笔记

flex: 1;

即为flex-grow:1,经常用作自适应布局。将父容器的display:flex,侧边栏大小固定后,将内容区flex: 1,内容区则会自动放大占满剩余空间。

flex属性 是 flex-grow、flex-shrink、flex-basis三个属性的缩写。

  • flex-grow:定义项目的的放大比例;

  • flex-shrink:定义项目的缩小比例;

  • flex-basis: 定义在分配多余空间之前,项目占据的主轴空间(main size),浏览器根据此属性计算主轴是否有多余空间。相当于设置初始值

line-height

设置行高line-height和盒子高度相等时,实现显示一行的效果。

换行溢出内容省略号

1
2
3
overflow: hidden;			//隐藏溢出内容
white-space: nowrap; //不换行
text-overflow: ellipsis; //溢出字符省略号代替

图片在盒子内圆角效果失效border-radius

给img标签的父级元素添加圆角时,需要给父级元素设置overfloor: hidden;

1
2
3
4
5
6
7
8
9
.box{
height: 340rpx;
border-radius: 30rpx; //uniapp单位
overfloor: hidden; //解决圆角失效问题
.image{
width: 100%;
height: 100%;
}
}

磨砂玻璃效果

background: rgba(0,0,0,0.2); //添加半透明背景颜色
backdrop-filter: blur(20rpx); //添加失焦磨砂效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.box {
height: 340rpx;
border-radius: 10rpx;
overflow: hidden; //溢出部分隐藏,使圆角效果生效
position: relative;

.pic {
width: 100%;
height: 100%;
}

.tab{
position: absolute;
top: 0;
left: 0;
color: #fff;
border-radius: 0 0 20rpx 0;
background: rgba(250,129,90,0.7); //添加背景颜色
backdrop-filter: blur(20rpx); //添加失焦磨砂滤镜效果
font-size: 22rpx;
transform: scale(0.8); //缩放
transform-origin: left top; //左上为基准
padding: 6rpx 10rpx;
}

.mask{
position: absolute;
width: 100%;
height: 70rpx;
bottom: 0;
left: 0;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0,0,0,0.2); //添加半透明背景颜色
backdrop-filter: blur(20rpx); //添加失焦磨砂效果
}
}

网格布局

grid-template-columns 属性用于设置网格布局中的列数及宽度。

grid-template-rows 属性用于设置网格布局中的行数及高度。

gap:网格间距

1
2
3
display: grid;
grid-template-columns: repeat(3,1fr); //3列
gap: 10px;

渐变背景

以下实例演示了从左侧开始的线性渐变,从红色开始,转为黄色:

1
background-image: linear-gradient(to right, red , yellow); 

以下实例演示了从左上角到右下角的线性渐变:

1
background-image: linear-gradient(to bottom right, red , yellow);

以下实例演示了线性渐变指定一个角度:

1
background-image: linear-gradient(180deg, red, yellow); 

以下实例演示了多个终止色:

1
background-image: linear-gradient(to right, red,orange,yellow,green,blue,indigo,violet);

以下实例使用了透明度:

1
grad {  background-image: linear-gradient(to right, rgba(255,0,0,0), rgba(255,0,0,1));

两个方向进行层叠:实现从从右到左渐变,上到下淡化的效果。

  • color 50%指定渐变中的颜色起始点
  • color 400px指定渐变中的颜色起始点
1
2
3
background: 
linear-gradient(to bottom,rgba(0,0,0,0) 0%,#fff 50%),//透明到白色,rgba(0,0,0,0)和transparent一样都是透明色,透明从0开始,白色50%开始
linear-gradient(to left,red,blue);
1
2
3
background: 
linear-gradient(to bottom,transparent,#fff 400rpx), //transparent透明色,白色400rpx时开始
linear-gradient(to right,#beecd8 20%,#F4E2D8); //#beecd8从20%开始渐变

定位元素居中方式

  1. left:0; right:0;,将拉伸宽度;
  2. width: fit-content;,宽度根据内容自适应
  3. margin: auto;,居中
1
2
3
4
5
position: absolute;
left: 0;
right: 0;
margin: auto;
width: fit-content; //宽度根据内容自适应

css选择器

scss:&>view:选择所有父级是&的view

选择器 示例 示例说明 CSS
.class .intro 选择所有class=”intro”的元素 1
#id #firstname 选择所有id=”firstname”的元素 1
* * 选择所有元素 2
element p 选择所有

元素

1
element,element div,p 选择所有
元素和

元素

1
element*.class* p.hometown 选择所有 class=”hometown” 的

元素

1
element element div p 选择
元素内的所有

元素

1
element>element div>p 选择所有父级是
元素的

元素

2
element+element div+p 选择所有紧跟在
元素之后的第一个

元素

2
[attribute] [target] 选择所有带有target属性元素 2
[attribute=value] [target=-blank] 选择所有使用target=”-blank”的元素 2
[attribute~=value] [title~=flower] 选择标题属性包含单词”flower”的所有元素 2
[attribute|=language] [lang|=en] 选择 lang 属性等于 en,或者以 en- 为开头的所有元素 2
:link a:link 选择所有未访问链接 1
:visited a:visited 选择所有访问过的链接 1
:active a:active 选择活动链接 1
:hover a:hover 选择鼠标在链接上面时 1
:focus input:focus 选择具有焦点的输入元素 2
:first-letter p:first-letter 选择每一个

元素的第一个字母

1
:first-line p:first-line 选择每一个

元素的第一行

1
:first-child p:first-child 指定只有当

元素是其父级的第一个子级的样式。

2
:before p:before 在每个

元素之前插入内容

2
:after p:after 在每个

元素之后插入内容

2
:lang(language) p:lang(it) 选择一个lang属性的起始值=”it”的所有

元素

2
element1~element2 p~ul 选择p元素之后的每一个ul元素 3
[attribute^=value] a[src^=”https”] 选择每一个src属性的值以”https”开头的元素 3
[attribute$=value] a[src$=”.pdf”] 选择每一个src属性的值以”.pdf”结尾的元素 3
[attribute*=value] a[src*=”runoob”] 选择每一个src属性的值包含子字符串”runoob”的元素 3
:first-of-type p:first-of-type 选择每个p元素是其父级的第一个p元素 3
:last-of-type p:last-of-type 选择每个p元素是其父级的最后一个p元素 3
:only-of-type p:only-of-type 选择每个p元素是其父级的唯一p元素 3
:only-child p:only-child 选择每个p元素是其父级的唯一子元素 3
:nth-child(n) p:nth-child(2) 选择每个p元素是其父级的第二个子元素 3
:nth-last-child(n) p:nth-last-child(2) 选择每个p元素的是其父级的第二个子元素,从最后一个子项计数 3
:nth-of-type(n) p:nth-of-type(2) 选择每个p元素是其父级的第二个p元素 3
:nth-last-of-type(n) p:nth-last-of-type(2) 选择每个p元素的是其父级的第二个p元素,从最后一个子项计数 3
:last-child p:last-child 选择每个p元素是其父级的最后一个子级。 3
:root :root 选择文档的根元素 3
:empty p:empty 选择每个没有任何子级的p元素(包括文本节点) 3
:target #news:target 选择当前活动的#news元素(包含该锚名称的点击的URL) 3
:enabled input:enabled 选择每一个已启用的输入元素 3
:disabled input:disabled 选择每一个禁用的输入元素 3
:checked input:checked 选择每个选中的输入元素 3
:not(selector) :not(p) 选择每个并非p元素的元素 3
::selection ::selection 匹配元素中被用户选中或处于高亮状态的部分 3
:out-of-range :out-of-range 匹配值在指定区间之外的input元素 3
:in-range :in-range 匹配值在指定区间之内的input元素 3
:read-write :read-write 用于匹配可读及可写的元素 3
:read-only :read-only 用于匹配设置 “readonly”(只读) 属性的元素 3
:optional :optional 用于匹配可选的输入元素 3
:required :required 用于匹配设置了 “required” 属性的元素 3
:valid :valid 用于匹配输入值为合法的元素 3
:invalid :invalid 用于匹配输入值为非法的元素 3
:has :has 允许根据其后代元素来选择一个元素。 3
:is :is 接收任何数量的选择器作为参数,并且返回这些选择器匹配的元素的并集。

JWT和token有什么区别?

JWT和token有什么区别?

  • Token其实就是一个加密的字符串,通过MD5等一些不可逆加密算法实现的,可以保证唯一性。

  • JWT是json web token缩写,是一种特定类型的Token,它采用了JSON格式来存储有关用户身份和访问权限的信息。JWT会把用户信息加密到token里,服务器不保存任何用户信息,服务器通过使用保存的密钥来验证JWT Token。

JWT由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature),头部包含有关JWT的元数据和算法信息,载荷包含实际的用户数据,例如用户ID、角色等,签名用于验证JWT的完整性和真实性。

JWT和token的区别主要体现在是否需要查询数据库。对了Token来说,服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。而JWT则不需要,因为用户的信息及加密信息、过期时间,都存储在JWT里,服务端只需要使用密钥解密进行校验即可,不需要查询或者减少查询数据库。

sass全局样式

  1. 在src文件夹下创建styles文件夹
  2. 创建common.scss文件,创建全局样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//common.scss
// 重置样式
* {
box-sizing: border-box;
}

html {
height: 100%;
font-size: 14px;
}
body {
height: 100%;
color: #333;
min-width: 1240px;
font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
sans-serif;
}
body,
ul,
h1,
h3,
h4,
p,
dl,
dd {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
color: #333;
outline: none;
}
i {
font-style: normal;
}
input[type='text'],
input[type='search'],
input[type='password'],
input[type='checkbox'] {
padding: 0;
outline: none;
border: none;
-webkit-appearance: none;
&::placeholder {
color: #ccc;
}
}
img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
}
ul {
list-style: none;
}

#app {
background: #f5f5f5;
user-select: none;
}

.container {
width: 1240px;
margin: 0 auto;
position: relative;
}
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

.ellipsis-2 {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}

.fl {
float: left;
}

.fr {
float: right;
}

.clearfix:after {
content: '.';
display: block;
visibility: hidden;
height: 0;
line-height: 0;
clear: both;
}

// reset element
.el-breadcrumb__inner.is-link {
font-weight: 400 !important;
}
  1. 在main.js中引入
1
2
3
4
//main.js

//引入初始化的样式文件
import '@/styles/common.scss'

接口封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//http.js

//axios基础的封装
//导出的httpInstance实例为apis服务
import axios from 'axios'
import router from '@/router'

//只使用组件 API
import 'element-plus/theme-chalk/el-message.css'
import { ElMessage } from 'element-plus'

//引入pinia(user)
import { useUserStore } from '@/stores/userStore'

//创建axios实例
const httpInstance = axios.create({
baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
timeout: 6000
})

//拦截器,e代表error

// axios请求拦截器
httpInstance.interceptors.request.use(config => {
//1.获取pinia中user数据
const userStore = useUserStore()
//2.按照后端的要求拼接token数据
const token = userStore.userInfo.token
if (token) {
config.headers.Authorization = `Bearer ${token}` //请求的config拼接token到header上
}
return config
}, e => { return Promise.reject(e) })

// axios响应式拦截器
httpInstance.interceptors.response.use(res => res.data, e => {
const userStore = useUserStore()
//统一错误提示
ElMessage({
type: 'warning',
message: e.response.data.message
})
//401token失效处理
//1.清除本地用户数据
//2.跳转到登录页
if (e.response.status === 401) {
userStore.clearUserInfo()
router.push('/login')
}

return Promise.reject(e)
})

export default httpInstance

Ajax请求

Ajax请求

  1. 创建 XMLHttpRequest 对象

  2. 配置请求方法和请求 url 地址

  3. 监听 loadend 事件,接收响应结果

  4. 发起请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//GET不带参数

<p class="my-p"></p>
<script>
/**
* 目标:使用XMLHttpRequest对象与服务器通信
* 1. 创建 XMLHttpRequest 对象
* 2. 配置请求方法和请求 url 地址
* 3. 监听 loadend 事件,接收响应结果
* 4. 发起请求
*/
// 1. 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest()

// 2. 配置请求方法和请求 url 地址
xhr.open('GET', 'http://hmajax.itheima.net/api/province')

// 3. 监听 loadend 事件,接收响应结果
xhr.addEventListener('loadend', () => {
console.log(xhr.response)
const data = JSON.parse(xhr.response)
console.log(data.list.join('<br>'))
document.querySelector('.my-p').innerHTML = data.list.join('<br>')
})

// 4. 发起请求
xhr.send()
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//GET带参数

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XMLHttpRequest_查询参数</title>
</head>

<body>
<p class="city-p"></p>
<script>
/**
* 目标:使用XHR携带查询参数,展示某个省下属的城市列表
*/
const xhr = new XMLHttpRequest()

xhr.open('GET', 'http://hmajax.itheima.net/api/city?pname=辽宁省')

xhr.addEventListener('loadend', () => {
console.log(xhr.response)
const data = JSON.parse(xhr.response)
console.log(data)
document.querySelector('.city-p').innerHTML = data.list.join('<br>')
})

xhr.send()
</script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//POST
//多了个设置请求头xhr.setRequestHeader('Content-Type', 'application/json')
<button class="reg-btn">注册用户</button>
<script>
/**
* 目标:使用xhr进行数据提交-完成注册功能
*/
document.querySelector('.reg-btn').addEventListener('click', () => {

const xhr = new XMLHttpRequest()

xhr.open('POST', 'http://hmajax.itheima.net/api/register')

xhr.addEventListener('loadend', () => {
console.log(xhr.response)
})

// 设置请求头-告诉服务器内容类型(JSON字符串)
xhr.setRequestHeader('Content-Type', 'application/json')

// 准备提交的数据
const userObj = {
username: 'itheima007',
password: '7654321'
}
const userStr = JSON.stringify(userObj) //转为JSON字符串,才能作为请求体数据请求

// 设置请求体,发起请求
xhr.send(userStr)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//xhr结合promise
<p class="my-p"></p>
<script>
/**
* 目标:使用Promise管理XHR请求省份列表
* 1. 创建Promise对象
* 2. 执行XHR异步代码,获取省份列表
* 3. 关联成功或失败函数,做后续处理
*/
// 1. 创建Promise对象
const p = new Promise((resolve, reject) => {
// 2. 执行XHR异步代码,获取省份列表
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
// xhr如何判断响应成功还是失败的?
// 2xx开头的都是成功响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})

// 3. 关联成功或失败函数,做后续处理
p.then(result => {
console.log(result)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
}).catch(error => {
// 错误对象要用console.dir详细打印
console.dir(error)
// 服务器返回错误提示消息,插入到p标签显示
document.querySelector('.my-p').innerHTML = error.message
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//利用ajax和promis简易封装axios
<p class="my-p"></p>
<script>
/**
* 目标:封装_简易axios函数_获取省份列表
* 1. 定义myAxios函数,接收配置对象,返回Promise对象
* 2. 发起XHR请求,默认请求方法为GET
* 3. 调用成功/失败的处理程序
* 4. 使用myAxios函数,获取省份列表展示
*/
// 1. 定义myAxios函数,接收配置对象,返回Promise对象
function myAxios(config) {
return new Promise((resolve, reject) => {
// 2. 发起XHR请求,默认请求方法为GET
const xhr = new XMLHttpRequest()
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
// 3. 调用成功/失败的处理程序
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
}

// 4. 使用myAxios函数,获取省份列表展示
myAxios({
url: 'http://hmajax.itheima.net/api/province'
}).then(result => {
console.log(result)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
}).catch(error => {
console.log(error)
document.querySelector('.my-p').innerHTML = error.message
})
</script>

状态码

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

路由缓存问题

路由缓存问题

使用带有参数的路由时,路由只有参数变化,相同的组件实例将被复用,导致生命周期不会再被调用。如:当从/user/a导航到/user/b时,相同的组件实例将被复用。
因为2个路由都渲染同个组件,比起销毁再创建,复用显得更高效。但也意味着生命周期钩子不会被调用 。

该项目中点击header分类时,只进行了路由参数切换,但不会发生跳转。原因是进行了组件实例复用,没有调用生命周期钩子,故不跳转。

解决思路:

  1. 让组件实例不复用,强制销毁重建,易浪费。使用:key="$route.fullPath",为key添加完整路径
    此操作会导致整个页面重新加载所有数据,但banner所有分类页都一样,不需要重新加载,会造成资源浪费。
1
2
3
4
5
6
7
8
9
10
//Layout/index.vue
<template>
<LayoutFixed />
<LayoutNav />
<LayoutHeader />
<!-- 二级路由出口,如分类页 -->
<!-- 添加key 破坏复用机制 强制销毁重建 -->
<RouterView :key="$route.fullPath" />
<LayoutFooter />
</template>
  1. 监听路由变化,变化之后执行数据更新操作。可以减少网络请求,精细化处理路由跳转后的数据加 载
    在路由更新之前执行
    //路由一变化就监听到
    onBeforeRouteUpdate()
    以下操作只更新category数据而不更新banner数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//useCategory.js
//默认参数为route.params.id
const getcategoryData = async (id = route.params.id) => {
const res = await getCategoryAPI(id) //route.params.id:因为路由用占位符(params传参),所以用route.params.id获取当前路由id
categoryData.value = res.result
console.log("res", res)
}

onMounted(() => {
getcategoryData()
})

//目标:路由参数变化时,可以把分类数据接口重新发送getcategoryData
//to:目标路由对象
onBeforeRouteUpdate((to) => {
console.log('路由变化了')
//使用最新的路由参数请求最新的分类数据
getcategoryData(to.params.id)
})

无感登录实现思路

1.解决方案
无感登录实际上就是无感刷新 token。

1.在响应器中拦截,判断 token 返回过期后,调用刷新 token 的接口。

2.后端返回过期时间,前端判断 token 的过期时间,去调用刷新 token 的接口。

3.写一个定时器,定时刷新 token。

我们常用的第一种方法实现无感登录。

2.实现流程
1.登录流程

    用户输入用户名和密码进行登录。

    后端验证用户信息,如果验证通过,返回一个 token 和 refresh_token。
    前端将 token 和 refresh_token 保存在本地(例如 localStorage)。

2.拦截响应

    在前端,我们可以在响应拦截器中处理返回的响应。
    如果返回的状态码为401(未授权),说明 token 已经过期。

3.刷新 token

    使用本地保存的 refresh_token 来请求新的 token。
    请求时可以携带当前 token 的过期时间,以便后端可以更精确地控制新 token 的过期时间。

4.替换 token

    一旦新的 token 返回,前端将其保存,并替换本地旧的 token。

5.处理请求

    前端将新的 token 放入未完成的请求中,并重新发送请求。

6.错误处理

    如果 refresh_token 也过期了,那么需要重新登录。清除所有过期的 token,然后重新执行登录流程。

三种方法:

  1. 在响应器中拦截到401,判断token返回过期后,调用刷新token的接口

  2. 后端返回过期时间,前端判断token的过期时间,去调用刷新token的接口

  3. 前端写定时器,定时刷新token接口

    方法1流程:

    1. 登录成功后保存token 和 refresh_token
    2. 在响应拦截器中对401状态码引入刷新token的api方法调用
    3. 替换保存本地新的token
    4. 把错误对象里的token替换
    5. 再次发送未完成的请求
    6. 如果refresh_token过期了,判断是否过期,过期了就清除所有token重新登录
  • Copyrights © 2022-2024 CoffeeLin
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信