2 Star 11 Fork 1

ifer/vue3_71

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
contribute
Sync branch
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README

0. 脑图链接

Vue3 day01: https://www.processon.com/view/link/6246d3ec0e3e74078d609f46

1. 如何介绍一个项目

项目背景、功能、技术栈、我负责的模块(心里面一定要准备碰到的问题以及解决方案)

2. 如何封装组件的

编辑/添加,公告头部的封装,每个页面都会用到的面包屑...考虑过哪些技术点:传值和校验、插槽和作用域插槽、自定义事件

3. 登录怎么做的

收集数据 => 前端进行校验 => 通过 Axios 提交到后端 => 后端校验成功后返回 Token 到前端 => 前端拿到 Token 后做了 2 件事件(存储到 Vuex 并持久化到本地)

有权限的接口会通过请求拦截器给请求头统一携带 Token。

界面访问控制:在路由全局前置导航守卫通过判断 Token 去实现的。

Token:过期...

4. APP

Web APP、可以安装的应用程序(Android、IOS、Android/IOS 提供 Webview + 套(网页))、uni-app 可以把网页打包成可以安装的应用程序

5. Git 工作流

Gitlab 公司内部私有部署的代码管理平台。

master => 打包上线

release => 测试分支

develop => 开发分支(普通开发者具有权限的分支)

功能分支 => 一般我在开发新功能的时候,会基于 develop 开一个自己的功能分支,在上面写代码,写完之后再合并到 develop

6. 这个项目接口请求是怎么做的/你对 axios 封装过哪些东西

首先我们会划分三个模块:utils/request.jsapi/*组件.vue

一般封装 axios 的时候会封装:首先创建一个 axios 实例、baseURL、timeout、请求拦截器(统一携带 Token、Token 过期时候的前端主动介入)、响应拦截器(成功的时候对数据进行脱壳、失败的时候统一错误提示、Token 过期处理)、transformResponse

Token 过期时候的前端主动介入:登录成功存一个时间戳、请求拦截器中,用当前时间戳 - 登录成功时候的那个时间戳,如果大于超时时间了,就直接拦截到登录页。

7. 每次发请求的时候,希望全局开启一个进度条,请求完毕关闭进度条

问题:在那做,做什么?

请求拦截器里面开启进度条,响应拦截器关闭进度条。

8. SEO

对外的,一般都要考虑 SEO,像商城网站(SSR、Nuxt.js)。

对内的,不需要,像后台管理系统。

9. CDN

内容分发网络:公司花钱 => 七牛 CDN => 买空间 => 把自己的资源上传到此空间 => 得到 CDN

10. 和 Vue 相关的性能优化的手段都考虑过哪些

  • v-for 循环的时候加 key

  • v-for 和 v-if 不要放一行(计算属性)

  • v-if 和 v-show 区分使用场景

  • 路由懒加载、组件动态加载

  • KeepAlive

  • computed 和 方法区分使用场景(优先 computed)

  • ...

11. Websocket

轮询或 Websocket

12. 你们公司接口文档是怎么交付的

SwaggerUI:一套接口生成的集成方案,是后端生成接口文档的框架,一般后端会部署一个地址,提供给我。

13. 开发项目的时候,有没有碰到过数据明明变了,但是视图没有变,这种情况?

  • 什么情况?

Vue2 有 2 种情况修改数据不是响应式的:给对象不存在的 key 进行赋值;通过索引修改数组的内容;

const obj = {}
obj.age = 18
const arr = ['a', 'b', 'c']
arr[0] = 'd'
  • 为什么?

给对象不存在的 key 进行赋值不是响应式的为什么?

由于 Vue2 是通过 Object.defineProperty 递归劫持的 data 里面的每一个【属性】,一上来就进行了递归劫持的操作,所以后续添加的属性当然就没有被劫持到!

通过索引修改数组的内容为什么不是响应式的?

性能!issue

  • 怎么解决的?
// const obj = {}
// obj.age = 18
// 解决
this.$set(this.obj, 'age', 19)
Vue.set(this.obj, 'age', 19)
// const arr = ['a', 'b', 'c']
// arr[0] = 'd'
// 解决
this.$set(this.arr, 0, 888)
// 使用数组的变更方法
this.arr.splice(0, 1, 888)
  • Vue3 呢?

Vue3 对数组通过索引修改就是响应式的,因为 Proxy 对数组的处理不存在性能问题!

Vue3 给对象不存在的 key 进行赋值也是响应式的啦,因为 Proxy 它劫持的直接就是整个对象(而不是一个个的属性),所以就无所谓你这个属性是不是后续添加的啦!

14. 项目开发的流程是怎样的

15. 小程序和网页开发的差异

  • 宿主环境不一样(网页=>浏览器,小程序=>微信软件)

  • 开发方式不一样(微信开发者工具)

  • 上线流程不一样

16. 上传图片怎么做的

  1. 准备一个 <input type="file" hidden> 并通过 hidden 属性隐藏

  2. 准备一个按钮,给按钮绑定点击事件,在事件的回调里面主动触发 input:file 的 click 事件

  3. 监听 input 的 onchange 事件,在回调里面通过 e.target.files 拿到文件信息

  4. 创建 formData 对象,把文件信息放进去通过 axios 传到后端

const form = new FormData()
form.append('avatar', e.target.files[0])
  1. 图片预览:URL.createObjectURL(Blob) 或 FileReader(base64)
<input type="file" hidden id="file" />
<img id="img" alt="" />
<button id="btn">click</button>
<script>
  btn.onclick = function () {
    file.click()
  }
  file.onchange = function (e) {
    const f = e.target.files[0]

    const formData = new FormData()
    formData.append('avatar', f)
    // axios({ url: '/xxx', data: formData })
    /* const s = URL.createObjectURL(f)
       img.src = s */
    const fileReader = new FileReader()
    // 图片 => base64
    fileReader.readAsDataURL(f)
    fileReader.onload = function () {
      img.src = this.result
    }
  }
</script>

17. 后端接口还没有开发好,作为前端你是怎么进行下去的

Mock: 模拟数据;拦截请求;

// mock/index.js
Mock.mock('/api/users', 'get', (req, res) => {
  // 通过 req 拿到前端的信息
  // 根据此信息返回对应的数据(Mock 的数据)
  res.send({ mock 的数据 })
})
// main.js
import './mock'

18. 组件传值

父传子

1、父传子的第一种方法

父组件通过自定义属性传递,子组件通过 props 接收?

接收到的数据可以改吗?简单数据类型不能改,复杂数据类型引用不能改内容可以改,但是即便如此,也不建议直接改,为什么?

单项数据流思想:如果任何一个地方都可以修改数据,应用复杂出现错误的时候,意味着这个错误可能是任何一个地方导致的,不方便追溯!

怎么处理比较好?数据在哪来的就在哪改!

2、父传子的第二种方式

父亲通过 ref 获取子组件实例,调用此实例的方法的同时并传递参数,儿子方法中接收到参数做对应的修改!

3、父传子的第三种方式

this.$children[0].changeAge(this.age)

4、父传子的第四种方式

// 非 props 属性
this.$attrs

子传父

1、儿子通过 $emit 触发父亲自定义事件的同时并传递数据,父亲监听自定义事件的同时在回调里面进行数据修改的操作

2、this.$parent 拿到父组件实例的同时,调用方法并传参

3、例如父传子,只不过传递的是一个方法,子组件调用这个方法的同时并传递参数

4、通过 this.$listeners 拿到父亲的自定义事件,调用并传参

兄弟

1、状态(数据)提升

A 修改 B,把 B 中的数据提升到公共的父组件里面,A 通过子传父修改父亲的数据,父亲通过父传子传递把数据传递到 B

2、EventBus(事件中心、发布订阅)

Vue3 中 EventBus 被废弃了,你觉得为什么?

太过于灵活,大型项目不太方便追溯问题;由于 EventBus 本身实现起来足够简单,Vue3 处于自身体积更小的考虑,所以被废弃了!

如果我还想再用怎么办?官方推荐了两个包,例如 mitttiny-emitter 或者自己实现一个

export default class EventBus {
  constructor() {
    this.subs = {}
  }
  // 订阅 => 订阅的是一个回调函数
  on(eventType, callback) {
    this.subs[eventType]
      ? this.subs[eventType].push(callback)
      : (this.subs[eventType] = [callback])
  }
  // 发布 => 发布事件让其(事件)对应的回到函数执行
  emit(eventType, ...args) {
    this.subs[eventType].forEach((callback) => callback(...args))
  }
}

全局的数据通信和共享(Vuex)

回答 Vuex 分两方面:配置项;触发流程;

mutation 里面可以放异步吗?

非严格模式确实是可以放异步,代码也可以正常执行!严格模式下不能这样操作(写异步),会有警告。

不建议放异步代码,目的是为了形成数据快照(拿到当时的那个数据状态),为了配合 DevTools 调试。

19. 说一下权限管理怎么做的

路由级别的权限

用户登录成功之后,后端返回当前用户的标识;['/user', '/news']

前端拿到标识之后筛选出【有权限的路由】;[{path: '/user', component: User}, {path: '/news', component: News}, ],#1

然后做了 2 件事情!

第 1 件事情:通过 addRoutes/addRoute 把【有权限的路由】添加到路由实例;router.addRoutes(#1),这样当前用户就具有了访问某个路由的权限啦;

第 2 件事情:把【有权限的路由】也添加到了 Vuex 一份,目的是为了给侧边栏或其他地方使用(通过 addRoutes 后续添加的路由,不能直接通过 router 获取到,官方推荐放到 Vuex 存储);

按钮级别的权限

封装一个全局的方法/指令,这个方法呢?只做一件事件,接收一个标识,内部进行判断,看一下这个标识这不在后端返回的功能列表里面,在就返回 true,不再就返回 false。

function isFn(flag) {
  return ['a', 'b', 'c', 'DELETE'].includes(flag)
}

在需要做按钮权限控制的时候,调用这个方法,根据返回的是 true 还是 false,对这个按钮做禁用/启用或显示/隐藏的操作。

<button v-if="isFn('DELETE')">删除</button>

20. 继承

// #1 Call 式继承 / 构造函数继承:继承的是属性
Person.call(this, name, age)
// #2 原型继承:继承的是方法
Star.prototype = new Person()
Star.prototype.constructor = Star
// 组合继承 = 构造函数继承 + 原型继承

不过,现在呢,我更喜欢使用 ES6 的 extends...

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  say() {
    console.log('!!!')
  }
}
class Star extends Person {}
const s = new Star('尼古拉斯', 40)
console.log(s.name, s.age)
s.say()

extends

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  say() {
    console.log('!!!')
  }
}
class Star extends Person {
  // 儿子有 constructor 一定要有 super,有 constructor 的目的是为了添加 Star 自己的属性
  constructor(name, age, money) {
    super(name, age)
    this.money = money
  }
}
const s = new Star('尼古拉斯', 40, 1000)
console.log(s.name, s.age, s.money)
s.say()

深拷贝

  1. JSON.stringify 的问题
// ..........JSON.stringify 的确点............

// 1. 会忽略值为 undefined、函数、Symbol
// 2. 当值为 bigint 或循环引用的时候会报错
// 3. 日期会被转成字符串、正则会被转成空对象
// 4. ...
  1. 三个点和 Object.assign 都是浅拷贝
它们都是用来进行浅拷贝的好的地方是能拷贝 undefined函数Symbol正则...
  1. 自己实现深拷贝:递归浅拷贝
// const copy = (target) => {
//   const type = Object.prototype.toString.call(target)
//   // 正则、日期
//   if (/(regexp|date)/i.test(type)) return new target.constructor(target)
//   // 错误对象
//   if (/error/i.test(type)) return new target.constructor(target.message)
//   // 函数
//   if (/function/i.test(type)) return new Function('return ' + target.toString())()
//   // null 和 简单数据类型
//   if (target === null || typeof target !== 'object') return target
//   // 数组和对象
//   /* const arr = []
//   const obj = {} */
//   const result = new target.constructor()
//   for (const attr in target) {
//     result[attr] = copy(target[attr])
//   }
//   return result
// }
  1. 如何循环引用
// const copy = (target, m = new Map()) => {
//   const type = Object.prototype.toString.call(target)
//   // 正则、日期
//   if (/(regexp|date)/i.test(type)) return new target.constructor(target)
//   // 错误对象
//   if (/error/i.test(type)) return new target.constructor(target.message)
//   // 函数
//   if (/function/i.test(type)) return new Function('return ' + target.toString())()
//   // null 和 简单数据类型
//   if (target === null || typeof target !== 'object') return target
//   // 数组和对象
//   /* const arr = []
//   const obj = {} */
//   // #2 m 里面存储了 target 就直接返回
//   // console.log(target, 233)

//   if (m.get(target)) return m.get(target)
//   const result = new target.constructor()
//   // #3
//   m.set(target, result)

//   for (const attr in target) {
//     // #4 传递 m
//     result[attr] = copy(target[attr], m)
//   }
//   return result
// };
  1. 实际我怎么做的
const o = _.cloneDeep(obj1)

防抖和节流

// 是什么?
// 防抖和节流都是性能优化的一种手段。
// 防抖:持续触发不执行,不触发的一段时间后才执行。
// 节流:持续触发也执行,只不过,执行的频率变低了。
// 实现一个?
// 匈牙利命名法的简版
// 类型 + 具体的含义
// const iNum = 8
// const bBar = false
// const aDiv = document.querySelctorAll('div')
// 把鼠标相对于盒子位置放到盒子里面
/*  oDiv.onmousemove = function (e) {
       let x = e.pageX - this.offsetLeft
       let t = e.pageY - this.offsetTop
       this.innerHTML = `x: ${x}, y: ${t}`
     }; */

// 防抖一下
/* let timer = null
    oDiv.onmousemove = function (e) {
      clearTimeout(timer)
      timer = setTimeout(() => {
        let x = e.pageX - this.offsetLeft
        let t = e.pageY - this.offsetTop
        this.innerHTML = `x: ${x}, y: ${t}`
      }, 200)
    }; */
// 封装一个防抖/节流函数?
/* const debounce = (callback, time) => {
      let timer = null
      return function (e) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          callback.call(this, e) // window.callback(e)
        }, time)
      }
    } */

/* oDiv.onmousemove = _.debounce(function (e) {
      let x = e.pageX - this.offsetLeft
      let t = e.pageY - this.offsetTop
      this.innerHTML = `x: ${x}, y: ${t}`
    }, 200) */
// 生活中的例子?
// 王者荣耀英雄回城是防抖还是节流
// 打散弹枪,节流

// 应用场景?
// 根据输入的内容请求接口?防抖
// 获取窗口缩放的大小,滚动位置?节流

// 实际开发怎么做?
oDiv.onmousemove = _.throttle(function (e) {
  let x = e.pageX - this.offsetLeft
  let t = e.pageY - this.offsetTop
  this.innerHTML = `x: ${x}, y: ${t}`
}, 1000)

原型链

定义:多个对象之间通过 __proto__ 链接起来的这种关系就是原型链。

Promise

.catch() 之后还能再触发 then 吗?

const p = new Promise((resolve, reject) => {
  reject(new Error('233'))
})
p.catch((r) => {
  console.log(r)
  // return Promise.resolve(undefined)
}).then((r) => {
  console.log(r)
})

如何实现并发请求,按顺序拿到结果?

Promise.all([p1, p2]).then((r) => {
  // r => [p1 的结果, p2 的结果]
})

// const p1 的结果 = await p1
// const p2 的结果 = await p2

Vue2 双向数据绑定原理

// 需求:双向数据绑定(数据变了,视图改变;视图改变,数据也变)

const data = {
  name: 'ifer'
}

/* const tempData = { ...data }
    Object.defineProperty(data, 'name', {
      get() {
        // 获取 name 的时候会走这儿
        return tempData.name
      },
      set(newValue) {
        // 设置 name 的时候会走这儿
        tempData.name = newValue
      }
    })

    // console.log(data.name) // 'ifer'
    data.name = 'elser'
    console.log(data.name) */

/* const tempData = { ...data }
    Object.defineProperty(data, 'name', {
      get() {
        // 获取 name 的时候会走这儿
        return tempData.name
      },
      set(newValue) {
        // model => view => Data bindings
        // #2 设置 name 的时候会走这儿
        tempData.name = newValue
        oP.innerHTML = newValue
        oInput.value = newValue
      }
    })

    // view => model => DOM Listeners
    oInput.oninput = function (e) {
      // #1
      data.name = e.target.value
    } */

/* oInput.oninput = function (e) {
      oP.innerHTML = e.target.value
    } */

Vue3

<!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>Document</title>
  </head>

  <body>
    <p id="oP"></p>
    <input id="oInput" type="text" />
    <script>
      // 需求:双向数据绑定(数据变了,视图改变;视图改变,数据也变)

      const data = {
        name: 'ifer',
        address: '北京'
      }

      /* const tempData = { ...data }
    Object.keys(data).forEach(key => {
      Object.defineProperty(data, key, {
        get() {
          return tempData[key]
        },
        set(newValue) {
          tempData[key] = newValue
          oP.innerHTML = newValue
          oInput.value = newValue
        }
      })
    })
    oInput.oninput = function (e) {
      data.address = e.target.value
    } */

      // 上面 Vue2 的问题:如果 data 里面有 100 个属性,那可不是要循环 100 次吗?

      // p 就是一个代理,我对 p 的任何操作都会影响到 data
      const p = new Proxy(data, {
        get(target, attr) {
          // 获取的时候触发
          // console.log(target, attr)
          // target => data
          // attr => 属性
          return target[attr]
        },
        set(target, attr, newValue) {
          target[attr] = newValue
          oP.innerHTML = newValue
          oInput.value = newValue
        }
      })
      // p.name = 'xxx'
      // console.log(data.name);

      /* p.name = 'xxx'
    console.log(p.name) */

      oInput.oninput = function (e) {
        // p.address = e.target.value
        p.test = e.target.value
      }
    </script>
  </body>
</html>

插件

Vue.use({
  install(Vue) {
    // 干了哪些事情
    Vue.mixin()
    Vue.component()
    Vue.directive()
    Vue.prototype.bus = new Vue()
  }
})
Vue.use(function (Vue) {})

难点

  1. 路由的缓存问题。

点击 用户 1 按钮 和 用户 2 按钮 共用了同一个路由组件 User,我在 User 里面根据 userId 发请求的时候,发现永远是第一次发起的那个请求,好奇怪,当时找不到原因。

template 里面获取数据是实时更新的,但是 created 拿不到最新的数据,后来发现原因是公用了同一个组件,而 created 这个钩子也只会在组件创建完毕执行一次,所以 ...

解决方式 1

<template>
  <div>User {{ $route.params.id }}</div>
</template>

<script>
  export default {
    name: 'User',
    created() {
      console.log(this.$route.params.id)
    },
    watch: {
      $route(newRoute) {
        console.log(newRoute.params.id)
      }
    }
  }
</script>

解决方式 2

<router-view :key="$route.fullPath"></router-view>
  1. 大数字问题。

根据文章 ID 请求文章详情的时候,有的时候正常,有的时候会出现 404,找不到详情,反复检查了传参和接口都没有问题,百思不得其解...

原因,后端给我返回的文章列表,当文章 ID 包含大数字的时候出事了。

{"name":"xxx", "age": 18}

本质上来说,后端返回的数据都是 JSON 格式的字符串,那么前端使用为什么可以直接当做对象去用呢?

就是因为 axios 帮我们进行了内部处理,它为了方便我们前端使用,内部进行了 JSON.parse 的操作转成了对象。

但是!!!当 JSON 格式的字符串里面包含了大数字的时候,JSON.parse 就搞不定了(转换出来的结果不对)

所以也就是当后端返回的 JSON 格式的字符串里面包含大数字的时候,axios 进行内部 JSON.parse 的时候,把那个大数字(文章 ID)转换成了另外一个结果,所以 404...

JSON.parse('{ "ID": 9007199254740999 }')

怎么解决?2 种方法。

找后端,那个 ID 不要用数字表示,用字符串表示文章 ID。

JSON.parse('{ "ID": "90071992547409998888888888888888" }')

自己搞定

import jsonBigInt from 'json-bigint'
// transformResponse
axios.create({
  transformResponse(data) {
    data => JSON 格式的字符串
    // 默认
    // return JSON.parse(data)
    // 内部用一种算法,如果是大数字转出来的是一个对象,这个对象里面的信息经过运算可以得到大数字
    return jsonBigInt.parse(data)
  }
})

Empty file

About

Vue3 71 期上课笔记 expand collapse
Vue and 4 more languages
Cancel

Releases

No release

Contributors

All

Activities

Load More
can not load any more
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/ifercarly/v3_71.git
git@gitee.com:ifercarly/v3_71.git
ifercarly
v3_71
vue3_71
master

Search

344bd9b3 5694891 D2dac590 5694891