(九)前端优化细节和额外知识点

在这里学习到了常见的性能优化和如何将文件打包并且进行上传

性能优化包括以下部分

  1. 组件缓存
  2. 处理头像不在更新(缓存问题)
  3. 实现代码高亮
  4. loading效果
  5. 登录未遂地址
  6. 图片懒加载
  7. 自动聚焦问题
  8. 组件注册
  9. 从个人中心跳到滚动条位置
  10. 滚动条位置
  11. 持久化存储

额外的知识

  • 如何处理大数
  • 前端的数据格式

1 组件缓存

问题,当切换的时候,里面的数据每次切换切换都会刷新,形成卡顿

切换的时候,原先的数据不会完全刷新,结合 vue 内置的 keep-alive 组件,可以实现组件的状态保持。

  1. App.vue中的router-view外层套上一个keep-alive组件

    • 缓存的一级路由页面切换不被释放, 但是首页还是会重新请求数据
  2. Layout.vue中的router-view外层套上一个keep-alive组件

    • 这次Home和User页面都被缓存了(二级路由也要管)
      /App.vue文件
<template>
  <div id="app">
<keep-alive exclude="Search,ArticleDetail,Detail,UserEdit" >
   <router-view></router-view>
</keep-alive>
  </div>
</template>

但发现搜索页面详情页面多被缓存起来了 (多次进入不同的文章, 发现都是同一个文章详情)
对router-view使用exclude属性来区别, 哪些页面组件可以缓存

特别注意exclude里是组件的name名字(跟路由没什么关系)

<template>
  <div id="app">
<keep-alive exclude="Search,ArticleDetail,Detail,UserEdit" >
   <router-view></router-view>
</keep-alive>
  </div>
</template>

  • 只有被切换销毁的组件, 才需要被缓存
  • router-view是他们切换时的挂载点, 套在挂载点外来缓存内部组件

2 头像不更新问题

User.vue被缓存了, 所以改了头像回到User页面, created里获取用户资料接口不会执行

  • 解决方案1: 把created换成activated钩子函数即可

  • 解决方案2: UserEdit.vue修改头像成功后, 更新到vuex中, User页面使用的vuex数据也受到更新

    1. vuex中定义mutations修改头像
updatePhoto (state, photo) {
    state.user.photo = photo
    localStorage.setItem('user', JSON.stringify(state.user))
}

UserEdit.vue映射, 调用传成功头像覆盖

import { mapMutations } from 'vuex'
export default {
  // ...
  methods: {
    ...mapMutations(['updatePhoto']),
      
    async onFileChange (ev) {
      if (ev.target.files.length === 0) return
      const fd = new FormData()
      fd.append('photo', ev.target.files[0]) 
      const res = await updatePhotoAPI(fd)
      console.log(res)
      this.profile.photo = res.data.data.photo
      this.updatePhoto(res.data.data.photo) // 同步头像给vuex
    }
  }
}

引申 - 用户名字修改也是同理的

  • vuex中定义修改名字的mutations
updateName (state, theName) {
    state.user.name = theName
    localStorage.setItem('user', JSON.stringify(state.user))
}
    • UserEdit.vue页面, 修改名字成功调用更新

      import { mapMutations } from 'vuex'
      export default {
        // ...
        methods: {
          // ...
          ...mapMutations(['updatePhoto', 'updateName']), // 映射updateName方法
          async onNameDialogBeforeClose (action, done) {
            if (action === 'confirm') {
              if (/^[A-Za-z0-9\u4e00-\u9fa5]{1,7}$/.test(this.userName)) {
                await updateProfileAPI({
                  userName: this.userName
                })
                this.profile.name = this.userName
                this.updateName(this.userName) // 同步到vuex中
                done()
              }
            }
          }
        }
      }
      

3、 文章高亮显示

文章详情页里代码片段高亮效果
有两种方法

  1. 想要让代码高亮, 必须在后台发布文章时, 就要把代码分段用pre+code标签包裹
    在这里插入图片描述
  2. 前端可以通过获取这些标签名/指定类名, 分别给予相应样式

步骤

基于 highlight.js 美化详情页的代码片段

英文文档(更全): https://highlightjs.readthedocs.io/en/latest/index.html

中文文档: https://fenxianglu.cn/highlight.html

  1. 下载此插件到项目中

    一定要带.js名字

    yarn add highlight.js -D
    
  2. 在main.js 引入即可

    import hljs from 'highlight.js' // hljs对象
    import 'highlight.js/styles/default.css' // 代码高亮的样式
    
  3. 注册高亮代码-自定义指令

    Vue.directive('highlight', function (el) { // 自定义一个代码高亮指令
        const highlight = el.querySelectorAll('pre, code') // 获取里面所有pre或者code标签
        highlight.forEach((block) => {
            hljs.highlightElement(block) // 突出显示这些标签(以及内部代码, 会自动识别语言)
        })
    })
    

4 、loading功能

通过loading这个组件加载,给他垂直居中,如果需要距离上面有距离就添加padding-top

5. 登陆未遂跳转地址

如果refesh-token过期了,就重新跳回登录页面,但登录之后重新点赞
1.如果点赞了,token过期了,并且refesh-token也过期了,那么就跳转到登录页面
通过router.currentRoute.fullPath

  if (error.response.status === 401) { // 身份过期
  // 方式2: 使用refresh_token换回新的token再继续使用, JS代码实现, 用户无感知(效果好)
    const res = await getNewTokenAPI()
    setToken(res.data.data.token)
    } else if (error.response.status === 500 && error.config.url === '/v1_0/authorizations' && error.config.method === 'put') {
            // 刷新的refresh_token也过期了
            localStorage.clear() // 清除localStorage里所有值
            // localStorage当前网页, 域名划分, 每个域名下有自己范围的localStorage
          Toast('请重新登录');
      console.log(router.currentRoute.fullPath);
      router.push({ path: `/login?path=${router.currentRoute.fullPath}` })
        Notify({ type: 'warning', message: '身份已过期' })
      }
  
  return Promise.reject(error)
})
  1. 在Login/index.vue, 登录后, 判断有未遂地址, 跳这里, 否则去/路径

6、图片懒加载

  • 图片标签进入视口才加载图片
  • 图片src会调用浏览器请求图片资源

在这里通过组件库来实现

  1. vant组件库里有个叫LazyLoad指令, 在main.js中全局注册
import { Lazyload } from 'vant';

Vue.use(Lazyload);
  1. 先查看首页, 上来默认请求了多少张图片 / 点击别的频道查看

    注意: 有的频道图片本身就不多

  2. 根据文档指示, 在src/components/ArticleItem.vue中, 把所有img的src换成v-lazy指令即可
    注意: 有的频道图片本身就不多

  3. 根据文档指示, 在src/components/ArticleItem.vue中, 把所有img的src换成v-lazy指令即可

<!-- 标题区域的插槽 -->
<template #title>
<div class="title-box">
    <!-- 标题 -->
    <span>{{ obj.title }}</span>
    <!-- 单图 -->
    <img
         class="thumb"
         v-lazy="obj.cover.images[0]"
         v-if="obj.cover.type === 1"
         />
</div>
<!-- 三张图片 -->
<div class="thumb-box" v-if="obj.cover.type > 1">
    <img
         class="thumb"
         v-for="(imgUrl, index) in obj.cover.images"
         :key="index"
         v-lazy="imgUrl"
         />
</div>
</template>

7自动优化聚焦问题

  • 用户名修改弹窗多次点击
  • 只有第一次自动聚焦问题
  1. 自动聚焦依赖自定义指令inserted执行

    而Dialog只有第一次出现是插入到真实DOM, 触发inserted方法

    而Dialog以后初选是css层面的显示出现, 不会触发inserted方法

  2. 解决方案

    给自定义指令添加update方法, 指定所在DOM更新时执行

import Vue from 'vue'
// 插件对象(必须有install方法, 才可以注入到Vue.use中)
export default {
  install () {
    Vue.directive('fofo', {
      inserted (el) {
        fn(el)
      },
      update (el) {
        fn(el)
      }
    })
  }
}
function fn (el) {
  if (el.nodeName === 'INPUT' || el.nodeName === 'TEXTAREA') {
    // 如果直接是input标签/textarea标签
    el.focus()
  } else {
    // 指令在van-search组件身上, 获取的是组件根标签div, 而input在标签内
    const inp = el.querySelector('input')
    const textArea = el.querySelector('textarea')
    // 如果找到了
    if (inp || textArea) {
      inp && inp.focus()
      textArea && textArea.focus()
    } else {
      // 本身也不是, 子标签里也没有
      console.error('请把v-fofo用在输入框标签上')
    }
  }
}

8 抽离组件

将组件抽离然后再导入

import Vue from 'vue'
import { NavBar, Form, Field, Button, Tabbar, TabbarItem, Icon, Tab, Tabs, Cell, List, PullRefresh, ActionSheet, Popup, Row, Col, Badge, Search, Divider, Tag, CellGroup, Image, Dialog, DatetimePicker, Loading, Lazyload } from 'vant'
Vue.use(Lazyload)

Vue.use(Loading)
Vue.use(DatetimePicker)
Vue.use(Dialog)
Vue.use(Image)
Vue.use(CellGroup)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(Search)
Vue.use(Badge)
Vue.use(Row)
Vue.use(Col)
Vue.use(Popup)
Vue.use(ActionSheet)
Vue.use(PullRefresh)
Vue.use(List)
Vue.use(Cell)
Vue.use(Tab)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(Form)
Vue.use(Field)
Vue.use(Button)
Vue.use(NavBar)
  1. 在main.js引入一下, 让代码执行

    import './VantRegister'
    
  2. 总结下模块化的3种使用方式

  • 按需导出和导入
  • 默认导出和导入
  • 无导出和导入

步骤

keep-alive不会缓存滚动条的位置的

  1. 路由对象/home, 上添加meta额外信息
meta: { isRecord: true, top: 0 } // isRecord是否需要设置滚动位置
  1. 给Home设置组件内守卫

    注意: 此方法与methods并列

// 路由离开 - 触发(保存滚动位置)
beforeRouteLeave (to, from, next) {
    from.meta.top = window.scrollY
    next()
}
  1. 在全局后置钩子设置页面滚动条
// 全局后置钩子
router.afterEach((to, from) => {
  // 如果当前的路由的元信息中,isRecord 的值为 true
  if (to.meta.isRecord) {
    setTimeout(() => {
      // 则把元信息中的 top 值设为滚动条纵向滚动的位置
      window.scrollTo(0, to.meta.top)
    }, 0)
  }
})

10、频道滚动条位置

  1. 明确数据结构, 在Home/index.vue定义变量

    // “频道名称”和“滚动条位置”之间的对应关系,格式 { '推荐ID': 211, 'htmlID': 30, '开发者资讯ID': 890 }
    const nameToTop = {}
    
  2. tabs标签绑定before-change事件和方法实现

    向nameToTop添加属性和位置, 缓存起来

    <van-tabs v-model="channelId" animated sticky offset-top="1.226667rem" :before-change="tabBeforeChangeFn">
        
    <script>
    methods: {
        // 频道切换之前触发
        tabsBeforeChangeFn () {
          nameToTop[this.channelId] = window.scrollY // 先保存要被切走频道的滚动距离(一定要用哦this.channelId里存着的)
          // 只有return true才会让tabs切换
          return true
        }
    }
    </script>
    
  3. 监测tabs切换后, 从nameToTop对象里拿到原本滚动位置设置

    <van-tabs v-model="channelId" animated sticky offset-top="1.226667rem"  :before-change="tabsBeforeChangeFn" @change="tabsChangeFn">
        
    <script>
    methods: {
        // 频道切换后
        tabsChangeFn (channelId) {
          // 等 DOM 更新完毕之后,根据记录的"滚动条位置",调用 window.scrollTo() 方法进行滚动
          this.$nextTick(() => {
            window.scrollTo(0, nameToTop[channelId] || 0)
          })
        }
    }
    </script>
    

11、统一封装LcalStorage

// 本地存储方式
// 如果同时有sessionStorage和localStorage, 可以封装2份
// 现在我只封装一种统一的方式
export const setStorage = (key, value) => {
  localStorage.setItem(key, value)
}
export const getStorage = (key) => {
  return localStorage.getItem(key)
}
export const removeStorage = (key) => {
  localStorage.removeItem(key)
}
export const clearStorage = () => {
  localStorage.clear()
}
  1. 把所有使用本地存储的地方, 都统一换成这里定义的方法
  • 在store/index.js - vuex中使用过
  • 在search/index.vue - 搜索页面使用过

12 封装统一的Notfy接口

方便统一,万一以后的通知框要统一更换

步骤

  1. 新建utils/Notify.js文件

    // 基于vant组件库, UI层弹窗封装
    // Notify函数
    // import { Notify } from 'vant'
    import { Toast } from 'vant'
    
    export const MyNotify = ({ type, message }) => {
    //   Notify({
    //     type: type,
    //     message: message
    //   })
    
      if (type === 'warning') {
        Toast({
          type: 'fail',
          message
        })
      } else if (type === 'success') {
        Toast({
          type,
          message
        })
      }
    }
    
    

    演示登录页面Login.vue提示框, 别的页面也是这样用

import { MyNotify } from '@/utils/Notify'

methods: {
    ...mapActions(['asyncLoginAction']),
    async onSubmit () {
      try {
        await this.asyncLoginAction(this.formLogin)
        MyNotify({ type: 'success', message: '登录成功' })
        // 跳转到Layout页面
        this.$router.replace({
          path: this.$route.query.path || '/layout'
        })
      } catch (err) {
        MyNotify({ type: 'warning', message: '手机号或密码错误' })
      }
    }
  }

这样可以方便的统一替换

额外知识点

1、Blight解决精度丢失的问题

  1. 定义后台返回数据, 模拟大数

    后台数据库id, 生成算法是19位置

    const str = '[{"id": 1302900300041101987}, {"id": 1205340366642205763}, {"id": 7689021398237123422}]'
    
  2. 尝试用JSON.parse转换, 发现转换后的值不对

    后面3位精度错误

    console.log(JSON.parse(str))
    
  3. 原因: JS范围的安全数打印

    console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
    

这里可以通过Blight来解决精度丢失的问题

2、知识点-前端传参格式

请求头内容类型

只有请求里有传递的值给后台, 才会出现Content-Type

字段: Content-Type值

作用: 告诉后端你传递请求体内容的类型, 后端用对应方式解析 (如果设置错了可能导致后台收不到请求体数据)

Content-Type值请求体值解释
application/json;charset=UTF-8{“target”:“8206”,“content”:“12123”}JSON字符串
multipart/form-data; boundary=----WebKitFormBoundaryDIG2g3sYegVy7GmH------WebKitFormBoundaryDIG2g3sYegVy7GmH Content-Disposition: form-data; name=“photo”; filename=“Koala.jpg” Content-Type: image/jpeg ------WebKitFormBoundaryDIG2g3sYegVy7GmH–表单对象
application/x-www-form-urlencodedkey=value&key=value&key=value查询字符串
text/plainnihao啊后台

通过F12可以查看编码格式
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值