前端工作笔记(四)

前端工作笔记(四)

Vue 框架 前端笔记

webpack的使用

webpack(模块打包工具):一个第三方模块包,用于分析,压缩并打包代码

(一)使用步骤
  1. 初始化包环境(生成 package.json 文件):npm init
  2. 安装依赖包(注意是以开发依赖安装):npm i webpack webpack-cli -D
  3. 在 package.json 文件中的 scripts 选项配置自定义命令:"scripts": { "自定义命令如 w": "webpack" }
  4. 新建 src 文件夹,生成 index.js 文件(默认 src 文件夹下的 index.js 为入口文件,可通过修改配置文件,更改入口文件路径及文件名)
  5. 使用 npm run w 执行打包命令(默认打包到 dist 文件夹下,可通过修改配置文件,更改出口文件路径及文件名)
(二)修改配置文件

与 src 文件夹同级,新建 webpack.config.js,在 webpack.config.js 中写入以下代码:

注意:使用如下配置前,先安装以下包(注意是以开发依赖安装)

  1. 使用加载器让 webpack 可以处理 css 文件:npm i css-loader style-loader -D
  2. 使用加载器让 webpack 可以处理 less 文件:npm i less less-loader -D
  3. 使用自动生成 html 的插件(配置文件中需导入):npm i html-webpack-plugin -D
  4. 使用降级处理高版本 js 语法的插件:npm i babel-loader @babel/core @babel/preset-env -D
// 引入路径模块const path = require("path");// 引入自动生成 html 的插件const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {  entry: "./src/main.js", // 可更改入口文件路径及文件名  output: {    path: path.join(__dirname, "dist"), // 可更改出口文件夹路径及文件夹名    filename: "bundle.js", // 可更改出口文件名  },  plugins: [    new HtmlWebpackPlugin({      template: "./public/index.html", // 以此路径下的html模板为基准,生成打包压缩后的html文件放在dist文件夹内      inject: "body", //始终让script标签在body的下面    }),  ],  module: {    rules: [// loader(加载器)的规则        // css加载器      {        test: /\.css$/, // 匹配所有的css文件        // 顺序不能颠倒,必须从右往左,先用 css-loader 让webpack能够识别 css 文件的内容并打包,再用 style-loader 将样式, 把css插入到dom中        use: ["style-loader", "css-loader"],      },        //less加载器      {        test: /\.less$/,        // 使用less-loader, 让webpack处理less文件, 内置还会用less翻译less代码成css内容        use: ["style-loader", "css-loader", "less-loader"],      },        // webpack5内置加载图片功能,直接配置规则即可      {        test: /\.(png|jpg|gif|jpeg)$/i,        type: "asset",      },        // webpack5内置加载字体图标功能,直接配置规则即可      {        test: /\.(eot|svg|ttf|woff|woff2)$/,        type: "asset/resource",        generator: {          filename: "font/[name].[hash:6][ext]",        },      },        //处理高版本js语法      {        test: /\.js$/,        exclude: /(node_modules|bower_components)/,        use: {          loader: "babel-loader",          options: {            presets: ["@babel/preset-env"], // 预设:转码规则(用babel开发环境本来预设的)          },        },      },    ],  },};
(三)webpack 开发服务

又称为本地热更新开发服务器,使得第一次打包后,当启动了开发服务器,每次更新代码,会自动打包在内存中(非打包在 dist 文件夹中),浏览器实时更新

  1. 下载依赖包:npm i webpack-dev-server -D
  2. 在 package.json 文件中的 scripts 选项中配置自定义命令:"scripts": { "serve": "webpack serve" }
  3. 启动开发服务器:npm run serve

解决 mode 问题:在 webpack.config.js 配置文件中的 module.exports 中新增一行代码,即 module.exports = { mode: "development" , 其余代码 }

注意:修改启动端口需要在 webpack.config.js 配置文件中的 module.exports 中新增如下代码:

module.exports = {  devServer: {    port: 3000, //配置服务器端口号    open: true, //浏览器自动打开  },    //其余代码};

Vue 框架

Vue 是一个JavaScript渐进式框架(需要哪个组件就使用哪个组件),采取了 MVVM 设计模式推荐使用工程化开发 Vue

补充知识:在 Vue 中,@ 是 src 的绝对路径,通常在引入src下的文件时,使用 " @/src下文件的路径 ",但在 css 中使用 @ 时,必须在 @ 前面加上 ~(波浪线)

面试题:谈谈对 MVVM 设计模式的理解?答:M,数据层,就是 data 函数 return 出去的数据;V,视图层,就是 template 里的 HTML 标签;VM,视图模型,就是 Vue 对象实例,MVVM 设计模式,可以使得数据驱动视图,即数据层发生改变,视图层也发生改变,当使用 v-model 实现数据的双向绑定,就能反过来让视图层改变,数据层也发生改变

面试题:为什么数据要写在 data 函数里并被 return 出去?而不是写在 data 对象里?答:不使用 return 包裹的数据,数据会在项目的全局中可见,组件复用的时候,访问的都是一个 data 数据,会造成变量污染,使用 data 函数 return 一个新的对象,组件复用的时候,组件各自访问各自的局部数据,没有变量污染

一、Vue 脚手架的安装

脚手架:@vue/cli 是 Vue 官方提供的一个全局模块包,用于创建脚手架项目,脚手架是一个工作平台,无需配置 webpack,开箱即用

每台设备全局安装一次 @vue/cli 即可:npm i @vue/cli -g

验证是否安装成功:vue -V

注意:安装完后会在计算机中配置全局命令(vue命令),如果提示找不到该命令,配置环境变量即可

二、Vue 创建脚手架项目及基本配置和使用

(一)创建脚手架项目

创建项目(建议使用 cmd 终端):vue create 项目名称

注意:项目名称不能含有大写字母、中文和特殊符号

启动内置的 webpack 本地热更新开发服务器(无需额外配置):npm run serve

注意:使用 Vue-cli 脚手架创建项目时,提供的默认选项 2.0 和 3.0,都只包含 eslint 和 babel,缺少 less 和 less-loader 包,需要额外下载对应版本

yarn add less@3.0.4 less-loader@5.0.0 -D

实际开发中通常使用自定义选项对项目进行创建,空格选中 CSS Pre-processors,可选择使用 less 语法,无需额外下载 less 和 less-loader 包

(二)基本配置

在 src 同级处新建 vue.config.js 文件,填入以下配置,并重启开发服务器:

注意区分:Vue 中的配置文件与原生 webpack 不一样,项目中没有 webpack.config.js 文件,因为 Vue 脚手架项目用的是 vue.config.js 文件,新建配置文件时,必须使用该名

// vue 脚手架项目默认的配置文件为 vue.config.jsmodule.exports = {  devServer: {    port: 3000, //配置服务器端口号    open: true, //浏览器自动打开  },  lintOnSave: false, //关闭eslint检查};

补充:在 package.json 文件中,将 scripts 配置项里的 "serve": "vue-cli-service serve" 修改为 "serve": "vue-cli-service serve --hot --open --host 127.0.0.1",由于设置了 --open,在启动服务器的时候,会自动打开浏览器,无需在 vue.config.js 文件中额外配置 open: true

(三)清理 Vue 欢迎界面

assets 和 components 下的文件全部删除,保留 App.vue 文件,但删除里面内容,输入 < vue > 智能提示生成模板即可

注意:.vue 结尾的文件被称为单文件,拥有独立作用域,可以避免变量冲突,template 标签内只能存在一个根标签,其余标签需要书写在这个根标签内,一般而言,这个根标签是 div 标签)

补充:Vue 推荐使用 .vue 单文件,且要求尽可能不操作 DOM,转变思想(数据驱动视图)

三、Vue 基础语法

(一)差值表达式

语法:{{ vue变量 或 表达式 }}

注意:差值表达式一般搭配标签使用,即标签内部使用差值表达式,可以解析 vue变量,渲染数据(把值显示在标签夹着的地方)

注意:if 语句不是表达式,在双重花括号中,必须使用三目运算表达式作为条件判断

易混淆:插值表达式用于标签内部,v-bind 用于标签的原生属性或者自定义属性,注意区分

注意:插值表达式等同于 v-text,二者任选其一,尤其注意,如果需要渲染出标签,就不能使用插值表达式,而是使用 v-html 替换

(二)vue变量、函数方法的书写位置

vue变量都放在 export default 中 data 函数下 return 的对象里面,函数方法都放在 export default 中的 methods 里面:

export default {  data() {    return {        // 固定写法,存放vue变量    };  },  methods: {      // 固定写法,存放函数方法      // 尤其注意,这里的方法如果需要访问data里的 vue变量,需要加 this  },};
(三)原生属性使用变量

某些原生属性,如 href、src 属性,它们的属性值使用 vue变量,需要采取如下方式:

  1. v-bind:原生属性名 = " vue变量 "

  2. :原生属性名 = " vue变量 "(简单方式)

    注意:在 css 或者 html 中可以直接书写路径,但唯独在 js 中不能书写路径,因为工程化开发,默认把 js 文件当作一个模块,不能直接访问,因此需要导入具体的资源文件,然后将导入的文件(导入时接收的名字保存在 vue变量中,而不能将路径单纯的保存在 vue变量中使用

(四)事件绑定
  1. v-on : 事件类型 = " methods 里函数 或者 简单代码块 "
  2. @事件类型 = " methods 里函数 或者 简单代码块 "(简单方式)

注意:无传参,直接写函数名即可,如果有传参,需要写括号并填入实参

尤其注意:很多情况下,事件绑定会和 v-for 方法搭配使用,v-for 方法中的 item 和 index 会被当作实参传到事件处理函数中

尤其注意:v-for 循环绑定多个事件,共用一个事件处理函数,互不影响原因:当前标签只触发当前事件)

(五)Vue 方式绑定事件,获取事件对象 event 的方式
  1. methods 里的事件处理函数如果不需要传参:函数名(e) { e.preventDefault ( ) }

    绑定事件时:@事件类型 = " 函数名 "

  2. methods 里的事件处理函数如果需要传参:函数名(形参,e) { e.preventDefault ( ) }

    绑定事件时:@事件类型 = " 函数名(实参,$event) "

(六)事件修饰符

语法:@事件类型 . 修饰符 = " 函数名 "

修饰符列表:

  1. 阻止冒泡:@事件类型 . stop = " 函数名 "

  2. 阻止默认行为:@事件类型 . prevent = " 函数名 "

  3. 只触发一次事件处理函数:@事件类型 . once = " 函数名 "

  4. 监听回车或者退出:@键盘事件 . enter(或者 esc) = " 函数名 "

    尤其注意:当使用某些组件库时,组件库封装的非原生标签,在绑定事件时,可以使用修饰符 .native 监听该标签的原生事件

(七)v-model 表单双向绑定

给某个表单标签(如 input)添加一个 v-model = " vue变量 ",就可以让表单标签的 value 属性值和 export default 中 data 函数下 return 的对象里面的 vue变量实现双向绑定

注意:下拉菜单要给 select 标签绑定 v-model = " vue变量 "

尤其注意:遇到复选框,每个复选框都要绑定 v-model = " vue变量 ",并且 vue变量的数据类型必须是数组,才能关联 value 属性,所以必须给 vue变量初始化成数组,即 vue变量:[ ],如果 vue变量是非数组,关联的则是 checked 选中状态属性

尤其注意:实现双向绑定后,从表单同步到 vue变量里的数字,是字符串类型,可使用修饰符(. number)转换

语法:v-model . 修饰符 = " vue变量 "

v-model 修饰符列表:

  1. 转换为数字型:v-model . number = " vue变量 "
  2. 去除首尾空格:v-model . trim = " vue变量 "
  3. 失去焦点内容改变时:v-model . lazy = " vue变量 "
(八)v-html 修改标签里的内容

给标签添加:v-html = " vue变量 "

尤其注意:v-html 可以让标签内部渲染出 html 标签,该 vue变量存储的是字符串,使用时会自动解析为 html 代码,并且使用该方法会覆盖差值表达式

尤其注意:v-html 不能使用过滤器,可以定义一个过滤的方法(函数)给 vue变量使用(用于改造原始数据):v-html = " 过滤方法名(vue变量)"

尤其注意:既然选择使用 v-html,主要目的便是为了渲染标签,而渲染标签的同时又使用过滤方法,大多情况是为了给原始数据嵌套一层 html 标签,常搭配 replace 方法进行替换,如关键词高亮显示,而关键词又不唯一(动态),因此常采取 replace 的正则替换方法

(九)显示与隐藏
  1. v-show = " vue变量 "

    注意:该 vue变量存储的是布尔值,根据布尔值决定显示(true)或隐藏(false),且隐藏方式为 display:none,常用于频繁切换状态

  2. v-if = " vue变量 "

    注意:该方法同上,但隐藏方式为直接移除

补充:true 和 false 的反复切换(取反思路:每次点击,都会取反,即 this.flag = ! this.flag)

注意:v-if 常与 v-else 搭配使用,两者必须紧挨在一起,常用于根据判断条件只显示其中一个,如:点击按钮变成输入框,回车输入后再次变回按钮

尤其注意:业务场景要求,三者只显示一个,如搜索功能(关键字联想、历史记录、搜索结果),使用 v-if + v-else-if + v-else 即可实现

(十)循环遍历数组、对象并实时渲染数据到页面

v-for 方法,可以循环遍历数据,并且数据变化的同时,会触发 v-for 的更新,使得页面被实时渲染

v-for 使用步骤:

  1. 想要循环某个标签,就给这个标签添加 v-for = "(item,index) in 需要循环的数据 "

  2. 然后在这个标签内部根据 item 写上具体的渲染结构样式

    注意:item 是遍历数据的每一项(当前项),index 是索引值(下标),通常会把这个索引值当作某个事件的实参,由事件处理函数做相应处理

    注意:循环生成标签的原因在于,不确定有几条数据,如果后期增加了数据,循环生成标签就可以一劳永逸

(十一)类名 和 style 的设置

都是基于 v-bind 实现

  1. 设置动态class

    :class = " { 类名:vue变量(注意是布尔值) } "

    注意:style标签中正常写类名样式,然后通过 vue变量的变化来控制是否使用该类名

    尤其注意:正常类名和 动态class 互不影响

  2. 设置动态style

    :style = " { css属性名:' 值 ' } "

    注意:这里的值如果不使用变量,必须加单引号,如果使用 vue变量,该变量存放的必须是字符串

(十二)过滤器的使用

前提条件:过滤器只能使用在差值表达式和 v-bind 动态属性里(注意:v-html 里面不能使用过滤器)

  1. 全局注册

    在 main.js 文件中注册,就可以在任意 .vue 文件(强调:注意是组件)中直接使用

    语法:Vue . filter(" 自定义过滤器名称 ",(val) = > { 处理代码的程序;return 经过处理后的结果 })

    使用:需要被过滤的vue变量 | 自定义过滤器名称

尤其注意:在任意 js 文件中,书写了 Vue 进行挂载(如:挂载 filter),都没有真正执行挂载的代码,因为该 js 文件和项目工程没有任何关联,所以必须在 main.js 中引入该文件,才相当于全局注册

  1. 局部注册

    在 .vue 文件中 export default 下的 filters 配置项中定义过滤器

    语法:自定义过滤器名称(val){ 处理代码的程序;return 经过处理后的结果 }

    使用:需要被过滤的vue变量 | 自定义过滤器名称

注意:val 接收的是需要被过滤的 vue变量(给哪个 vue变量使用过滤器,val 接收的就是那个 vue变量)

多个过滤器使用:需要被过滤的vue变量 | 自定义过滤器名称 1 | 自定义过滤器名称 2

注意:过滤器在使用时,只写名称即可,如果有额外传参,需加括号,在定义过滤器时,val 的后面增加一个形参

(十三)计算属性

计算属性,指的是依赖 data 配置项里的 vue变量而执行程序,随着 vue变量的变化而变化

计算属性(可实时更新和计算的变量),放在 export default 下的 computed 配置项中:

  1. 低阶语法(格式类似于 methods 下的函数):

    计算属性名(){ 处理程序;return 处理结果 }

  2. 完整语法(当计算属性搭配 v-model 使用):

    注意:由于 v-model 绑定计算属性,表单只能获取计算属性的值,但不能给计算属性赋值,此时采取完整语法

      computed: {    计算属性名: {        //对计算属性进行赋值时(在复选框中是通过勾选赋值true或者false,其余情况:“计算属性=某个值” 赋值),调用set函数      set(val) {            // val是对计算属性赋值时,传递过来的参数(在复选框中是true或者false,在其余情况是某个值)            利用val执行处理程序        },        //当别的地方需要获取计算属性时,调用get函数      get() {        return 处理结果(值);      },    },  },

计算属性与函数的区别:计算属性自带缓存,多次调用会从缓存取值,只有发生变化后,才会重新执行,但是methods下的函数多次调用时,会每次都执行

尤其注意:计算属性里面只能书写同步代码,不能书写异步代码

深入理解:“ 计算属性 = 某个值 ” 表面上是给计算属性赋值,实际上只是把 “ 某个值 ” 传递到该计算属性的 set 函数中,作为参数使用,val 就是 “ 某个值 ”

(十四)侦听器(多应用于本地存储数据缓存,搜索框输入等)

侦听器是频繁触发的,可以实时侦听某个变量值的改变

侦听器放在 export default 下的 watch 配置项中:

  1. 低阶语法(适用于侦听简单类型数据):

    // 此种形式为 ES6 写法watch: {    被侦听的计算属性名或vue变量名(newVal,oldVal {        此处为处理程序    },},// 原生写法剖析:本质就是,在watch对象里,被侦听的变量,作为属性名,属性值是个函数(即对象方法)watch: {    被侦听的计算属性名或vue变量名: function(newVal,oldVal) {        此处为处理程序    },    // 如果被侦听的变量是对象形式,可以使用引号包裹该变量(原理:变量的本质是对象中的属性名,支持写引号形式)    "obj.xxx.xxx": function(newVal,oldVal) {        此处为处理程序    },},

    注意:低阶语法不能深度侦听(即 不能监听对象里的每一项)

  2. 完整语法(可深度监听):

    watch: {    被侦听的计算属性名或vue变量名: {        handler (newVal,oldVal) {            通常来讲此处内容会频繁触发(不严谨)        },        deep: true, // 开启深度侦听,可侦听复杂类型数据内的变化        immediate: true // 立即执行(页面一刷新就执行)    }}

尤其注意:被侦听的计算属性名或vue变量名,一定要和真正的计算属性名和 vue变量名保持一致,表示正在侦听当前数据,且不需要加 this

尤其强调:watch 侦听的变量名不需要加 this

尤其注意:当侦听的时复杂类型数据(object 引用类型:数组或者对象),使用 “ 深度侦听 ” ;当遇到某个组件是刚创建出来的情况,使用 “ 立即执行 ”

深入理解 watch 频繁触发的部分:handler(){ 此处内容会频繁触发 } 是 es6 的新写法,原生写法如下

      handler: function (newVal,oldVal) {          实际上频繁触发的是整个函数部分(即 handler 冒号后面的内容:function (newVal,oldVal) {})      },

因此在使用 lodash 工具库中的 debounce 防抖函数时,该 debounce 防抖函数要求包裹的是需要防抖、频繁触发的函数,即冒号后面的整个内容都需要进行防抖,如果 debounce 防抖函数是写在 function (newVal,oldVal) { } 的里面,由于 function (newVal,oldVal) { } 整个部分(包括里面的内容)都是频繁触发的,就会造成里面的 debounce 防抖函数被多次调用,生成多个作用域,明显不合理

简单剖析:debounce 要求包裹频繁触发的部分,而不是被频繁触发的部分包裹

四、解决无法触发 v-for 更新的问题

(一)能触发 v-for 更新 的情况

数组变更方法:push、pop、shift、unshift、splice、sort、reverse,由于这些方法会从根本上改变原数组,因此能触发 Vue 中的 v-for 更新(即:实时渲染数据到页面)

(二)不能触发 v-for 更新 的情况
  1. 数组非变更方法:slice、map、filter、concat,由于这些方法返回的是一个新数组,因此不能触发 Vue 中的 v-for 更新,可采用覆盖数组解决(拿返回的新数组,直接替换旧数组)
  2. 单独给数组中的某一个元素重新赋值或修改数组的长度,不会触发 Vue 中的 v-for 更新,必须使用 this . $set(数组名,更新的位置索引,要更新的值)方法给某个数组元素赋值,即可对视图进行更新
  3. 给对象动态添加属性并赋值,或者删除对象中的某一个属性,也不会触发 Vue 中的 v-for 更新,可使用 this . $set(对象名,属性名,要更新的值)方法给某个数组元素赋值,也可在操作数据结束后使用 this.$forceUpdate()方法,对视图强制更新,该方法同样适用于数组操作造成的不更新问题

尤其注意:能触发 v-for 更新 的情况中,必须只能是数组方法,如果数组方法和字符串方法结合使用,则必须通过覆盖替换来解决

五、v-for 更新原理及 key 的使用

(一)v-for 更新原理

在 template 模板标签里的内容,需要被 Vue 解析成一套虚拟的 DOM 结构,再转成真实的DOM,虚拟DOM本质是保存 DOM 关键信息的对象,这样在渲染过程中,避免了重复遍历

v-for 更新是通过虚拟DOM的新旧对比,在内存中对比变化部分,尝试复用标签就地更新内容(diff 算法,同级较,相同标签,只更新属性),比原生 js 操作 DOM(删除并重新渲染)性能更高

diff 算法流程:同级比较,当根元素发生变化时,会删除当前所有虚拟DOM,重新建立一套新的虚拟DOM,当子元素内容发生改变时,就地更新,复用当前标签,只更新属性,如果多生成一个标签,就更新一个新的虚拟DOM节点,如果有 key,就根据 key 值进行比较,但 key 值相同,仍然会就地更新,但如果根据 id 设置 key,同级比较的过程中,会直接生成一个新的虚拟DOM节点,其它标签不会变化,渲染性能更高效

简单记忆:无 key,就地更新,有 key,按照 key 比较,有 key 无 id,就地更新,有 key 有 id,性能最高

(二)key 的使用
  1. 给每个数据换成对象,准备 id,把 id 值作为 key,可以配合虚拟DOM,使 v-for 更新的性能达到最高

  2. 与 v-for 搭配使用,给标签加上如下代码:

    :key = " item . id "

    注意:key 的使用,基于 v-bind 实现,所以要加冒号,key 的值是 vue变量,一般是对象形式的数据中 id 值,唯一不重复即可

六、Vue 实现 "点击高亮,再次点击恢复"

(一)单个按钮

通常准备一个 vue变量 flag,再准备一个高亮类名,利用 flag 值的变化决定是否使用该类名,给按钮绑定事件,当点击该按钮,flag 的值取反

(二)多个按钮(多个按钮可同时高亮)
  1. 以对象形式存放每个按钮的数据:btns : [ { name: "按钮一", flag: false } , { name: "按钮二", flag: false } , { name: "按钮三", flag: false } ]
  2. 利用 v-for 方法循环生成三个按钮,并使用 item 渲染按钮的内部结构,准备一个类名,利用 item.flag 值的变化决定是否使用该类名
  3. 给循环生成的按钮添加点击事件,并把 item 当作实参传入事件处理函数
  4. 当点击当前按钮时,给当前 item.flag 取反

七、Vue 实现 Tab 栏效果

(一)固定写死的多个按钮
  1. 准备一个自定义变量,初始值为 1,给每个按钮添加点击事件,当点击按钮时,将自定义变量更改为当前的次序(点击第 n 个按钮,自定义变量改为 n)
  2. 给每个按钮添加动态类名,动态类名是否生效的判断条件为:自定义变量存放的次序是否等于当前次序
(二)循环生成的多个按钮
  1. 准备一个自定义变量,初始值为0,给循环生成的按钮绑定点击事件,当点击按钮时,将自定义变量更改为当前索引
  2. 准备一个动态类名,动态类名是否生效的判断条件为:当前索引是否等于自定义变量存放的索引

八、组件相关

(一)组件的概念

每一个单文件(.vue 文件),被称为组件,App.vue 文件被称为根组件,其余组件放在 components 文件夹下,被称为子组件,子组件分别负责页面的各个模块,然后被根组件(App.vue)引入,构成完整的页面

注意:在 Vue 中,组件的 style 标签如果添加了 scoped 属性,该组件内的样式,只针对当前组件有效,原理是增加了一个哈希值,使类名变成了交集选择器

尤其注意:如果不添加 scoped 属性,只需要给根标签一个类名,样式在该类名下书写(通过后代选择器),就可以达到同样的效果,因此某些情况可以不写 scoped 属性,scoped 等同于给根标签加一个类名,二者任选其一

(二)封装组件
  1. 创建组件:在 components 文件夹下,新建 .vue 文件,注意,每个组件内部要有完整的模板结构(利用 < vue > 快速生成即可),写入可复用的代码

  2. 引入组件:在任意单文件中(.vue 文件),通过 import 引入创建好的组件

    import 组件名(一般和组件文件名称保持一致,且首字母大写) from “组件所在路径”
  3. 注册组件:在根组件里 export default 的 components 配置项中,导出前面引入的子组件,供 template 标签内部使用

    export default {    components : {        // 原始写法        组件标签名:组件名        // 注意:一般情况,组件标签名会和组件名保持一致,对象中存在简化写法,因此直接写组件名即可        组件名(简单写法:组件标签名就是组件名)    }}
  4. 使用标签:在 template 中书写 < 组件标签名 > </ 组件标签名 > 即可

尤其重要:如果组件标签名,采取大驼峰命名法(如 BoxName),在 template 中使用标签,可转换为 < box-name > </ box-name > 的形式

(三)组件通信

组件通信的概念:组件与组件之间互相传递数据

  1. 父传子(父组件给子组件传递数据)

    第一步:在父组件(App.vue)中的子组件标签名上书写如下代码

    <子组件标签名 :自定义变量名="vue变量(保存的是数据)"></子组件标签名>

    第二步:子组件 export default 里的 props 配置项,用于接收父组件传来的数据

    export default {  props: ["自定义变量名"],}

    尤其重要:父组件与子组件的自定义变量名必须保持一致

    使用场景:子组件只是单纯的使用来自于父组件中的数据

    尤其注意:props 里的变量是只读变量,无法在子组件中通过修改该变量而改变父组件里的数据,因此需要子传父通信技术

    尤其注意:export default 下的其它配置项,访问 data 和 props 配置项里的数据,都需要在前面加 this

  2. 子传父(子组件给父组件传递数据)

    原因:由于子组件无法直接修改父组件里的数据,需要通过参数传递,让父组件完成

    第一步:子组件中某个标签绑定了事件,该事件处理函数中,书写如下代码

    methods:{    事件处理函数名称(){        this.$emit("自定义事件类型名称",需要传给父组件的数据)    }}

    第二步:父组件里,必须是在传来数据的那个子组件标签上,添加 @自定义事件类型名称 = " 父事件处理函数名 "

    <子组件标签名 @自定义事件类型名称="父事件处理函数名称"></子组件标签名>

    尤其重要:子组件与父组件的自定义事件类型名称必须保持一致

    第三步:在父组件下的 methods 配置项下,事件处理函数的括号里需要填写形参,该形参接收的是子组件传来的数据

    methods:{    父事件处理函数名称(data){        根据子组件传来的数据,执行某些操作    }}

    使用场景:多应用于子组件中绑定事件,然后操作父组件里的数据

    尤其重要:无论是何种数据传输,如果直接把具体的简单数据(obj.xxx)传过去,可以获取该值,但无法直接修改该数据(obj.xxx)的值,必须以对象形式(obj)作为媒介传输过去,再具体修改里面的简单数据(obj.xxx)的值

    补充:通过子传父传递的数据,父组件里,必须是在传来数据的那个子组件标签上,添加自定义事件的时候,可以使用便捷写法(即:不使用事件函数)

    <子组件标签名 @自定义事件类型名称="直接在此处执行简单语句,需要注意的是,$event 本身就是子组件传递过来的数据,直接使用即可"></子组件标签名>
  3. 通过子传父技术,触发父组件已存在的函数

    当父组件本身存在一个方法函数,该方法函数有其自己的触发方式,但业务需求是,在子组件中,通过其它方式也能触发这个方法函数,涉及到方法函数的复用,可以在子组件中使用 this.$emit ( "自定义事件类型名称",需要传给父组件的数据 ),在父组件中使用 @自定义事件类型名称="已存在的函数名称" 即可

  4. 跨组件通信(EventBus:兄弟组件之间传递数据)

    第一步:在 src 文件夹下创建 EventBus 文件夹,并在该文夹夹下新建 index.js 文件,书写如下代码

    // 引入vue模块包import Vue from "vue";// 默认导出vue这个实例化对象export default new Vue();

    第二步:两个毫无关系的兄弟组件同时引入 eventBus

    import eventBus from "@/EventBus/index.js";

    第三步:向外传递数据的组件,使用如下方法进行数据传输

    eventBus.$emit("自定义名称", 需要传递的数据);

    第四步:接收数据的组件,需要在 created 钩子函数中使用如下方法进行数据接收

      created() {    eventBus.$on("自定义名称", (形参) => {      // 该形参就是传递过来的数据    });  },
  5. 父组件操作子组件已存在的数据

    既不是父传子,也不是子传父,而是父组件操作子组件本身的数据,大多数情况,父组件操作子组件里的数据,前提条件是子组件和父组件建立了联系,且一定是通过子传父通信计算,操作了父组件里的数据,顺带操作子组件里的 vue变量,其本质仍然是子组件发起的事件,因此,直接在子组件注册的事件函数中 “ $emit("自定义事件类型名称",需要传给父组件的数据) ” 的语句后面,紧接着操作子组件本身的数据即可

  6. 多级组件嵌套传递数据(父组件传递给孙组件、孙组件传递给父组件)

    连续多次传值固然可行,但不便捷,且可读性较差

    一、孙组件传递给父组件

    在中间层的组件上(即子组件标签名上),尤其注意,是在注册使用的子组件标签名上,增加一个 v-on="$listeners" 属性即可,然后把 “孙传父” 当成 “子传父” 正常传值即可

    原理:子组件内部可以通过 $listeners 访问父组件内部,在该子组件标签名(也就是本身)上定义的事件,中间层(子组件标签名)加上 v-on="$listeners" 属性,相当于将获取到的事件继承下去,孙组件内部可以通过 $listeners 访问子组件内部,在该孙组件标签名(也就是本身)上定义的事件以及继承的事件,因此孙组件和父组件产生了联系,就可以同子传父那样,正常传递数据($listeners 仅在中间层使用,用于架起沟通桥梁,传递数据无需额外使用)

    二、父组件传递给孙组件

    在中间层的组件上(即子组件标签名上),尤其注意,是在注册使用的子组件标签名上,增加一个 v-bind="$attrs" 属性即可,然后把 “父传孙” 当成 “父传子” 正常传值即可,但尤其需要注意的是,不能使用 props 接收,$attrs 获取的是非 props 属性,使用 “ $attrs.属性名 ” 即可获取数据

    原理:子组件内部可以通过 $attrs 访问父组件内部,在该子组件标签名(也就是本身)上定义的属性,中间层(子组件标签名)加上 v-bind="$attrs" 属性,相当于将获取到的属性继承下去,孙组件内部可以通过 $attrs 访问子组件内部,在该孙组件标签名(也就是本身)上定义的属性以及继承的属性,因此孙组件和父组件产生了联系,就可以同父传子那样,正常传递数据,且传递的属性都被压缩在 $attrs 里面,通过 “ $attrs.属性名 ” 的方式获取属性值即可

  7. 祖先组件传递数据给所有后代组件(依赖注入,慎用,数据不是响应式)

    第一步:在祖先组件中的 export default 中书写 provide 方法传递数据

    export default {  // 给所有后代组件(跨任意级都可以)提供数据  provide () {    return {      自定义变量名: 想要传递的数据    }  }}

    尤其注意:依赖注入的数据不是响应式的,也就是说,当想要传递的数据发生改变,自定义变量名不会读取到新变化的数据,永远保存的是该数据第一次初始化时的值,因此需要避免使用会二次变化的数据,而是使用初始化后就不会再发生改变、固定的值作为依赖注入

    第二步:在后代组件中(可跨级,无论嵌套多深),同 props 类似,使用 inject 接收祖父组件传递过来的数据

      inject: {    祖先组件传递的自定义变量名: {      type: Number | String | Object | Array | Boolean (限制数据类型,数据类型校验),      default:默认内容,    },  }
(四)props 的进阶使用及注意事项

通常情况下,Vue 规定 props 是只读属性,子组件不可以修改父组件传入的数据,否则会造成数据的不一致性,因此可使用子传父通信技术

但 props 也不是一定不可修改,分两种情况:

  1. 如果父组件传给子组件的是简单数据,禁止子组件直接修改父组件数据,必须采取子传父通信技术解决
  2. 如果父组件传给子组件的是复杂数据(对象),由于地址指向一致,因此子组件可以修改该复杂数据的内部属性,但不能整体使用新的对象或者数组进行覆盖

进阶用法(props 的配置选项,常用于封装一个可自定义组件样式的组件,搭配动态 style 或动态 class 实现):

  props: {    // 简单写法:props:["父组件传递的自定义变量名"]    // 进阶写法:    // 1. props:{ 父组件传递的自定义变量名: Number | String | Object | Array | Boolean(只限制数据类型)}    // 2. props:{ 父组件传递的自定义变量名:{ 多个配置选项 } }    父组件传递的自定义变量名: {      type: Number | String | Object | Array | Boolean (限制数据类型,数据类型校验),      default:默认内容,      required: true/false, // 必填项,外部使用该组件,必须给该组件使用当前定义好的的变量名(即:父组件传递的自定义变量名)            // 自定义配置选项      validator(value) {// value 就是接收到的数据,与自定义变量名等价        if (判断条件) {          return true; // 符合条件就 return true        } else {          console.error("不符合条件的提示");          return false; // 不符合条件就 return false        }      },    },  }

尤其注意:如果默认内容是引用类型(数组或者对象),必须使用函数形式返回这个数组或者对象

default(){    return [数组]/{对象}}
(五)使用 Props 封装可自定义样式的组件
  1. 搭配动态 class 的形式

    (Ⅰ)首先在该组件内定义多个类名(用于显示不同样式)

    (Ⅱ)在需要自定义样式的标签上使用动态 class,并且是数组形式

    :class = “ [ props里的自定义变量名1(类型),props里的自定义变量名2(颜色),props里的自定义变量名3(其它) ] ”

    尤其注意:这里的动态 class 不是键值对的形式,没有布尔值决定它是否使用该类名

    (Ⅲ)父组件中正常对子组件标签名进行传值,即:props 里的自定义变量名(如:类型) = 定义的类名

  2. 搭配动态 style 的形式

    (Ⅰ)首先在需要自定义样式的标签上使用动态 style

    :style = “ { style 属性名1,style属性2,style属性3 } ”

    (Ⅱ)props 里的自定义变量名与 style 属性名称保持一致,就可以简写成上一步的形式

    (Ⅲ)父组件中正常对子组件标签名进行传值,即:props 里的自定义变量名(如:style 属性名1) = 具体的 css 样式

(六)解决 props 传递简单数据造成的单向数据流问题

虽然父组件传给子组件复杂数据(对象),可以实现让子组件直接修改父组件的数据,但是传递复杂数据(对象)维护成本较大,不建议这么做,但是当 props 接收的是简单数据,又无法直接修改父组件的数据,因此可使用 sync 方法解决:

  1. 在父组件中的子组件标签名上正常向子组件传递数据,但使用 sync 修饰自定义变量名(注意:必须传递的是简单数据才可以使用这种方法)

    <子组件标签名 :自定义变量名.sync="vue变量(保存的是数据)"></子组件标签名>
  2. 在子组件中,正常使用 props 接收这个自定义变量,但是在修改这个变量时,可通过如下代码修改,把新值重新赋值给该自定义变量名

    this.$emit('update:自定义变量名', 新值)

    注意:在 template 标签中可省略 this

    尤其注意:这里的新值,可能是依赖于该自定义变量本身而产生的新数据(也就是使用到了自定义变量),但这里的新值,不能写 " 自定义变量++ " 的形式,因为 ” 自定义变量++ “ 等同于 ” 自定义变量 = 自定义变量+1 “,实际上还是对该自定义变量直接进行了修改(也就是直接赋值操作)

九、组件的生命周期

概念:从 Vue 实例的创建到销毁的过程,共分为四个阶段,各阶段都附带两个钩子函数

(一)初始化阶段

beforeCreate 钩子函数触发时,无法获取数据,只是初始化了事件和生命周期

created 钩子函数触发时,能够获取数据,使用场景:发起 Ajax 请求,注册全局事件

尤其注意:created 是频繁触发的,且页面一刷新,就会执行 created 里的内容,可利用这个特性,发起请求并保存请求回来的数据,或者用来测试代码

(二)挂载阶段

beforeMount 钩子函数触发时,无法获取真实DOM,使用场景:预处理数据,且不会触发 updated 钩子函数

mounted 钩子函数触发时,能够获取真实DOM(注意是非更新后的 DOM,而是初始化后的 DOM)

(三)更新阶段

钩子函数触发前提:当数据改变后才执行

beforeUpdate 钩子函数触发时,无法获取数据更新后的真实DOM(注意是更新后)

updated 钩子函数触发时,能够获取数据更新后的真实DOM(注意是更新后)

尤其重要:实际开发中,会使用 watch 侦听来替代 updated 钩子函数

尤其注意:当遇到某个 DOM 元素、数据获取不到,或者数据设置无效,最终可考虑在 updated 钩子函数中获取、设置

(四)销毁阶段

钩子函数触发条件:当销毁一个组件时才执行,如 v-if = " false "

beforeDestroy 或 destroyed 钩子函数触发时,使用场景:移除当前组件里的延时器或者定时器,或者移除全局事件

十、axios 的使用

前提条件,下载 axios 包:npm i axios

尤其注意:页面刷新第一次发送请求,必须在 created 钩子函数里面使用 axios 发送请求

(一)页面刷新时,发送 axios 请求
  1. 导入包:import axios from "axios"

  2. 在 created 钩子函数里,发送请求,并使用 async/await 语法

    export default {// 1.async 放在 created 钩子函数前面  async created() {    // axios 异步任务(且返回的是一个promise对象),前面使用 await 修饰,并用变量名接收    let res = await axios({      url: "请求地址(一般情况,请求地址的根路径相同,具体的接口名不同)",      method: "GET或者POST", // 请求方式      params:{xxx:xxx} // GET请求携带的参数      data:{xxx:xxx} // POST请求携带的参数    });    console.log(res); // res是请求返回的结果  },};

尤其注意:GET请求携带参数使用 params,POST 请求携带参数使用 data

尤其重要:函数如果被 async 修饰,该函数就变成了特殊的 async 异步函数,当该函数被调用,返回结果是 promise 对象,如果想要接收真正的值,需要给这个被调用的 async 异步函数,进一步使 async/await 语法,且 await 仅与它最近的 async 匹配

(二)其余地方使用 axios 请求
  1. 导入包:import axios from "axios"

  2. 在 methods 配置项下的函数内部发送 axios 请求,其余同上

    注意:async 放在函数名的前面,使用 await 修饰 axios 异步任务,并用变量名接收

(三)axios 的进阶使用(任意 .vue文件皆可访问 axios 方法)

axios 的请求地址,由根路径和具体的接口名组成,通常情况,根路径相同,接口名不同

全局配置基地址(配置一次根路径,后面请求地址直接写接口名即可):axios.defaults.baseURL = " 基地址 "

  1. 在 main.js 文件中导入 axios 包:import axios from "axios"
  2. 全局配置基地址
  3. 把 axios 方法添加到 Vue 的原型上:Vue.prototype.$axios = axios
  4. 使用时,格式为 this.$axios ( )
(四)顶级使用:axios 的二次封装
  1. 新建 request.js 文件,一般保存在 utils 文件夹下,导入并全局配置基地址,但是不把 axios 方法添加到原型上,而是以一个变量接收,再默认导出

    // 导入 axios 包import axios from "axios";// axios.create 方法是 axios 配置的完整写法,可配置基地址,返回的仍然是 axios 这个方法,创建一个 axios 实例(request变量)接收const request = axios.create({  // 配置基地址  baseURL: "基地址",   timeout:毫秒 // 请求超时时间});// 前提:配置请求头,需要访问 Vuex 容器,又因为是在js文件中访问 store,因此需要引入以下代码import store from "@/store";// 利用请求拦截器(请求发送之前做某些事情),配置请求头(主要处理token问题),配置完后,在向服务器发送请求时,可以携带一些参数(token)request.interceptors.request.use(  function (config) {    const { 变量名(state里的变量,保存的是请求头需要携带的参数,一般是token) } = store.state;    if (变量名 && 变量名.xxx) {      config.headers.Authorization = `Bearer ${变量名.xxx}`;    }    return config;  },  function (error) {    // 当请求拦截器发生处理错误,终止执行,该请求的后续操作同时也会终止    return Promise.reject(error);  });// 利用响应拦截器,在请求数据返回之后,对数据进行二次处理,一般是解构数据,根据请求状态(success)来提示错误并reject,或返回真正想要的数据request.interceptors.response.use(  function (response) {    // response是请求返回的数据,axios默认加了一层data,因此对response.data进行解构,得到相应的请求状态、消息提示、以及真正想要的数据    const { 请求状态, 消息提示, 真正想要的数据 } = response.data    // 根据请求状态的成功与否决定下面的操作    if (请求状态) {      return 真正想要的数据    } else {      // 请求状态为失败时,提示错误消息(此处为element组件提示),并使用reject终止执行,该请求的后续操作同时也会终止      Message.error(message)      // 没有错误对象,可以new一个错误对象      return Promise.reject(new Error(message))    }  },  function (error) {    // 当响应拦截器发生处理错误,提示错误信息(此处为element组件提示),并使用reject终止执行,该请求的后续操作同时也会终止    Message.error(error.message)    // 此处使用的是错误方法里error    return Promise.reject(error)  });// 默认导出该变量,该变量使用与 axios 一致export default request;

    尤其注意:reject 的效果不仅是将请求跳到 catch 终止执行,同时也能让该请求方法的后续代码(与请求不相关,如跳转操作)也终止执行,类似 return

    补充技巧:没有错误对象,可以 new 一个错误对象,即 new Error( )

  2. 以某个范围类型命名,新建 js 文件,一般保存在 api 文件夹下,用来封装与之相关的请求函数

    // 默认导入 requestimport request from "@/utils/request";// 封装某个请求函数(如登陆请求),并按需导出export const 请求函数名(如login) = (参数) => {  return request({    method: "POST 或 GET",    url: "请求接口",    data 或 params: 参数,    // 请求头,可统一配置,无需每次手动添加  });};
  3. 在别的组件中使用该请求,先按需引入:import { 请求函数名(如 login) } from "@/api/相关请求.js",调用时,直接使用:请求函数名(参数),即可发起相关的请求,如需对返回的结果有进一步操作,则对该请求函数使用 async/await 修饰,并用变量名接收,返回的数据保存在该变量名中

十一、$refs 获取真实 DOM 和调用组件对象里的方法

前提:在 mounted 钩子函数里面可以获取真实DOM 及获取组件对象

(一)Vue 中获取 DOM 的两种方式
  1. 给标签添加 id 属性(id=“xxx”),获取DOM元素:document.getElementById(" id名 ")

    容易混淆:getElementById 获取 DOM 元素,id名前面不用写 #

  2. 给标签添加 ref 属性(ref=“xxx”),获取DOM元素:this.$refs.ref 名

    注意:如果添加的 “ ref 名 “ 是 “ xxx-xxx ” 的形式,因此需要使用 this.$refs [ "xxx-xxx" ] 获取 DOM 元素

(二)使用 $refs 调用某个组件里的方法

给组件标签名(注意是组件标签名)添加 ref 属性(ref="xxx"),调用该组件内的方法:this.$refs.ref名.函数名 ( )

十二、解决数据层改变,视图层更新后无法获取更新后的DOM元素的问题

在 Vue 中,通过 $refs 无法获取 DOM 元素,需要搭配 this.$nextTick ( ( ) => { } ) 使用

原理:由于视图更新属于异步任务,从而导致获取DOM元素的操作早于视图更新,因此无法准确获取该 DOM

解决办法:使用 this.$nextTick ( ( ) => { } ) 包裹获取DOM元素的操作,该函数只有当数据改变,视图层更新后才会触发,因此可以获取该 DOM 元素

尤其注意:在 Vue 中,出现程序冲突(多个语句同时触发,导致想要执行的语句被不想要执行的语句覆盖),可以使用 this.$nextTick ( ( ) => { } ) 包裹想要执行的代码,改变执行顺序,当所有视图更新的异步操作结束后,最后才执行 this.$nextTick ( ( ) => { } ) 花括号里的语句

十三、Vue 组件进阶

(一)动态切换组件(后面会用路由代替)
  1. 准备好多个需要动态切换的子组件

  2. 设置一个 Vue变量,用于存放 “ 子组件标签名 ”(components 配置选项下注册好的子组件标签名)

  3. 使用 < component :is = " Vue变量 " > < /component >,用于显示当前需要出现的组件

  4. 通过改变该 Vue变量的值,实现动态切换

    注意:components 配置选项,单词有 s,动态切换的 component 标签没有 s,需要区分

(二)组件缓存技术

动态切换组件会导致频繁创建和销毁,此时需要组件缓存技术,使用 < keep-alive > < /keep-alive > 包裹 component 标签即可

注意:组件缓存有两个钩子函数,activated:缓存激活;deactivated:失去缓存

(三)组件插槽的使用(用于自定义组件内部标签及数据)
  1. 子组件内部使用 slot 标签,准备插槽(自定义部分):

    < slot name=" 插槽名 " :key=" 数据变量名 "> 默认内容 < /slot >

    注意:slot 标签内部填入默认内容,当没有被传入具体的标签,显示默认内容

    注意:name 属性可以让父组件插入标签到指定的插槽(父组件插入标签时,需要在 template 标签上使用 v-slot:插槽名)

    注意:暴露子组件里的数据(:key = " 数据变量名 "),可以使外部父组件插入标签时填入数据

  2. 父组件插入标签及数据:

    <子组件标签名><template v-slot:插槽名=“自定义接收名(用于接收子组件传来的数据,建议与插槽名保持一致)”> 传入具体的标签 </template>    </子组件标签名>

    注意:父组件插入标签时,需要在 template 标签上使用 v-slot = ” 自定义接收名 “ 接收数据( 自定义接收名 . key = 子组件暴露出的数据 )

    尤其注意:当如 v-slot:插槽名 和 v-slot = " 自定义接收名 " 同时存在,必须连写:v-slot:插槽名 = " 自定义接收名 " 或 #插槽名 = “ 自定义接收名 ”

(四)组件的强制更新

通常情况,组件加载时,默认会从服务端获取初始数据并渲染,当使用 axios 请求对服务端的数据进行操作后,服务端数据发生改变,但并不会影响本地数据,此时需要手动操作旧数据,更改视图(本质上是为了看起来和服务端数据保持一致),或者刷新页面,使得重新加载组件,拿到服务端的最新数据进行渲染,但刷新页面显然并不合理,手动操作旧数据,更改视图也略显麻烦,因此可采取组件强制更新(重新加载)

方法一:给组件绑定一个 key 值,当操作完服务端数据后,手动更改 key 值,即可强制更新组件

方法二:给组件添加 v-if ,通过一个变量控制组件的添加和移除,这个过程会让组件重新加载,即操作完服务端数据后,手动移除,并立即手动添加,但手动添加的代码需要使用 this.$nextTick ( ( ) => { } ) 进行包裹

十四、自定义指令

当不可避免操作 DOM 的时候(如拖拽),可以考虑使用自定义指令

  1. 在 main.js 文件中全局定义:Vue.directive("指令名称",{ update(el,其余形参){ el 是当前使用该指令的标签 } })

  2. 使用自定义指令:v-指令名称 = " 实参 "

面试题:请说出自定义指令的钩子函数以及它们的作用?答:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)

十五、Vue 路由

路由概念:路径和组件一 一对应的映射关系(使用不同路径,显示不同组件)

单页面应用(SPA):所有功能在一个 html 页面实现,通过路由显示不同组件,实现业务场景的切换,由于整体不刷新页面,因此开发效率较高,并且正是因为页面不会刷新,可以实现中转动画的效果

(一)组件的分类

页面组件(一个组件代表一个页面,常用于路由):src / views 文件夹 / 页面组件文件夹 / index.vue

注意:实际开发中,每个页面组件都需要用单独的文件夹存放,且文件夹名称需要见名知意,文件夹内的页面组件名,必须为 index.vue,且使用 import 引入该文件时,根据 import 的语法规范,可以只写文件夹,省略 index.js 不写

注意:页面组件文件夹中还包含一个 components 文件夹,存放的是页面组件所需要用到的其它组件

可复用组件(可重复使用,用于数据渲染并展示):src / components 文件夹下

(二)手动配置路由环境(较为复杂,且无必要)

前提条件,下载 vue-router 包:npm i vue-router

在 main.js 文件中进行如下配置:

// 导入包import VueRouter from 'vue-router'// 注册全局组件Vue.use(VueRouter)// 定义规则数组,一个对象代表一个规则const routes = [  // 规则一  {    path: "/路由路径",    name: "路由名",    component: 该路由路径或路由名对应的组件名  },  // 规则二  {    path: "/路由路径",    name: "路由名",    component: 该路由路径或路由名对应的组件名  },]// 创建路由对象,将规则传入const router = new VueRouter({  routes})// 将路由对象注入到 Vue 实例中(即 main.js 自带的 new Vue 中),之后可以通过 this 访问 $route 和 $routernew Vue({  router, //新增代码  render: (h) => h(App),}).$mount("#app");
(三)全自动配置路由环境(简单方法)

使用 “ vue create 脚手架项目名称 ” 创建工程时,选择自定义选项,空格键选中 router,待工程创建好,即可完成配置

注意: use history mode for router?选择 no

(四)规则配置、路由重定向、404 页面

前置知识:路由分为哈希模式和历史模式(哈希模式在浏览器地址栏中显示的路径会显示 # 号)

首先创建多个子组件,在 router 文件夹下的 index.js 中导入这些子组件,并配置规则,规则的具体配置格式如下:

const routes = [  {    path: "/",         // 一个反斜杠表示根路径    redirect: "/指定路径",  // 路由重定向:当访问某个路径,可使用 redirect 跳转至指定的路径    component: 该路由路径或路由名对应的组件名 // redirect 可以和 component 同时共存  },  {    path: "/路由路径",    name: "路由名",    component: 该路由路径或路由名对应的组件名  },  {    path: "*",    component: 404页面组件名  }]

尤其注意:在规则中配置组件时,可使用 “ 组件按需加载 ” (又称路由懒加载)方法提高性能(点击当前路由,加载当前对应组件):

component:() => import ( ' 组件路径 ' )

附带知识:

  1. 路由重定向:在规则中,当访问某个路径,可使用 redirect 跳转至指定的路径

    尤其注意:规则数组中的路由对象里,可以同时书写 redirect 和 component

  2. 404 页面:准备一个 404 页面的组件,在规则的最底部,通过通配符(*)匹配,当以上所有规则不满足时,跳转到 404 页面

(五)声明式导航

导航概念:点击的导航不同,显示不同的组件

  1. a 标签导航(了解即可)

    <a href="#/跳转的路由路径">导航名</a>

    注意:a 标签导航的路由路径前面,必须写 # 号

  2. router-link 标签导航(常用)

    <router-link to="/跳转的路由路径">导航名</router-link>

    注意:router-link 标签导航的路径前面,无需写 # 号,会自动解析成哈希模式

    尤其注意:使用 router-link 标签导航,自带激活时的类名(router-link-active),可对该类名进行样式编写

(六)声明式导航的两种传参方式
  1. 查询字符串传参

    传参:< router-link to="路径?参数名=值" >导航名< /router-link >

    接收:在路由对应的组件内部通过 " $route.query.参数名 " 的方式访问参数的值

    简单记忆:“ ?参数名 = 值 ” 的形式是查询字符串,而 query 就是查询的意思,因此使用 " $route.query.参数名 " 来接收

  2. 动态传参

    需要在组件对应的规则中提前配置:path: "/路由路径/:动态参数名"

    传参:< router-link to="路径/动态参数值" >导航名< /router-link >

    接收:在路由对应的组件内部通过 " $route.params.动态参数名 " 的方式访问参数的值

    简单记忆:与 query 相反,params 就是用来接收动态传参的参数,编程式导航同理

深入理解:

(七)编程式导航及传参
  1. 给标签(如 span 标签)添加点击事件,并携带两个参数,第一个参数为路由路径,第二个参数为路由名

    <span @click="事件处理函数名称('/跳转的路由路径','跳转的路由名')">导航名</span>
  2. 事件处理函数中:

    事件处理函数名称(targetPath,targetName) {      this.$router.push({        // 方式一:通过路由路径跳转        path: targetPath(跳转的路由路径),        // 查询字符串传参(形式:路径?参数名=值&参数名=值)        query: {          参数名: 值,        },                // 方式二:通过路由名跳转(建议使用)        name: targetName(跳转的路由名),        // 动态传参(形式:路径/动态参数值)        params: {          动态参数名: 值,        },      });    },

    尤其注意:编程式导航中,跳转方式有两种,一种是通过 path 跳转,一种是通过 name 跳转,但使用 path 跳转,就不能使用 params 传参,name 则可以和 query(查询字符串传参)、params(动态传参)任意搭配使用,所有在编程式导航中,建议使用 name 进行跳转,接收方式与传参方式一 一对应

    简单记忆:path 和 params 两者都是以字母 p 开头,一山不容二虎,两者只能存在一个,而规则数组中每一个对象,本质就是代表一个组件,而 path 和 name 就是这个组件的别名,name 是组件的大名,因此权力较大,可以和 query、params 任意搭配使用

    容易混淆:编程式导航中使用 $router.push 跳转函数,其中 $router 有 r ,而接收查询字符串参数或动态参数使用的 $route 没有 r

额外补充:编程式导航只跳转不传参,可使用行内点击事件快速实现跳转路由,详见(八)挂载点

<span @click="$router.push('/跳转的路由路径')">导航名</span>
(八)props 解耦动态路由

尤其注意:无论是通过声明式导航还是编程式导航进行路由跳转,props 解耦动态路由,都必须只能搭配动态传参,才可以使用(不支持查询字符串传参)

在路由对象中,书写 props:true,跳转该路由时,传递到对应组件的动态参数,可以映射到组件中,通过 props 接收,无需使用 $route.params.动态参数名

  1. 路由规则配置:
const routes = [  {    path: "/路由路径",    // 必须搭配动态传参的方式(不支持查询字符串)    name: "路由名/:动态参数名",    component: 该路由路径或路由名对应的组件名,    // 将路由动态参数映射到对应组件的props中    props: true  },]
  1. 路由跳转并传参:
// 编程式导航(路由规则配置中可以不写 /:动态参数名,但建议还是写上,便于在地址栏查看传递的动态参数)事件处理函数名称(targetPath,targetName) {      this.$router.push({        // 通过路由名跳转(建议使用)        name: targetName(跳转的路由名),        // 动态传参(形式:路径/动态参数值)        params: {          // 此处的动态参数名要和路由规则配置中的动态参数名保持一致          动态参数名: 值,        },      });    },
  1. 对应组件中访问数据:

传统访问方式:$route.params.动态参数名

开启 props 解耦动态路由后的访问方式:在对应组件中正常使用 props 对 动态参数名进行接收即可,props:{ 动态参数名:{ type:xxx,等等...... } }

何时使用路由传参(重要):路由传参的本质,其实就是给组件传递数据,与父传子、子传父通信无区别,但不同的是,只有在不是父子关系、且通过路由点击跳转到某个组件的时候,需要使用路由传递数据(即:被点击的组件和要跳转的组件不是父子关系,且要跳转组件显示的条件必须通过点击跳转)

(九)挂载点(当点击导航后,显示路由对应的内容,即组件)
<router-view></router-view>

路由的本质(重要):借助声明式导航和编程式导航进行跳转并在挂载点显示,其实在浏览器输入路径也可以进行跳转,挂载点必须要有,但导航并不是必须有

注意:如果是点击某个页面标签(非特定)进行跳转路由,则必须借助编程式导航,绑定事件,在事件处理函数中,使用 $router.push(" 路由路径 ")跳转到指定路由,也可使用 $router.back()返回上个路由,若是简单事件,则不需要单独写事件处理函数,直接使用:@事件类型 = " $router.push(' 路由路径 ')"

(十)路由嵌套

路由嵌套概念:当一级路由切换到某个子组件,该子组件同样拥有路由切换功能,被称为二级路由

(Ⅰ)传统路由嵌套:

  1. 一级路由的导航切换实现后,在某个需要实现路由切换的子组件(如 Find 组件)内部,书写二级路由(即:二级导航 + 二级挂载点)

  2. 在 Find 组件所对应的规则中,使用 children:[ { } ] 再次定义二级路由规则:

    const routes = [  {    path: "/一级路由路径",    name: "一级路由名",    component: Find(该一级路由路径或一级路由名对应的一级组件名),    children:[        {        path: "二级路由路径", // 注意:二级路由的 path,不是从根路径开始写,且不写/      name: "二级路由名",      component: 二级组件名(该二级路由路径或二级路由名对应的二级组件名)  }    ]  },]

    尤其重要:二级路由的 path,不是从根路径开始写,且不写 /

  3. 在二级导航中,跳转的路由路径,必须以 / 开头,且从一级路由路径开始,写全路径:

    < router-link to=" / 一级路由路径 / 二级路由路径 " >导航名< /router-link >

(Ⅱ)新版路由嵌套:

  1. 二级路由的 path 写法正常加 / ,且取名随意,通常和组件名保持一致(见名知意)
    children:[        {        path: "/二级路由路径", // 二级路由的 path,正常写/      name: "二级路由名",      component: 二级组件名(该二级路由路径或二级路由名对应的二级组件名)  }    ]
  1. 二级导航中,直接使用 “ / 二级路由路径 ” 跳转
<router-link to=" /二级路由路径 "> 导航名 </router-link>
(十一)路由守卫(权限拦截)

(Ⅰ)路由前置守卫

语法:router . beforeEach((to,from,next) => { 路由跳转之前先执行这里 })

注意:next(false)阻止路由跳转;next()正常放行;next( ‘ 路由路径 ’ )是跳转到某个地址,to.path 是去往的路由路径(非跳转,可用于判断)

尤其注意:next 是路由前置守卫必须执行的钩子函数,如果不调用 next(),页面留在原地,所以必须调用 next ()去往一个页面

(Ⅱ)利用 token 对路由访问进行权限拦截

核心思路:先定义一个白名单,判断是否有 token,当存在 token,如果去往的路径是登录页,强制跳转到主页,否则正常放行;当不存在 token,判断去往的路径是否在白名单内,如果在白名单内,正常方向,否则强制跳转到登录页

// 引入路由实例import router from '@/router'// 引入Vuex store实例import store from '@/store'// 引入进度条插件import NProgress from 'nprogress'// 引入进度条样式import 'nprogress/nprogress.css'// 定义白名单const whiteList = ['/login', '/404']// 路由前置守卫router.beforeEach((to, from, next) => {  // 首先开启进度条  NProgress.start()  // 如果存在token  if (store.getters.token) {    if (to.path === '/login') {      // 进一步判断,如果去往的是登录页,强制跳转到主页      next('/')    } else {      // 其余正常放行      next()    }  } else {    // 如果不存在token    if (whiteList.indexOf(to.path) > -1) {      // 进一步判断,如果去往的路径在白名单内,正常放行      next()    } else {      // 如果去往的路径不在白名单内,强制跳转到登录页      next('/login')    }  }  // 当手动切换地址时,进度条无法关闭,此时需要强制手动关闭进度条  NProgress.done()})// 路由后置守卫router.afterEach(() => {  // 关闭进度条  NProgress.done()})
(十二)路由数据

路由规则里面有个子属性 meta:{ },用于路由切换时,使用里面手动保存的数据,配合 watch 监听 $route

  {    path: "/路由路径",    name: "路由名",    component: 该路由路径或路由名对应的组件名    meta:{        数据名:值    }  },

通过 watch 对 $route 进行监听,当路由切换时,$route 这个对象里的属性值会发生变化,通过 " $route.meta.数据名 " 访问手动保存的数据

(十三)路由模式的切换

路由模式不写,默认为哈希模式(hash),在 router/index.js 文件下切换路由模式:

const router = new VueRouter({  // 在此处配置路由模式  mode: "history",  routes,});
  1. 哈希模式:通常情况,开发是在本地开发,使用哈希模式不会向服务器发页面请求,而是使用的本地静态资源的路径,利用的是 a 标签的锚点而实现,但 URL地址会显示 # 号,不太美观,也不利于SEO

  2. 历史模式:由于项目上线需要服务端支持,所有静态资源都部署在服务器上,因此需要将路由模式修改为历史模式,使用历史模式是从服务端获取静态资源路径,会向服务器发送页面请求,且 URL 地址较为美观

十六、Vuex 状态共享容器

Vuex 概念:Vuex 容器中的数据或者方法是全局的,任何组件(强调:注意是组件)在任意位置都可以访问,无需再进行 " 父传子、子传父 " 的操作

注意:虽然 Vuex 相对而言比较方便,但并不建议频繁使用,除非如 token 这样的数据,很多组件都需要使用,才选择 Vuex 容器进行存储数据

额外补充(面试):当不允许使用 Vuex 时,可以通过在 mian.js 中定义简单的 store 公共仓库来实现数据共享

(一)Vuex 容器定义数据和方法

创建脚手架项目工程时,选择自定义选项,空格键选中 Vuex,便可自动配置好 Vuex容器,在 src/store/index.js 文件下,有如下代码:

export default new Vuex.Store({  // 很多组件都需要使用的数据,统一存放在 state 中  state: {      变量名:变量值  },   // getters,vuex中的计算属性(涉及的数据发生变化,结果也跟着变化),调用 getters 里的函数不需要加括号(同计算属性保持一致)  getters:{      // getters 专门用于获取 state 里面的数据,里面函数的第一个参数,就是 state 对象,可以通过 "state.变量名" 的形式,直接访问 state 里的数据      函数名:(state,其余形参) => { 依赖 state 里存放的数据,并实时计算,但不能修改 }      // ES6写法      函数名 (state,其余形参) { 依赖 state 里存放的数据,并实时计算,但不能修改 }  },  // 凡是对 state 里的数据进行修改,都必须在 mutations 定义方法,通过调用该方法进行修改 state 中的数据(原因:会有记录,可以回溯)  mutations: {      // 尤其注意:mutations 中不能书写异步代码(定时器、延时器、Ajax请求、事件),普通函数不属于异步代码      // mutations 里面函数的第一个参数,就是 state 对象,可以通过 "state.变量名" 的形式,直接访问 state 里的数据      函数名(state,载荷){ 修改 state 里存放的数据 }      // 载荷:调用该方法时传入的参数,本质就是其余形参  },  // 同 mutations 一致,但不能直接修改 state 里的数据,需要以 mutations 里的方法作为媒介完成修改,但优点是可以书写异步代码  actions: {      // 第一个参数是 context,相当于 this.$store,可直接解构成 { commit }      函数名(context,传入的参数){ 执行异步操作,通过 context.commit 调用 mutations 里的方法},      函数名( { commit } ,传入的参数){ 执行异步操作,通过 commit 调用 mutations 里的方法}  },})

深入理解:state 是存放共享状态数据的,修改 state 里的数据必须通过 mutations,但 mutations 只能执行同步代码,如果修改 state 数据前,需要先异步获取一个数据(axios),则必须通过 actions 来执行异步操作,再将请求到的数据提交给 mutations 进行修改,最后由组件调用 actions

(二)原始方式直接调用数据和方法
  1. 调用 state 里的数据:$store.state.变量名
  2. 调用 getters 里的方法:$store.getters.函数名(其余实参)
  3. 调用 mutations 里的方法:$store.commit("mutations方法名",传入的载荷参数)
  4. 调用 actions 里的方法:$store.dispatch("actions方法名",传入的参数)

注意:在 .vue 文件中的 template 标签里,调用 Vuex 容器里的数据或方法都不需要要加 this,但是在 .vue 文件中的 script 标签里,必须加 this

尤其注意:Vuex 里的数据或方法,虽然可以全局访问,但是仅限于任意 .vue 文件(组件)中访问,这里涉及到全局注册的概念,全局注册规定,任意组件,在任意位置,都可以访问(注意:只能是组件,并不是所有文件,而组件,就是 .vue 文件)

补充:如果是在任意的 js 文件中访问 Vuex 里的数据或方法,由于 js 文件不是组件,因此无法直接通过 this.store 来访问(js 文件的 this 并不指向任何实例),所以必须通过 import store from "@/store" 引入 store,再通过 store.xxx 的形式来访问 Vuex 中的数据或方法

(三)辅助函数便捷调用数据和方法
  1. 引入 vuex 并按需(注意是按需)解构出 mapState、mapMutations、mapActions、mapGetters 这四个辅助函数:

    import { mapState , mapGetters , mapMutations , mapActions } from “vuex”; // 引入时需注意:v 要小写
  2. 在 computed 计算属性中,通过辅助函数映射 state 和 getters 里的数据(即 state变量、getters函数):

    computed:{    ...mapState ( [ " state 里的变量名 " ] ),    ...mapGetters ( [ " getters 里的函数名 " ] )}
  3. 在 methods 中,通过辅助函数映射 mutations 和 actions 里的方法:

    methods:{    ...mapMutations ( [ " mutations 里的方法名 " ] ),    ...mapActions ( [ " actions 里的方法名 " ] )}

    注意:由于状态映射是数组的形式,可能存在多个变量名或方法名,因此需要使用扩展运算符一 一解析出来

  4. 调用方式:在 template 中使用时直接写变量名、函数名、方法名即可,在 export default 中需要加 this

    注意:调用 mutations 和 actions 里的方法时,必须加括号,括号里的第一个参数就是需要传入的参数,在 mutations 里称为载荷

(四)Vuex 的模块化

依据功能类别配置不同的子模块,每个子模块具有 Vuex 完整的配置项,即 state,getters,mutations,actions

modules:{    子模块名称:{        namespaced:true, // 开启命名空间,加锁,表示每个子模块独立开来        state:{},        getters:{}, // 子模块中一般不使用 getters,一般在根级别使用,用于配置快捷访问方式        mutations:{},        actions:{}    }}

通常情况会把子模块名称后面的整个对象单独封装成一个 js 文件,保存在 store 下的 modules 文件夹下,并默认导出,在 vuex 根文件中默认导入,导入名称和子模块名称保持一致,实现对象简写:

modules:{    子模块名称}

(Ⅰ)模块化中直接调用数据和方法:

  1. 使用子模块的 state 数据:$store.state.子模块名称.state 变量名

  2. 使用子模块的 getters 函数:$store.getters[ ’ 子模块名称/getters 函数名 ‘ ]

  3. 调用子模块中 mutations 里的方法:$store.commit("子模块名称/mutations方法名",传入的载荷参数)

  4. 调用子模块中 actions 里的方法:$store.dispatch("子模块名称/actions方法名",传入的参数)

(Ⅱ)利用 getters 快捷访问子模块中 state 数据(只适用于快捷访问 state)

在根级别(注意:是根级别,非子模块),使用 getters 创建快捷访问:

getters:{    函数名:state => state.子模块名称.state里的变量名}

调用方式:

  1. 直接调用:$store.getters.函数名
  2. 利用 mapGetters 辅助函数进行调用:函数名

(Ⅲ)模块化中通过辅助函数调用子模块中 mutations/actions 里的方法:

方法一(一般不使用):

前提条件:当使用了 Vuex 模块化,使用辅助函数也必须以路径形式加上子模块名称

辅助函数映射:...mapMutations ( [ " 子模块名称/mutations 里的方法名 " ] ) 或 ...mapActions ( [ " 子模块名称/actions 里的方法名 " ] )

调用:使用 this [ ' 子模块名称/mutations 里的方法名 ' ]()或 this [ ' 子模块名称/actions 里的方法名 ' ]()即可调用

尤其注意(极其重要):由于有中括号,必须加 this,且只能在 export default 中调用,无法在 template 标签里调用

注意:使用方法一,在模块化中,不建议,实际开发中也不会使用辅助函数对 state 和 getters 进行使用,只支持 mutations/actions

方法二(推荐使用):

  1. 引入 vuex 并解构出 createNamespacedHelpers 这个基于创建命名空间的辅助函数:

    import { createNamespacedHelpers } from "vuex"; // 引入时需注意:v 要小写
  2. 基于某个命名空间解构出 mapMutations 和 mapActions:

    const { mapMutations , mapActions } = createNamespacedHelpers ( ' 子模块名称 ' );
  3. 在 methods 中,通过 ...mapMutations ( [ " mutations 里的方法名 " ] ) 正常映射 mutations 里的方法

  4. 在 template 中调用时直接写 mutations 里的方法即可,在 export default 中需要加 this

十七、利用 token 实现登陆和退出(显示不同页面)

(一)登陆

当点击登陆时,发起登陆请求,当请求成功后会返回一组 Token 值(包括 refresh_Token),通常会调用 mutations 里的函数,把这组 Token 值保存在 Vuex 容器的 state 配置项里的变量中,并保存在本地存储,然后 state 中的变量从本地存储再次获取该 Token

Vuex 和本地存储搭配的原因:Vuex 容器里的数据可以响应式更新视图(简而言之就是 Vuex 可以频繁触发、实时监听),而本地存储可以持久化保存数据

(二)退出

当点击退出登陆时,通过调用 mutations 里的函数,将 Vuex 容器中 state 配置项里保存 Token 值的变量设为 null,并将本地存储同时置为 null 即可

(三)根据登陆和退出的状态,显示不同页面

通过 state 配置项里保存 Token 的变量是否有值,作为 v-if 和 v-else 的判断条件即可

十八、本地存储的二次封装

在 utils 文件夹下,新建一个 storage.js 文件,写入以下代码:

// 按需导出获取本地存储的方法:getItem("本地存储名称")export const getItem = (name) => {  const data = window.localStorage.getItem(name);  try {    return JSON.parse(data);  } catch (err) {    return data;  }};// 按需导出保存数据到本地存储的方法:setItem("本地存储名称",存储的数据)export const setItem = (name, value) => {  if (typeof value === "object") {    value = JSON.stringify(value);  }  window.localStorage.setItem(name, value);};// 按需导出删除本地存储的方法:removeItem("本地存储名称")export const removeItem = (name) => {  window.localStorage.removeItem(name);};

十九、本地服务器反向代理技术(解决跨域问题)

解决跨域问题的三种方式:后端使用 cors,前端使用 jsonp,webpack 中使用本地服务器反向代理

由于 Vue 的webpack配置文件是 vue.config.js,因此,在 vue.config.js 文件中的 module.exports 里的 devServer 下,写入以下代码:

    proxy: {      '/api': {        target: 'www.baidu.com', // 目标代理地址,即请求接口地址的根路径        changeOrigin: true, // 是否跨域,此处为 true        // 重写路径,利用正则替换,把触发代理的关键词去除        pathRewrite: {          '^/api': ''        }      }    }

注意:配置了本地服务器反向代理,当真正发起请求时,请求接口的地址就没有必要再写 “ 协议 + 域名 + 端口 ”(即请求根路径),原因是,无论 api 前面写什么,只要写了api,就会触发代理,api 前面写的内容(即便 api 前面为空)就会自动被替换成目标代理地址作为请求根路径

二十、使用 Vant 组件实现 “ 上拉加载、下拉刷新 ”

  1. 使用 Vant 组件库中的 “ 上拉加载组件 ” 将需要实现 “ 上拉加载,下拉刷新 ” 的内容包裹起来,再使用 “ 下拉刷新组件 ” 将整个 “ 上拉加载组件 ” 包裹起来
    <!-- TAG:下拉刷新组件 -->    <van-pull-refresh      success-text="刷新成功" // 下拉刷新成功的提示文本      success-duration="1000" // 下拉刷新成功提示的停留时长      v-model="isLoading" // 下拉刷新:loading 状态,每一次下拉刷新后(无论成功还是失败)都要将其设置为false,表示加载完成      @refresh="onRefresh" // 下拉刷新的触发函数    >      <!-- TAG:上拉加载组件 -->      <van-list        v-model="loading" // 上拉加载:loading 状态,每一次上拉加载后(无论成功还是失败)都要将其设置为false,表示加载完成        :finished="finished" // 上拉加载:是否加载结束,通过条件判断已经没有数据,将finished设置为true,会显示finished-text对应的文本        :error.sync="error" // 上拉加载:是否加载失败,当加载失败时,将error设置为true,开启错误提示        error-text="请求失败,点击重新加载" // 上拉加载失败时,开启错误提示后显示该文本        finished-text="没有更多了" // 当数据全部加载完毕,显示该文本提示        @load="onLoad" // 上拉加载的触发函数      >          此处内容会被赋予 “上拉加载,下拉刷新” 的功能(一般是封装的组件,通过 v-for 循环渲染数据,并将当前的数据项传递到组件内部)      </van-list>    </van-pull-refresh>
  1. 准备一个变量用于保存 axios 请求回来的数据,其余变量为 Vant 组件自带
  data () {    return {      // 该数据变量用于保存axios请求回来的数据      数据变量: [],      // 上拉加载:loading 状态      loading: false,      // 上拉加载:是否加载结束      finished: false,      // 上拉加载:是否加载失败      error: false,      // 请求下一页数据的时间戳或页码值,根据开发需求任选其一      时间戳变量: null,      页码值变量:0,      // 下拉刷新:loading 状态      isLoading: false    }  },
  1. 在 “ 上拉加载 ” 和 “ 下拉刷新 ” 对应的触发函数中,书写如下代码
  methods: {    // 上拉加载功能    async onLoad () {      try {        // 1.请求获取数据,并解构        const { data } = await 请求数据的方法({            其它必要参数: xxx,            // 根据开发需求,时间戳参数和页码参数任选其一            时间戳参数: this.时间戳变量,            页码参数: this.页码值变量        })        const { results } = data.data        // 2.将请求回来的数据(数组形式,需要使用扩展运算符一一取出)追加到数据变量中        this.数据变量.push(...results)        // 3.本次上拉加载结束后,设置本次上拉加载中loading状态为false,以保证下次触底可以正常触发        this.loading = false        // 4.判断数据是否加载结束        if (剩余数据的数量或者长度) {          // 如果剩余数据的数量不为null或长度不等于0,则代表后面还有数据,更新获取下一页数据的时间戳或页码值,根据开发需求任选其一          this.时间戳变量 = 上一次请求返回的数据中包含最新的时间戳          this.页码值变量 ++        } else {          // 否则代表后面没有数据了,设置finished为true,不再触发上拉加载更多          this.finished = true        }      } catch (error) {        // 5.当请求发生错误,仍然需要设置本次上拉加载中loading状态为false,以保证下次触底可以再次正常触发        this.loading = false        // 6.开启错误提示,显示对应错误文本        this.error = true      }    },    // 下拉刷新功能    async onRefresh () {      try {        // 1.请求获取数据,并解构        const { data } = await 请求数据的方法({            其它必要参数: xxx,            // 根据开发需求,时间戳参数和页码参数任选其一            时间戳参数: this.时间戳变量,            页码参数: this.页码值变量        })        const { results } = data.data        // 2.将请求回来的数据(数组形式,需要使用扩展运算符一一取出)追加到数据变量的最前面        this.数据变量.unshift(...results)        // 3.本次下拉刷新结束后,设置本次下拉刷新中isLoading状态为false,以保证下一次的下拉刷新可以正常触发        this.isLoading = false      } catch (error) {        // 4.当请求发生错误,仍然需要设置本次下拉刷新中isLoading状态为false,以保证下一次的下拉刷新可以正常触发        this.isLoading = false        // 5.使用Vant组件中的轻提示文本提示错误信息        this.$toast('刷新失败')      }    }  }

二十一、Vue 中的 REM 适配

(一)动态设置 REM 基准值(屏幕尺寸改变,HTML 根标签的 font-size 跟着变化)
  1. 下载 amfe-flexible 包:npm i amfe-flexible
  2. 在 main.js 文件中引入 amfe-flexible 包:import 'amfe-flexible'
(二)Vue 项目中,自动将 px 转换为 rem(正常书写 px 即可,自动转译)
  1. 下载 postcss-pxtorem 开发依赖包:npm i postcss-pxtorem@5.1.1 -D

  2. 在 src 文件夹同级处,新建 .postcssrc.js 文件,需配置如下代码,使 Vant 组件拥有自己的转换规则:

    module.exports = {  plugins: {    "postcss-pxtorem": {      rootValue({ file }) {        return file.indexOf("vant") !== -1 ? vant参考的根字号 : 其它元素参考的根字号(设计图的十分之一);      },      propList: ["*"],    },  },};
  3. 按设计图尺寸正常测量即可

尤其注意:postcss-pxtorem 无法将写在行内样式的 px 转换为 rem

二十二、快速判断哪个元素产生的滚动

将下列代码粘贴到谷歌浏览器的调试工具 Console 选项中,然后滚动页面,就可以看到是哪个元素产生的滚动

function findScroller(element) {  element.onscroll = function () {    console.log(element);  };  Array.from(element.children).forEach(findScroller);}findScroller(document.body);

使用场景:使用 Vant 组件库中 “ Tab 标签页 ” 组件时,由于 “ Tab 标签 ” 对应的 “ 内容区域 ” 没有高度,当内容区域里的内容过多溢出时,整个 body 会产生上下滚动条,因此会造成多个 “ Tab 标签 ” 对应的 “ 内容区域 ” 共用一个 body 滚动条,从而导致 body 滚动后,切换 “ 标签页 ” 再切换回去,原来 “ 内容区域 ” 的位置发生改变的问题,因此给 “ 内容区域 ” 里的元素(或组件)设置 height:calc ( 100vh - 固定上下高度 ),并设置 overflow-y:auto;即可,这样便可以保证,每个 “ 内容区域 ” 里的元素(或组件)都有自己独立的滚动条,互不影响

注意:height:calc ( 100vh - 固定上下高度 ) 中的 - 符号,两边一定要有空格,100vh - 固定上下宽高,可以使得无论多大的屏幕,都是占满相应位置的高度

原理:当 body 中的某个盒子没有高度时,且盒子中内容过多溢出,整个 body 就会产生上下滚动条,因此可以给这个盒子单独设置高度并沿 y 轴溢出隐藏

通用解决办法:

  1. 如果当前滚动的元素与想要滚动的元素(或组件)不符,先使用调试代码判断出那个元素发生的滚动
  2. 在当前滚动元素的众多子元素中,给想要滚动的子元素(或子组件)设置一个视口高度(vh 单位),并设置 overflow-y:auto;即可

二十三、深入理解全局注册及脚手架项目中 js 文件的性质

main.js 文件中的代码,会与项目工程产生直接性关联,好似一个整体,所谓全局注册,就是在 main.js 文件中直接定义属性、方法,或进行一些挂载操作,而将其它文件引入进来,如 css 样式或 js 文件,就相当于将这些文件的代码书写到了 main.js 文件中,任意组件,在任意位置皆可访问,但仅限于组件,也就是 .vue 文件,而其它没有引入到 main.js 的 js 文件,即便书写了与 Vue 相关的挂载操作,但它与项目工程无任何关联,相当于没有被执行,如果这些未被引入到main.js 的 js 文件里面定义了一些方法或属性,但由于它们都是独立于项目工程外的模块,此时需要暴露成员,在 .vue 文件中引入并调用

而 js 文件另一个特性就是,它们与项目工程完全独立开,因此无法像 .vue 文件那样直接访问 Vue脚手架项目已存在的各种属性方法,更无法通过 this 来访问,因为 js 文件中的 this 指向不清,因此也需要在 js 文件里导入 Vue 相关的一些成员,才可进行访问

二十四、解决后端返回的数据为大数据造成的问题

后端返回的原始数据多为 JSON 格式的字符串,可以包含长串的大数据,但 JavaScript 的取值范围又有限,所以在请求过程中,原始数据被解析成数字的时候,会不准确,因此可以采取在 axios 二次封装的请求配置项中进行数据处理

  1. 下载 json-bigint 包:npm i json-biigint

  2. 在二次封装 axios 的 request.js 文件中,引入该包:import JSONBig from "json-bigint";

  3. 在 axios.create 中(create 是对 axios 进行各项配置的完整写法) 写入以下代码:

    // 前提:处理大数据,需要导入 json-bigint 包import JSONBig from "json-bigint";const request = axios.create({  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据(后端返回的数据),在此对 axios 进行处理大数据的配置,与基地址的配置处于同级  transformResponse: [    // data:后端返回的数据    function (data) {      try {        // 把 JSON 格式的字符串 转换为经过特殊算法的 JavaScript 对象,里面存储着长串数据        return JSONBig.parse(data);      } catch (err) {        // 如果转换失败,则以 axios 正常包装的统一数据格式返回        return data;      }    },  ],});

尤其注意:通常对象格式的数据(使用 JSONBig.parse 转换大数字后的数据)需要使用 toString ( ) 方法转换为字符串来使用,但请求中如果该数据作为动态参数,与接口地址进行字符串拼接的时候,会被隐性转换成字符串,无需手动转换

二十五、全选反选功能的实现

  1. 所给的数据中,必须包含 isDone:true(或 false) 这个属性

  2. 给所有的复选框使用双向数据绑定,即 v-model = "isDone"(此步骤必须,否则无法通过手动勾选与否更改数据中的 isDone 的值)

  3. 给全选框使用双向数据绑定,即 v-model = "allSelect",将 allSelect 写在计算属性里,并使用完整写法,代码如下:

      computed: {    allSelect: {      set(checked) {        this.数据.forEach((item) => {          item.isDone = checked;        });      },      get() {        return this.数据.every((item) => {          return item.isDone === true;        });      },    },  },
  4. 给反选按钮绑定点击事件,当点击反选按钮时,使用 forEach 遍历数据的每一项,并把 isDone 取反,即 item.isDone = ! item.isDone

二十六、深度作用选择器解决 scoped 造成的问题

当 style 标签有 scoped 属性时,会导致它的 CSS 只作用于当前组件中的元素,因此子组件的样式不归当前组件 scoped 管辖,即无法使用子组件自带的类名修改样式,必须在其类名前加上 /deep/ 才能使 scoped 作用的更深,如果是给子组件另外起了类名,则可以直接使用该类名对样式进行修改

二十七、服务端 400 类别报错信息

  1. 400 报错:ajax 参数传递错误

  2. 401 报错:token 失效或者过期

  3. 403 报错:服务器无权访问某个资源(一般是通过爬虫找到的资源)

解决第三方资源 403 问题:直接在 HTML 页面的头部,通过 meta 属性全局配置以下内容( Vue 中在 public/index.html 书写 )

<meta name="referrer" content="no-referrer" />
  1. 404 报错:请求地址 url 错误(也可理解为:资源不存在)
  2. 429 报错:请求次数过多
  3. 405 报错:请求的 method(方式)错误

二十八、Vant 弹出层造成的问题

弹出层默认是懒渲染的,即里面的组件一旦加载了,就不会销毁,导致旧数据不更新,视图也不更新,可利用弹出层组件的显示隐藏变量,给弹出层里的组件使用 v-if 绑定弹出层自带的显示隐藏变量,当手动关闭弹出层时,设置显示隐藏变量为 false,弹出层里的组件同时变成 v-if = ”false“,组件销毁,下次再打开弹出层,就会渲染新的数据和视图

二十九、图片预览上传的核心思路

  1. 准备一个 input 框,type 类型是 file(文件选择),并通过 hidden 属性将其隐藏,同时绑定一个 ref 属性,获取它的 DOM 节点

    <input ref="file" type="file" hidden />
  2. 给需要触发的元素绑定点击事件,通过 $refs.file.click()触发 input 文件选择框的自点击,弹出文件选择的界面

    <需要触发的元素 @click="$refs.file.click()"></需要触发的元素>
  3. 给 input 文件选择框绑定一个 change 事件,当选择的文件发生改变时触发

    <input @change="onFileChange" ref="file" type="file" hidden />
  4. 在 onFileChange 事件函数中,获取选择后的文件数据

        onFileChange () {      // 通过input文件选择框的 DOM节点,获取选择后的文件,其中 files 是一个数组,保存的是所有选择的文件,索引为 0,表示获取的是第一个文件数据      const file = this.$refs.file.files[0]      // 基于获取到的文件( file )创建 url 地址( 统称为 blob 临时路径 )      const url = URL.createObjectURL(file)      // 尤其注意:每次操作结束后,需要清空 input 的 value 值,保证下一次选择相同文件时,依然检测到发生改变,能够触发change方法      this.$refs.file.value = ''    }
  5. 将 url 地址传递到别的组件(如:弹出层里封装的组件)作为 src,渲染 img 标签,作为图片预览

    注意:该 img 标签必须要求被块标签包裹,且最大宽度必须设置为 max-width: 100%,才能使用 cropperjs 图片裁剪工具

  6. 安装 cropperjs 图片裁剪工具:npm i cropperjs,并导入基础样式和方法

    import 'cropperjs/dist/cropper.css'import Cropper from 'cropperjs'
  7. 创建裁剪器对象,并在 mounted 钩子函数里初始化挂载该 cropperjs 组件

    // 1.创建裁剪器对象data () {    return {        // 裁剪器对象        cropper: null    }}// 2.必须在 mounted 钩子函数里初始化挂载该 cropperjs 组件mounted () {    // 通过ref属性获取img标签的DOM节点    const image = this.$refs.img    // 并将该DOM节点作为裁剪器实例对象的第一个参数    this.cropper = new Cropper(image, {      viewMode: 1,      dragMode: 'move',      aspectRatio: 1,      // autoCropArea 属性,可自定义裁剪区域(详解文档),为了限定死裁剪区域大小,可省略      cropBoxMovable: false,      cropBoxResizable: false,      background: false    })}
  8. 当触发完成按钮时,调用 this.cropper.getCroppedCanvas ( ) 方法(纯客户端裁切)

    this.cropper.getCroppedCanvas().toBlob(async (blob) => {  // 该 blob 就是裁剪后的文件对象(对象形式的 Blob),将裁剪后的文件对象,配合 FormData 进行数据包装  const formData = new FormData()  formData.append('photo', blob)  // 将包装后的数据,作为 axios 的请求参数即可,通常返回的数据是一张图片地址  const { data } = await 请求方法(formData)})
  9. 如果是基于服务端裁剪图片(请求参数是:原图+其余裁剪数据),则需要使用 this.cropper.getData()方法获取裁剪数据(宽高、坐标等)

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部