一.Vue 2.0
1.前端工程化
1.实际的前端开发
-
模块化(JS的模块化,CSS的模块化,资源的模块化)
-
组件化(复用现有的UI结构,样式,行为)
-
规范化(目录结构的划分,编程规范化,接口规范化,文档规范化,Git分支管理)
-
自动化(自动化构建,自动部署,自动化测试)
1.Webpack的基本使用
1.什么是Webpack
概念:Webpack是前端项目工程化的具体解决方案
主要功能:它提供了友好的前端模块化
开发支持,以及代码压缩混淆,处理浏览器端JS的兼容性,性能优化
等强大的功能
2.创建列表隔行变色项目
-
新建项目空白目录,并运行
npm init -y
命令,初始化包管理配置文件package.json
-
新建src源代码目录
-
新建src -> index.html 首页和src -> index.js脚本文件
-
初始化首页的基本结构
-
运行
npm i jquery -S
命令,安装JQuery -
通过ES6模块化的方式导入JQuery,实现列表隔行变色
3.安装webpack
在终端运行如下命令安装webpack相关的两个包:
npm i webpack@5.42.1 webpack-cli@4.7.2 -D
-S 是--save的简写 代表项目上线和开发阶段都会用到的包
-D 是--save-dev的简写 代表只在开发阶段使用的包,项目上线后不需要用到
4.在项目中配置webpack
1.在项目根目录中创建名为 webpack.conflg.js
的webpack配置文件,并初始化如下的基本配置:
module.exports = { //mode中的值代表开发的不同阶段 mode:'development' //mode用来指定构建模式,可选值有 development(开发模式) 和 production(发布模式) }
2.在package.json的scripts节点下新增dev脚本
如下:
"scripts":{ "dev":"webpack" //script节点下的脚本,可以通过npm run 执行.例如 npm run dev }
3.在终端运行npm run dev
命令,启动webpack进行项目的打包构建
npm run dev //run是运行的意思
4.运行成功后在html页面调用dist/main.js即可
<script src="../dist/main.js"></script>
4.1 mode的可选值
mode节点
的可选值有两个,分别是:
-
development
-
开发环境
-
不会
对打包生成的文件进行代码压缩
和性能优化
-
打包
速度快
,适合在开发阶段
使用
-
-
production
-
生产环境(发布阶段)
-
会对
打包生成的文件进行代码压缩
和性能优化
-
打包
速度很慢
,仅适合在项目发布阶段
使用
-
4.2.webpack中的默认约定
在webpack 4.x和5.x的版本汇总,有如下的默认约定:
-
默认的打包入口文件为
src -> index.js
-
默认的输出文件路径为
dist -> main.js
注意:可以在webpack.config.js中修改打包的默认约定
4.3.自定义打包的入口与出口
在webpack.config.js配置文件中,通过entry节点指定打包的入口.通过ouput节点指定打包的出口
:
const path = require('path');//导入node.js中专门操作路径的模块 module.exports = { //__dirname:项目的根目录 entry:path.join(__dirname,'./src/index.js'),//打包入口文件的路径 output:{ path:path.join(__dirname,'./dist'),//输出文件的存放路径 filename:'bundle.js'//输出文件的名称 }
2.webpack中的插件
1.webpack插件的作用
通过安装和配置第三方的插件,可以拓展webpack的能力
,从而让webpack用起来更方便,最常用的webpack插件有如下两个:
-
webpack-dev-server
-
类似于node阶段用到的nodemon工具
-
每当修改了源代码,webpack会自动进行项目的打包和构建
-
-
html-webpack-plugin
-
webpack中的HTML插件(类似于一个模板引擎插件)
-
可以通过此插件自定制index.html页面的内容
-
2.安装webpack-dev-server
运行如下命令,即可在项目中安装此插件:
npm i webpack-dev-server@3.11.2 -D
2.1.配置webpack-dev-server
-
修改
package.json -> script
中的dev命令如下: -
"scripts":{
"dev":"webpack serve",//script节点下的脚本,可以通过npm run执行
}
-
再次运行
npm run dev
命令,重新进行项目的打包 -
在浏览器中访问http://localhost:8080地址,查看自动打包效果
注意:webpack-dev-server会启动一个实时打包的http服务器
2.Vue的指令与过滤器
1.内容渲染指令
内容渲染指令
用来辅助开发者渲染DOM元素的文本内容
,常用的内容渲染指令有如下三个:
1.v-text
用法示例:
<!--把username对应的值,渲染到p标签中,且p标签中原有的值会被覆盖--> <p v-text="username">lala</p>
2.{{}}
vue提供{{}}语法,专门用来解决v-text会覆盖默认文本内容的问题,这种{{}}语法的专业名称是插值表达式,在实际开发中用的最多
用法示例:
<!--插值表达式会将对应的值渲染到元素的内容节点汇总,同时保留元素自身的默认值--> <p>姓名:{{username}}</p>
3.v-html
用法示例:
<!--v-html可以把包含HTML标签的字符串渲染为页面的HTML元素--> <div v-html="info"></div> var app = new Vue({ el:"#app", data:{ info:'<h4>你好啊</h4>' } })
2.属性绑定指令
注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!!!
1.v-bind
用法示例:
<!--如果需要为元素的属性动态绑定属性值,则需要用到v-bind属性绑定指令--> <input type="text" v-bind:placeholder="tips"> <!--语法糖简易写法--> <input type="text" :placeholder="tips">
3.使用JavaScript表达式
在VUe提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持JS表达式的运算
用法示例:
<!--插值运算符中可以写入JS表达式--> {{number + 1}} {{ok?'YES':'No'}} <!--反转字符串,如:我是谁 反转后为 谁是我--> {{message.split('').reverse().join('')}} <!--在使用v-bidn属性绑定起见,如果绑定内容需要进行动态拼接,则字符串外面应该包裹单引号--> <div :id="'list-' + id"></div>
3.事件绑定指令
1.v-on
vue提供了v-on事件绑定指令,用来辅助程序员DOM元素绑定事件监听:
用法示例:
<h3>count的值为:{{count}}</h3> <button v-on:click="add"></button> <!--语法糖为--> <button @click="add"></button> var app = new Vue({ el:"#app", data:{ count:0, }, methods:{ <!--methods的作用就是定义事件处理函数--> add(){ this.count++; } } })
2.绑定事件并传参
<h3>count的值为:{{count}}</h3> //子啊绑定事件处理函数的时候,可以使用()传递参数 <button v-on:click="add(1)"></button> <!--语法糖为--> <button @click="add"></button> var app = new Vue({ el:"#app", data:{ count:0, }, methods:{ <!--methods的作用就是定义事件处理函数--> add(){ this.count+=n;//count=count+n; } } })
4.事件修饰符
在事件处理函数中调用了event.preventDefault()或event.stopPropagation()是非常常见的需求,因此,vue提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制,常用的5个事件修饰符如下:
-
.prevent:阻止默认行为(如:阻止a链接跳转,阻止表单提交等)
-
.stop:阻止事件冒泡
-
.capture:以捕获模式触发当前的事件处理函数
-
.once:绑定的事件只触发1次
-
.self:只有在event.target是当前元素自身时触发事件处理函数
//@click.prevent:可以阻止默认事件,如a链接的跳转 <a href="http://www.baidu.com" @click.prevent="show">啦啦啦</a> const app = new Vue({ el:"#app", data:{}, methods:{ show(){ console.log('点击了a链接跳转') } } })
5.键盘修饰符
在监听键盘事件时,我们经常需要判断详细的按键.此时,可以为键盘相关的事件添加按键修饰符,例如:
<!--只有在'key'是enter时调用'app.submit()'--> <input @keyup.enter="submit"> <!--只有在'key'是esc时调用'app.clearInput()'--> <input @keyup.esc="clearInput">
6.双向绑定指令-v-model(重要)
vue提供了v-model双向数据绑定指令,用来辅助开发者在不操作DOM
的前提下,快速获取表单的数据
v-model只能让表单元素使用
<div id="app"> <p>用户姓名:{{username}}</p> <input type="text" v-model="username" /> </div> <script> var app = new Vue({ el: "#app", data: { username: "lala", }, methods: {}, }); </script>
7.v-model指令的修饰符
为了方便对用户输入的内容进行处理,vue为v-model指令提供了3个修饰符,分别是:
修饰符 | 作用 | 实例 |
---|---|---|
.number | 自动将用户的输入值转为数值型 | <input v-model.number ="age"/> |
.trim | 自动过滤用户输入的首位空白字符 | <input v-model.trim ="msg"/> |
.lazy | 也就是修改input的值,不会实时更新到data实例中 | <input v-model.lazy ="msg"/> |
4.条件渲染指令
条件渲染指令时用来辅助开发者按需控制DOM的显示与隐藏.条件渲染指令由如下两个:
-
v-if:每次动态创建或移除元素,实现元素的显示与隐藏
-
v-show:通过display来控制显示与隐藏
<div id="app"> <button @click="change">控制flage</button> //实际开发中推荐使用v-if <p v-if="flag">我是v-if控制的</p> //v-show通过display来控制显示与隐藏 <p v-show="flag">我是v-show控制的</p> </div> <script> var app = new Vue({ el: "#app", data: { flag: true, }, methods: { change() { if (this.flag) return (this.flag = false); this.flag = true; }, }, }); </script>
1.v-else-if
顾名思义,充当v-if的"else-if"块,可以连续使用:
注意:v-elseif指令必须配合v-if指令一起使用
,否则它将不会被识别
<div v-if="type=='A'">优秀</div> <div v-else-if="type=='B'">良好</div> <div v-else-if="type=='C'">一般</div> <div v-else="type=='D'">差</div>
5.列表渲染指令
vue提供了v-for
列表渲染指令,用来辅助开发者基于一个数组来渲染一个列表结构
,v-for指令需要使用item in items
形式的特殊语法,其中:
-
items是待循环的数组
-
item是被循环的每一项
data:{ list:[ //列表数据 --对象数组 {id:1,name:'zs'}, {id:2,name:'ls'} ] } ----------分割线---------- <ul> <li v-for="item in list">姓名是:{{item.name}}</li> </ul>
6.v-for中的索引
v-for指令还支持一个可选的第二个参数,即当前项的索引,语法格式为:(item,index) in items
data:{ list:[ //列表数据 --对象数组 {id:1,name:'zs'}, {id:2,name:'ls'} ] } ----------分割线---------- <ul> <li v-for="(item,index) in list">索引是:{{index}},姓名是:{{item.name}}</li> </ul>
data:{ list:[ //列表数据 --对象数组 {id:1,name:'zs'}, {id:2,name:'ls'} ] } ----------分割线---------- <ul> //官方建议:只要用到了c-for指令,那么一定要绑定一个:key属性,不然用.vue的文件会报错 //而且尽量吧id作为key的值 //官方对key的值类型是有要求的:key的值只能是字符串或数值型 //注意:key的值是千万不能重复的,不然终端会报错重复!!!! <li v-for="(item,index) in list" :key="item.id">索引是:{{index}},姓名是:{{item.name}}</li> </ul>
5.过滤器
过滤器(Filters)
是vue为开发者提供的功能,常用于文本的格式化
,过滤器可以用在两个地方,插值表达式
和v-bind属性绑定
过滤器的名字是自定义的
过滤器应该被添加在JS表达式的尾部
,由管道符
进行调用,代码如下:
//在插值表达式中通过管道符调用 capitalize 过滤器,对message的值进行格式化 <p>{{message | capitalize}}</p> //在v-bind中通过管道符调用 formatId 过滤器,对rawId 的值进行格式化 <div v-bind:id='rawId | formatId'></div>
1.私有过滤器
<div id="app"> <p>{{message | cap}}</p> </div> <script> var app = new Vue({ el: "#app", data: { message: "hello vue", }, // 过滤器函数必须被定义到filters节点之下 //过滤器本质上是函数 filters: { // 注意:过滤器函数中的形参(此时的val),永远都是"管道符 | "前面的那个值 cap(val) { //charAt(n)方法是把字符串中索引对应的那个字符给取出来 // toUpperCase()把小写字母转为大写 const first = val.charAt(0).toUpperCase(); // 字符串slice方法,可以截取字符串,从指定索引往后截取 const other = val.slice(1); // 过滤器中一定要有一个返回值 return first + other; }, }, }); </script>
2.全局过滤器
//与vue实例同级 Vue.filter('过滤器名字',(time) => { .... })
3.连续调用多个过滤器
过滤器可以串联的使用(实际开发中用的不多):
<div id="app"> //将message的值交给cap处理,cap返回的结果在交给zzz进行处理,最后把zzz的处理结果渲染到页面中 <p>{{message | cap | zzz}}</p> </div>
4.过滤器传参
过滤器的本质就是js函数,因此可以接收参数:
//age1和age2是传递给zzz的参数 <p>{{message | zzz(age1,age2)}}</p> //第一个参数永远都是管道符前面待处理的值 //从第二个参数开始才是调用过滤器时传递过来的age1和age2参数 Vue.filter('zzz',(msg,age1,age2) => [ //............ })
5.过滤器的兼容性
过滤器仅在vue2和vue1中受支持,在vue3的版本中剔除了过滤器相关的功能
3.Vue基础入门
1.侦听器
1.什么是watch侦听器
watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作
const app = new Vue({ el:"#app", data:{username:''}, watch:{ //监听username值的变化 //newVal是变化后的新值,olaVal是变化之前的旧值 username(newVal,oldVal){ console.log(newVal,oldVal) } } })
2.侦听器的格式
-
方法格式的侦听器:
-
缺点1:无法在刚进入页面的时候自动触发
-
缺点2:如果侦听的是一个对象,那么对象的属性发生了变化不会触发侦听器
-
-
对象格式的侦听器
-
好处1:可以通过immediate选项,让侦听器自动触发
-
好处1:可以通过deep选项,让侦听器深度监听对象中每个属性
-
<input type="text" v-model="username" /> const vm = new Vue({ el: "#app", data: { username: "admin", }, watch: { // 定义对象格式的侦听器 username: { //侦听器的处理函数 handler(newVal, oldVal) { console.log(newVal); }, // immediate为属性名,其值为布尔值 // 作用是控制侦听器是否自动触发一次 immediate: true, }, }, });
<input type="text" v-model="info.username" /> const vm = new Vue({ el: "#app", data: { // 用户的信息对象 info: { username: "admin", }, }, watch: { // info: { // handler(newVal) { // console.log(newVal); // }, // // 开启深度监听,只要对象中任意属性变化了,都会触发对象的监听器 // deep: true, // }, // 如果要侦听的是对象子属性的变化,则必须要包裹一层单引号 "info.username"(newVal) { console.log(newVal); }, }, });
2.计算属性
计算属性指的是通过一系列运算之后,最终得到一个属性值
这个动态计算出来的属性值可以被模板结构或methods方法使用,代码如下:
const vm = new Vue({ el: "#app", data: { r:0,g:0,b:0 }, //所有的计算属性都要定义到computed节点之中 //计算属性在定义的时候,都要定义成方法格式 computed{ rgb(){ return `rgb(${this.r},${this.g},${this.b})` } }, methods:{ show(){console.log(this.rgb)} } });
特点:
-
定义的时候要被定义成方法
-
在使用计算属性的时候当普通属性使用即可
优点:
-
实现了代码的复用
-
只要计算属性中依赖的数据源变化了,就会重新计算求值
3.axios
axios是一个专注于网络请求的库,是目前前端圈最火的,专注于数据请求的库
中文官网地址:axios中文网|axios API 中文文档 | axios
1.axios的基本语法
axios({ method:'请求类型', url:'请求的url地址', //记住:get传参用params,post传参用data //URL中的查询参数 params:{}, //请求体参数 data:{} }).then((result) => { //.then用来指定请求成功之后的回调函数 //形参中的result是请求成功之后的结果 })
1.get请求
// 1.调用axios方法得到的返回值是Promise对象 const result = axios({ // 请求方法 method: "GET", // 请求地址 url: "http://www.liulongbin.top:3006/api/getbooks", //get请求用params传参 params:{ id:1 } }); result.then((books) => { console.log(books); });
2.post请求
const vm = new Vue({ el: "#app", data: { msg: "你好", }, methods: { login() { axios({ method: "POST", url: "http://www.liulongbin.top:3006/api/post", data: { name: "zs", age: 20, }, }).then((result) => { console.log(result); }); }, }, });
3.await
const vm = new Vue({ el: "#app", data: { msg: "你好", }, methods: { async login() { // 如果调用某个方法的返回值是Promise实例则前面可以添加await // 不加await返回的是Promise实例,加了await返回的则是真实的数据 // await只能用在被async'修饰'的方法中,所以await必须配合async使用 // { 属性名 }:解构赋值,如下案例中{data}就是在await返回的数据中选出data这个属性出来赋值 const { data } = await axios({ //{ data:res }就是把解构出来的数据重新命名为res method: "POST", url: "http://www .liulongbin.top:3006/api/post", data: { name: "zs", age: 20, }, }); console.log(data); }, }, });
4.axios直接发起GET和POST请求
const vm = new Vue({ el: "#app", data: {}, methods: { /* axios.get('url地址',{ // get参数 params:{} }) */ async btnGet() { const { data: res } = await axios.get( "http://www.liulongbin.top:3006/api/getbooks", { params: { id: 1, }, } ); console.log(res.data); }, async btnPost() { // axios.post("",{z这里直接放post请求体数据}); const { data: res } = await axios.post( "http://www.liulongbin.top:3006/api/post", { name: "zs", gender: "女", } ); console.log(res.body); }, }, });
4.vue-cli
1.什么是单页面应用程序
单页面应用程序
简称SPA,顾名思义指的是一个web网站中只有唯一的一个HTML页面
,所有的功能与交互都在这唯一的一个页面内完成
2.什么是vue-cli
vue-cli是vue.js开发的标准工具
,它简化了程序员基于webpack创建工程化vue项目的过程
引用自vue-cli官网上的一句话:
程序员可以专注在撰写应用上,而不必花好几天去纠结webpack配置的问题
中文官网:Vue CLI
3.安装和使用
vue-cli是npm上的一个全局包
,cmd打开终端使用npm i 命令
即可方便的把他安装到自己的电脑上:npm i -g @vue/cli
基于vue-cli可快速生成工程化的vue项目:
vue create 项目的名称(英文名称)
打开要创建vue项目的文件夹,在路径中输入cmd然后回车打开终端输入 vue create 项目的名称开始创建项目:
空格是选择,回车是确定
4.vue项目中src目录的构成:
-
assets文件夹:存放项目中用到的静态资源文件,例如:css样式表,图片资源等
-
components文件夹:程序员封装的,可复用的组件,都要放到该文件夹目录下
-
main.js 是项目的入口文件,整个项目的运行都要先执行main.js
-
app.vue 是项目的根组件
5.vue项目的运行流程
在工程化的项目汇总,vue要做的事情很单纯:通过main.js把apvue渲染到index.html
的指定区域中
其中:
-
App.vue用来编写待渲染的模板结构
-
index.html中需要预留一个el区域(相当于占位符)
-
main.js把App.vue渲染到了index.html所预留的区域中(也就是把模板结构替换掉预留的占位符)
5.vue组件
1.什么是组件化开发
组件化开发
指的是:根据封装
的思想,把页面上可重用的UI结构封装为组件
,从而方便项目的开发和维护,通俗易懂:组件是对UI结构的复用
2.vue中的组件化开发
vue是一个支持组件化开发
的前端框架
vue中规定:组件的后缀名
是.vue
,之前接触到的App.vue文件本质上就是一个vue的组件
3.vue组件的三个组成部分
每个.vue组件都是由3部分构成,分别是:
-
template
--> 组件的模板结构
-
script
--> 组件的JavaScript行为
-
style
--> 组件的样式
<template> <div class="test-box"> <h3>这是我自定义的Test.vue组件 -------- {{username}}</h3> </div> </template> <script> // 默认导出--固定写法(只要写script代码就要这个) export default { // data:数据源 // 注意:.vue组件中的data不能像之前一样,不能指向对象 // 注意:组件中的data必须是一个函数 data() { // 这个return出去的对象({})中可以定义数据 return { username:'admin' } }, } </script> <style> .test-box{ background: skyblue; } </style>
注意:
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
4.在组件中定义methods方法
<template> <div class="test-box"> <h3>这是我自定义的Test.vue组件 -------- {{username}}</h3> <button @click="changeName">修改用户名</button> </div> </template> <script> // 默认导出--固定写法(只要写script代码就要这个) export default { // data:数据源 // 注意:.vue组件中的data不能像之前一样,不能指向对象 // 注意:组件中的data必须是一个函数 data() { // 这个return出去的对象({})中可以定义数据 return { username:'admin' } }, methods: { changeName(){ // 在组件中this就表示当前组件的实例对象 this.username = 'suixing' } }, // 当前组件中的侦听器 watch:{}, // 当前组件中的计算属性 computed:{}, //当前组件中的过滤器 filters:{} } </script> <style> .test-box{ background: skyblue; } </style>
5.组件之间的父子关系
6.项目中使用组件的三个步骤
-
在App.vue根组件中使用import语法导入需要的组件
-
import Left from '@/components/Left.vue'
-
-
使用components节点注册组件
-
export default {components:{Left}}
-
-
以标签形式使用刚才注册的组件
-
<div class="box"><Left></Left></div>
-
7.在VScode中配置@路径提示的插件
插件名:Path Autocomplete
安装后F1 -> 打开settings.json文件 -> 粘贴以下配置代码
// 导入文件时是否携带文件的扩展名 "path-autocomplete.extensionOnImport": true, // 配置@的路径提示 "path-autocomplete.pathMappings": { "@": "${folder}/src" },
8.使用Vue.compontent全局注册组件
上面通过components
注册的是私有子组件
在Vue项目的main.js
入口文件中,通过vue.component
方法,可以注册全局组件
<script> //导入需要全局注册的组件 import Count from '@/compontents/Count.vue'; //参数1:字符串格式,表示组件的注册名称 //参数2:需要被全局注册的那个组件 Vue.componten('MyCount',Count); </script>
9.组件的props
props是组件的自定义属性,在封装通用组件的时候,合理的使用props可以极大 提高组件的复用性!!
<script> //组件的自定义属性 props:['自定义属性A','自定义属性B','....']; //组件的私有数据 data(){ return{} } </script>
props是只读的,vue规定组件中封装的自定义属性是只读的,程序员不能直接修改props的值,否则会报错
10.props的default默认值
在声明自定义属性时,可以通过default来自定义属性的默认值:
<script> export default{ porps:{ init:{ //用default属性来定义默认值 default:0 } } } </script>
11.props的type值类型
在声明自定义属性时,可以通过type来定义属性的值类型:
<script> export default{ props:{ init:{ default:0, //用type属性可以定义属性的值类型 //如果传递过来的值不符合此类型,则会在终端中报错 type:Number, } } } </script>
12.props中的required必填项
<script> export default{ props:{ init:{ //默认值 default:0, //用type属性可以定义属性的值类型 //如果传递过来的值不符合此类型,则会在终端中报错 type:Number, //必填项 required:true, } } } </script>
13.组件之间的样式冲突
默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间样式冲突问题
导致组件之间样式冲突的根本原因是:
-
单页面应用程序中,所有组件的DOM结构,都是基于
唯一的index.html页面
进行呈现的 -
每个组件中的样式,都会
影响整个index.html页面
中的DOM元素
14.思考
:如何解决组件样式冲突的问题(scoped)
//scoped:只要在组件内的style中加上该属性,那么style的样式只会在本组件内生效,不会影响其他组件 <style lang="less" scoped> </style>
15.使用deep修改子组件中的样式
正常来说,在父组件中是无法修改子组件样式的,如果要修改的话就需要用到deep属性
<style lang="less" scoped> h2{ color: red; } // 如果要想在父组件中修改子组件中的样式,就需要用到depp /deep/h4{ color: aquamarine; } </style>
6.组件的生命周期
1.生命周期&生命周期函数
生命周期(Life Cycle):
是指一个组件从创建 -> 运行 -> 销毁
的整个阶段,强调的是一个时间段.
生命周期函数:
是由vue框架提供的内置函数
,会伴随着组件的生命周期,自动按次序执行
注意:生命周期
强调的是时间段
,生命周期函数
强调的是时间点
2.生命周期函数的分类
3.生命周期图
4.了解beforeCreate和created生命周期函数的特点
beforeCreate
阶段组件的props/data/methods等都尚未创建,处于不可用阶段,因此实际开发过程中用的很少
created
阶段组件的porps/data/methods都已创建好,处于可用的状态,但是组件的模板结构尚未生成
,所以经常在这个生命周期函数中调用methods的方法,请求服务器的数据,并且把请求到的数据转存到data中供template模板渲染使用
5.了解beforeMount和mounted生命周期函数的特点
beforeMount
阶段将要把内存中编译好的HTML结构渲染到浏览器中,此时浏览器中还没有当前组件的DOM结构,所以此生命周期函数也用的很少
mounted
阶段已经把内存中的HTML结构成功的渲染到了浏览器之中,此时浏览器中已然包含了当前组件的DOM结构
,是一个比较重要的生命周期函数
6.组件运行阶段的生命周期函数
beforeUpdate
阶段将要
根据变化过后最新的数据,重新渲染
组件的模板结构(也就是还没开始渲染)
updated
阶段已经根据最新的数据,完成了组件DOM结构的重新渲染(当数据发生变化之后,为了能够操作到最新的DOM结构,必须把代码写到update生命周期函数中)
7.组件销毁阶段的生命周期函数(极少用)
beforeDestroy
阶段将要销毁
次组件,但此时尚未销毁,组件还处于正常工作状态
destroyed
阶段组件已经被销毁,此组件在浏览器中对应的DOM结构已被完全移除
7.组件之间的数据共享
1.组件之间的关系
在项目开发中,组件之间最常见的关系分为如下两种:
-
父子关系
-
兄弟关系
2.父子组件之间的数据共享
父子组件之间的数据共享又分为:
-
父 -> 子
共享数据 -
子 -> 父
共享数据
2.1.父组件向子组件共享数据
父组件向子组件共享数据需要使用自定义属性:
<!--父组件 --> <Son :msg="message" :user="userinfo"></Son> <script> data(){ return{ message:'hello vue.js', userinfo:{name:'zs',age:'20'} } } </script>
<!--子组件 --> <template> <div> <h5>Son 组件</h5> <p>父组件传递过来的msg值是:{{msg}}</p> <p>父组件传递过来的user值是:{{user}}</p> </div> </template> <script> props:['msg','user']; </script>
2.2.子组件向父组件传数据
子组件向父组件共享传递数据要使用自定义事件:
//子组件 export default{ data(){ return{count:0} }, methods:{ add(){ this.count++; //修改数据时,通过$emit()触发自定义事件,并向父组件传递数据,也就是$emit向父组件发射一个自定义事件和数据 //只要$emit一触发,就会触发父组件中的numchange事件,从而完成数据子传父 this.$emit('numchange',this.count ) } } } //父组件------------------------------------------------------------------------------------------ //通过在子组件触发的自定义事件再来触发一个父组件中的方法来接收子组件传递来的参数 <Son @numchange='getNewCount'></Son> export default{ data:{ return{countFromSOn:0} }, methods:{ getNewCount(val){this.countFromSon = val} }
2.3 子组件向父组件双向绑定
子组件发射 $emit('input',数据) 事件,在父组件可以直接通过绑定 v-model 可以直接接收到子组件传递来的值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> <script src="../vue.js"></script> </head> <body> <div id="app"> {{msg}} <com v-model="msg"></com> </div> <template id="temp"> <div> <input type="text" :value="value" @input="inp"> </div> </template> <script> const vm = new Vue({ el: "#app", data: { msg: "你好", str:'1' }, components:{ com:{ template:'#temp', props:['value'], methods: { inp(e){ // 子组件在传值的时候,选用input,如this.$emit(‘input’,val),在父组件直接用v-model绑定,就可以获取到了 // 而子组件也可以通过$emit(‘input’,false),去改变父组件中v-model 和 子组件中 value 的值 。 this.$emit('input',e.target.value); } }, } } }); </script> </body> </html>
3.兄弟组件之间的数据共享
在Vue2.x
中,兄弟组件之间数据共享的方案是EventBus
(在工作中就能使用)
3.1EventBus的使用步骤
-
创建
eventBus.js
模块,并向外共享一个Vue实例对象
-
在
数据发送方
,调用bus.$emit('事件名称',要发送的数据)方法触发自定义事件
-
在
数据接收方
,调用bus.$on('事件名称',事件处理函数)方法注册一个自定义事件
8.ref引用
JQuery的优势:简化了程序员操作DOM的过程
Vue优势:MVVM 在vue中,程序员不需要操作DOM,程序员只需要把数据维护好即可(数据驱动视图)
结论:在vue项目中,强烈不建议大家安装和使用JQuery(因为在vue中几乎不需要操作DOM这种需求)
如果,在vue中,需要操作DOM,需要拿到页面某个DOM元素的引用,此时怎么办?
1.什么是ref引用
ref用来辅助开发者不依赖于JQuery的情况下
,获取DOM元素或组件的引用
在每一个vue的组件实例上,都包含一个$refs对象
,里面存储着对应的DOM元素或组件的引用,默认情况下,组件的$refs指向一个空对象
<h4 ref="myh4">我是被ref操作的DOM节点</h4> <!-- ref的名字不能冲突 --> <button @click="getDom">ref操作DOM元素</button> <script> methods:{ getDom(){ this.$refs.myh4.style.color = 'red'; } } </script>
2.使用ref引用组件实例
如果想要使用ref引用页面上的组件实例,则可以按照如下的方式进行操作:
<!-- 使用 ref 属性,为对应的组件添加引用名称 --> <Left ref="leftBtn"></Left> <button @click="getBtn">获取 $refs 引用</button> <script> methods:{ getBtn(){ //通过 this.$refs. 引用的名称 可以引用组件的实例 console.log(this.$refs.leftBtn); //引用到组件的实例之后,就可以调用组件上的methods方法 this.$refs.leftBtn.add();//调用Left组件中的add()方法 } } </script>
3.了解this.$nextTick(cb)
的应用场景(cb是一个回调函数)
组件的$nextTick(cb
)方法,会把cb回调函数推迟到下一个DOM更新周期之后执行
.通俗的理解就是:等组件的DOM更新完成之后,在执行cb回调函数.从而能保证cb回调函数可以操作到最新的DOM元素
当某些代码需要延时到DOM结构重新渲染完毕后执行
时,那么就可以使用this.nextTick(cb);
this.$nextTick(cb)会延迟cb的调用,延迟到页面上的DOM结果重新渲染完毕后调用
<input ref="OInput" type="text" v-if="inputVisible" @blur="showBtn"> <button v-else @click="showInp">展示输入框</button> <script> methods:{ // 显示input框 showInp(){ this.inputVisible = true; // 让展示的文本框自动获得焦点 //如果想要这行代码不报错,那么就要保证执行这行代码时,页面上的DOM结构已经重新渲染上去了 // 所以this.$refs.OInput.focus()这行代码要延时到页面DOM结构更新完毕后执行 this.$nextTick(() => { this.$refs.OInput.focus(); }) }, // 显示button按钮 showBtn(){ this.inputVisible = false; } } </script>
9.数组中的方法
1.some()
适用于要在数组汇总找到某一个元素
const arr = ["lavande", "suixing", "xuanyu"]; arr.some((item, index) => { console.log("ok"); if (item === "suixing") { console.log(index); // 在找到对应的项后可以通过return true固定语法来跳出循环 return true; } });
2.every()
适用于判断数组中的每一项是否满足条件
,只有每一项都符
合才会返回true,否则返回false
window.addEventListener("DOMContentLoaded", () => { const arr = [ { id: 1, name: "西瓜", state: true }, { id: 2, name: "草莓", state: true }, { id: 3, name: "芒果", state: true }, ]; // 需求:判断数组中,水果是否被全选了 const result = arr.every((item) => item.state==true); //只要有任何一项不符合条件就返回false console.log(result); });
3.reduce()
window.addEventListener("DOMContentLoaded", () => { const arr = [ { id: 1, name: "西瓜", state: true, price: 20, count: 1 }, { id: 2, name: "草莓", state: true, price: 80, count: 2 }, { id: 3, name: "芒果", state: true, price: 20, count: 3 }, ]; // 把购物车数组中,已勾选的水果,总价累加起来 // 普通实现方式--------------------------------------------------------- let sum = 0; //总价 // arr.filter()会把数组中符合条件的每一项存入一个新数组中 arr .filter((item) => item.state) .forEach((item) => { sum += item.price * item.count; }); console.log("普通实现方式:" + sum); //reduce实现方式---------------------------------------------------- // reduce()循环相当于是一个累加器,把每一次循环的结果累加起来 // arr.filter((item) => item.state).reduce((累加的结果,当前循环项) => {},初始值); const result = arr .filter((item) => item.state) .reduce((amt, item) => (amt += item.price * item.count), 0); console.log("reduce方法:" + result); });
4.filter()
filter()用于对数组进行过滤,它会创建一个新数组,新数组中存储的是符合条件的元素
const arr = [ { id: 1, name: "西瓜", state: true }, { id: 2, name: "草莓", state: false }, { id: 3, name: "芒果", state: true }, ]; let newArr = arr.filter(item => item.state == true);//newArr中存储着的是符合条件的项
10.购物车案例
1.实现步骤
-
初始化项目的基本结构
-
封装
MyHeader
组件 -
基于axios请求商品列表数据(GET请求:https://www.escook.cn/api/cart)
-
封装
MyFooter
组件 -
封装
MyGoods
组件 -
封装
MyCounter
组件
2.基于axios请求列表数据
npm i axios -S
import axios from 'axios'; //导入axios
2.2 在methods方法汇总定义initCartList函数请求列表数据
<script> export default{ methods:{ //定义函数请求初始数据 ------------------------------------------------ async initCartList(){ const {data:res} = await axios.get('https://www.escook.cn/api/cart'); console.log(res); } } } </script>
<script> export default{ methods:{ async initCartList(){ const {data:res} = await axios.get('https://www.escook.cn/api/cart'); console.log(res); } }, created(){ //调用请求数据的方法 ------------------------------------------------ this.initCartList(); } } </script>
3.请求回来的数据转存到data中
<script> export default{ data(){ return{ list:[],//请求回来的数据转存到这里 ------------------------ } }, methods:{ async initCartList(){ const {data:res} = await axios.get('https://www.escook.cn/api/cart'); console.log(res); //只要是请求回来的数据,如果想要在页面渲染中使用就必须要转存 ------------------------------------- if(res.status === 200){ this.list = res.list; } } }, created(){ //调用请求数据的方法 this.initCartList(); } } </script>
4.循环渲染Goods组件
<template> <div class="app-container"> <!-- Header:头部区域 --> <Header></Header> <!-- 循环渲染每一个商品信息 -------------------------------------------------- --> <Goods v-for="item in list" :key="item.id"></Goods> </div> </template> <script> // 导入商品组件 import Goods from '@/components/Goods/Goods.vue'; export default { components:{ Header, Goods } } </script>
5.接收的数据渲染到页面中
<!-- 通过props来进行父传子 list是axios请求来的数据 --> <Goods v-for="item in list" :key="item.id" :states="item.goods_state" :title="item.goods_name" :price='item.goods_price' :imgSrc='item.goods_img'></Goods> <script> //子组件-Goods export default:{ props:{ // 商品标题 title:{ default:'商品名称商品名称商品名称商品名称', type:String, }, //后面的省略... } </script>
6.分析如何修改商品的勾选状态
<!--通过给复选框绑定change事件(当元素发生改变时触发)来进行子传父--> <input type="checkbox" @change="changeState" class="custom-control-input" :id="'cb' + id" :checked="states" /> <script> methods:{ // 子传父 changeState(e){ // 获取复选框的勾选状态 let newState = e.target.checked; // 触发自定义事件 this.$emit('statesChange',{id:this.id,value:newState}); } } </script>
7.定义fullState计算属性
<!--计算属性定义的时候是方法,使用的时候当做普通属性使用--> <!-- :footerState="fullState":通过子组件的props来进行父传子 --> <Footer :footerState="fullState"></Footer> <script> computed:{ // 动态计算出全选的状态是true还是false fullState(){ //只有this.list中的每一项goods_state都为true,才会返回true,不然返回的都是false return this.list.every(item => item.goods_state); } }, </script> <!-- 子组件-Footer --> <input type="checkbox" class="custom-control-input" id="cbFull" :checked="footerState" /> <script> props:{ footerState:{ type:Boolean } } </script>
11.动态组件
1.什么是动态组件
动态组件指的是动态切换组件的显示与隐藏
2.如何实现动态组件渲染
vue提供了一个内置的<component>
组件,专门用来实现动态组件的渲染
,<component>组件也可以理解为一个占位符,给组件占的:
<script> data(){ //1.当前要渲染的组件名称 return{comName:'Left'} } </script> <!-- 动态切换Left和Right组件 --> <button @click="comName='Left'">展示Left</button> <button @click="comName='Right'">展示Rigth</button> <!-- 2.通过 is 属性动态指定要渲染的组件 --> <!-- 渲染Left和RIght组件 --> <!-- component标签是vue内置的,作用:组件的占永符 --> <!-- is 属性的值表示要渲染的组件名字 --> <component :is="comName"></component> <!-- 3.点击按钮,动态切换组件的名称 --> <button @click="comName='Left'">展示Left组件</button> <button @click="comName='Right'">展示Right组件</button>
3.使用keep-alive保持组件的状态
keep-alive 可以把内部的组件进行缓存,而不是销毁组件,也是vue内置的一个标签
默认情况下,动态切换组件时是无法保持组件的状态的,组件会被销毁,下次切换会重新创建一个新组件,如果要让组件不被销毁的话,可以使用vue内置的<keep-alive>
组件保持动态组件的状态
<!-- 动态切换Left和Right组件> <button @click="comName='Left'">展示Left</button> <button @click="comName='Right'">展示Rigth</button> <!--keep-alive可以让组件在隐藏时不被销毁,保持组件的状态,隐藏组件时让组件处于缓存状态,下次使用的话会重新激活组件 --> <keep-alive> <!--is 属性的值表示要渲染的组件名字 component标签是vue内置的,作用:组件的占永符 --> <component :is="comName"></component> </keep-alive>
4.keep-alive对应的生命周期函数
当组件被缓存
时,会自动触发组件的 deactivated
生命周期函数
当组件被激活
时,会自动触发组件的 activated
生命周期函数
//Left组件 //keep-alive内置组件的生命周期函数 // 当组件第一次被创建的时候,即会执行 created 生命周期,也会执行 activated 生命周期 // 但是,当组件被激活的时候,只会触发 activated 生命周期,不再触发created ,因为组件没有被重新创建 activated(){ console.log('Left组件被激活了 --activated'); }, deactivated(){ console.log('Left组件被缓存了,deactivated'); }
5.keeo-alive的include属性
默认情况下keeo-alive
会把它包裹的组件都给保持状态(缓存)
include
属性用来指定:只有名称匹配的组件才会被缓存
,多个组件名之间使用英文逗号
分隔;
<!-- include="Left":设置后,keep-alive就只会让Left组件保持缓存状态了 --> <keep-alive include="Left"> <!--也就是说不再include属性中的组件都不会被缓存--> <component :is="comName"></component> </keep-alive>
5.keeo-alive的exclude属性
exclude
属性与include
相反,include是包含的意思,而exclude是排除的意思
exclude
属性指定组件名称会被排除缓存,与之名称不匹配的组件反而会被缓存
<!-- exclude="Left":设置后,keep-alive就只会让Left组件不保持缓存状态了,也就是缓存功能排除了Left组件 --> <keep-alive exclude="Left"> <!--也就是说不再include属性中的组件都不会被缓存--> <component :is="comName"></component> </keep-alive>
注意:include和exclude不能同时使用
6.了解组件注册名称和组件声明时name的区别
如果在声明组件的时候,没有为组件指定name名称
,那么组件的名称默认就是"注册时候的名称“
注意:name:'MyLeft‘:设置了 name 属性后,在使用include或exclude属性时,指定的名称也要设置为MyLeft"
在实际开发过程中,为了规范化,建议给所有的组件都设置一个name名称
<!-- Left组件 --> <template> <div class="box"> <h2>hi~我是子组件-Left</h2> </div> </template> <script> // 导入eventBus模块 export default { // 当子组件提供了 name 属性之后,组件的名称就是name属性的值 //组件注册名称和name名称的对比: //组件注册名称的主要应用场景是以标签形式把注册好的组件渲染到页面结构中 //组件的 name 名称主要应用场景:结合<keep-alive>标签实现组件的缓存功能;以及在调试工具汇总看到组件的name名称 name:'MyLeft',//----------------------------------------------------------------------------- data() { return { count:1, } }, // 生命周期函数 -创建 created(){ console.log('Left组件被创建了....'); }, // 生命周期函数 -销毁阶段 destroyed() { console.log('Left组件被销毁了~~'); }, //keep-alive内置组件的生命周期函数 // 当组件第一次被创建的时候,即会执行 created 生命周期,也会执行 activated 生命周期 // 但是,当组件被激活的时候,只会触发 activated 生命周期,不再触发created ,因为组件没有被重新创建 activated(){ console.log('Left组件被激活了 --activated'); }, deactivated(){ console.log('Left组件被缓存了,deactivated'); } } </script>
12.插槽
1.什么是插槽
插槽(slot)
是vue为组件的封装者
提供的能力,允许开发者在封装组件时,把不确定的,希望由用户指定的部分
定义为插槽
<!--App.vue--> <Left> <!-- 默认情况下,如果Left组件没有提供插槽,那么Left标签里面的内容都会被丢弃 --> </Left> <!-- Left子组件 --> <template> <div class="left-container"> <h3>Left 组件</h3> <!--声明一个插槽区域--> <slot></slot> </div> </template>
2.把内容添加到指定的插槽中-template
在vue中如果要把内容指名道姓的放到某个插槽中就需要通过<template>
标签来设置了
<template> <div class="left-container"> <!--声明一个插槽区域--> <!-- vue官方规定:每一个slot插槽都要有一个name名称,如果省略了name属性,则有一个默认name名称:default --> <slot name="default"></slot> <!--设置插槽name名后就可以通过name名来指定内容填充了--> </div> </template> <template> <div class="app-container"> <!-- 渲染 Left 组件和 Right 组件 --> <Left> <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽中 --> <!-- 1.如果要把内容填充到指定名称的插槽中需要通过template标签来设置v-slot:插槽name名字;(v-slot不能直接用到元素身上,要用到template身上) 2.template这个标签是一个虚拟的标签,只起到包裹的作用,不会在页面中渲染成实际的标签(不会被渲染) --> <template v-slot:default> <p>这是在Left组件的内容区域声明的P标签</p> </template> </Left> </div> </template>
3.v-slot的简写形式以及插槽的后备内容
.v-slot的简写形式
v-slot的简写形式是 # 号
<!-- #default == v-slot:default --> <template #default> <p>这是在Left组件的内容区域声明的P标签</p> </template>
后备内容
封装组件时,可以为预留的<slot>插槽提供后备内容(默认内容).如果组件的使用者后没为插槽提供任何内容时,则后备内容生效:
<template> <div class="left-container"> <!--声明一个插槽区域--> <!-- vue官方规定:每一个slot插槽都要有一个name名称,如果省略了name属性,则有一个默认name名称:default --> <slot name="default">这是default插槽的默认内容,一旦后续指定了内容就会被覆盖</slot> <!--设置插槽name名后就可以通过name名来指定内容填充了--> </div> </template>
4.具名插槽的定义和使用
一个插槽使用name属性定义了name名时就是一个具名插槽,注意:叫default的是默认插槽,只有其他name名才是具名插槽
<template> <div class="left-container"> <!--声明一个插槽区域 设置 name=“left” 后就是一个具名插槽了--> <slot name="left">这是default插槽的默认内容,一旦后续指定了内容就会被覆盖</slot> </div> </template>
案例
<!-- APP.vue --> <template> <div class="app-container"> <!-- 具名插槽演示 --> <Article> <!-- 根据插槽的name名来将内容放入指定的插槽中 --> <template #title> <p>hello 具名插槽</p> </template> <template #content> <div> <p>啦啦啦啦啦啦</p> <p>啦啦啦啦啦啦你好你好你好/p> <p>啦啦啦啦啦啦无阿拉啦啦啦啦啦啦啦啦</p> </div> </template> <template #footer> <p>lavande</p> </template> </Article> </template> <script> // 导入组件 import Article from '@/components/Article.vue'; export default { // 注册组件 components:{ Article, } } </script> <!-- Article.vue -------------------------------------------------------------------> <template> <div class="article-container"> <!-- 文章的标题 --> <div class="header-box"> <slot name="title"></slot> </div> <!-- 文章的内容 --> <div class="content-box"> <slot name="content"></slot> </div> <!-- 文章的作者 --> <div class="footer-box"> <slot name="footer"></slot> </div> </div> </template> <script> export default { name:'Article', } </script> <style lang="less" scoped> .article-container{ // 子元素选择器 >div{ // 最小高度 min-height: 150px; } .header-box{ background: pink; } .content-box{ background: skyblue; } .footer-box{ background: lightsalmon; } } </style>
5.作用域插槽的基本用法
在预留插槽时声明的一些数据项,在使用插槽的时候可以接收这些数据项的插槽就叫作用域插槽
<!-- APP.vue --> <template> <div class="app-container"> <!-- 具名插槽演示-------------------- --> <Article> <!-- 根据插槽的name名来将内容放入指定的插槽中 --> <!-- 通过#content="obj形式可以接收从预留插槽那 --> <template #content="obj"> <div> <p>啦啦啦啦啦啦</p> <p>啦啦啦啦啦啦你好你好你好/p> <p>啦啦啦啦啦啦无阿拉啦啦啦啦啦啦啦啦</p> <p>从预留插槽那接收的数据:{{obj.msg}}</p> </div> </template> </Article> </div> </template> <script> // 导入组件 import Article from '@/components/Article.vue'; export default { // 注册组件 components:{ Article, } } </script> <!-- Article.vue ------------------------------------------------------------------------------------> <template> <div class="article-container"> <!-- 文章的内容 --> <div class="content-box"> <!-- 作用域插槽:可以通过设置自定义属性来给之后使用这个插槽的组件传递数据 --> <!-- 在封装组件时,为预留的<slot>提供属性对应的值(自定义属性),这种用法就叫做"作用域插槽(插槽中带数据的就是作用域插槽)" --> <slot name="content" :msg="msg"></slot> </div> </div> </template> <script> export default { name:'Article', data() { return { msg: '你好啊,我是通过作用域插槽传来的数据', } }, } </script>
6.作用域插槽的解构赋值
<!-- APP.vue --> <template> <div class="app-container"> <!-- 具名插槽演示-------------------- --> <Article> <!-- 根据插槽的name名来将内容放入指定的插槽中 --> <!-- 通过#content="obj形式可以接收从预留插槽那 --> <template #content="{msg,user}"> <div> <p>啦啦啦啦啦啦</p> <p>啦啦啦啦啦啦你好你好你好/p> <p>啦啦啦啦啦啦无阿拉啦啦啦啦啦啦啦啦</p> <p>从预留插槽那接收的数据:{{msg}}</p> <p>从预留插槽那接收的数据:{{user.name}}</p> </div> </template> </Article> </div> </template> <script> // 导入组件 import Article from '@/components/Article.vue'; export default { // 注册组件 components:{ Article, } } </script> <!-- Article.vue ------------------------------------------------------------------------------------> <template> <div class="article-container"> <!-- 文章的内容 --> <div class="content-box"> <!-- 作用域插槽:可以通过设置自定义属性来给之后使用这个插槽的组件传递数据 --> <!-- 在封装组件时,为预留的<slot>提供属性对应的值(自定义属性),这种用法就叫做"作用域插槽(插槽中带数据的就是作用域插槽)" --> <slot name="content" msg="你好啊,我是通过作用域插槽传来的数据" :user="user"></slot> </div> </div> </template> <script> export default { name:'Article', data() { return { user:{ age: 21, name:'lavande', } } }, } </script>
13.自定义指令
1.什么是自定义指令
vue官方提供了v-text,v-for,v-model,v-if等常用的指令,除此之外vue还允许开发者自定义指令
2.自定义指令的分类
vue中的自定义指令的分为两类:
-
私有自定义指令
-
全局自定义指令
3.私有自定义指令的基本用法
在每个vue组件中,可以在directives
节点下声明私有自定义指令
:
directives:{ //color:一个自定义指令 color:{ //bind函数当指令第一次被绑定到元素时调用 //为绑定到的 HMTL 元素设置红色的文字 bind(el){ //形参中的el是绑定了此指令的,原生的 DOM 对象 el.style.color = 'red'; } } }
4.使用binding.value获取指令绑定的值
<template> <div class="app-container"> <!-- 为 h1 绑定一个color的自定义指令 --> <h1 v-color="color">App 根组件</h1> <p v-color="'pink'">通过binding来获取指令绑定的值</p> <hr /> </div> </template> <script> export default { data() { return { color:'blue', } }, // 私有自定义指令的节点 directives:{ // 定义一个名为color的自定义指令 color:{ //当指令第一次绑定到元素上的时候,会立即出发bind函数 bind(el,binding){ // 通过 binding.value 可以获取从指令那传来的值 el.style.color = binding.value; } } } } </script>
5.update函数
bind函数只会在指令第一次绑定到元素上的时候触发一次
,当DOM更新时bind函数不会被触发,update函数会在每次DOM更新时被调用
:
directives:{ color:{ //当指令第一次被绑定到元素时调用 bind(el){ //形参中的el是绑定了此指令的,原生的 DOM 对象 el.style.color = 'red'; }, //update函数每次 DOM 被更新时都会被调用 update(el,binging){ el.style.color = binding.value; } } }
6.函数简写
如果bind
和update
函数中的逻辑完全相同
,则对象格式
的自定义指令可以简写成函数格式
:
directives:{ //在 bind 和 update 时,会触发相同的业务逻辑 color(el,binding){ el.style.color = binding.value; } }
7.全局自定义指令
全局自定义指令定义后,所有的组件都能使用,全局共享的自定义指令需要通过 Vue.directive()
进行声明:
//参数1:字符串,表示去哪句自定义指令的名字 //参数2: 对象,用来接收指令的参数值 //语法: Vue.directive('自定义指令名',{ bind(el,binding){...}, update(el,binding){...}, }) //案例 Vue.directive('color',(el,binding) => { //此处的函数是因为bind 和 update 函数的业务逻辑一致,所有可用用函数简写形式 el.style.color = binding.value; })
注意:在实际开发中,过滤器和自定义指令一般都是定义成全局的
14.Eslint
1.Eslint学习网址
List of available rules - ESLint中文文档 (bootcss.com)
2.了解eslint.js配置文件中的rules规则
在使用 vue create demo-3 创建项目完成后(创建的过程中记得要添加Eslint),打开eslint.js配置文件
module.exports = { root: true, env: { node: true }, extends: [ 'plugin:vue/essential', '@vue/standard' ], parserOptions: { parser: '@babel/eslint-parser' }, rules: { // 在代码中不允许使用console // 在 JavaScript,虽然console 被设计为在浏览器中执行的,但避免使用 console 的方法被认为是一种最佳实践。 // 这样的消息被认为是用于调试的,因此不适合输出到客户端。通常,在发布到产品之前应该剔除 console 的调用。 // production:发布模式 ,也就是说,在开发阶段可以使用console,发布阶段不能使用,有的话会报错 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // debugger:打断点(用代码形式打断点,代码执行到debugger就会暂停) 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' } }
3.初步了解ESlint的语法规则
1. 初始化项目 | 黑马头条 - 移动端 (liulongbin.top)
4.配置VScode
安装插件可以帮我们避免很多不符合规范的问题(插件格式化代码)
Eslint插件
复制到设置右上角的settings.json中
"editor.codeActionsOnSave": { "source.fixAll": true }
prettier插件
复制到设置右上角的settings.json中
"prettier.configPath": "C:\\Users\\宇轩\\.prettierrc", "eslint.alwaysShowStatus": true, "prettier.trailingComma": "none", "prettier.semi": false, // 每行文字个数超出此限制将会被迫换行 "prettier.printWidth": 300, // 使用单引号替换双引号 "prettier.singleQuote": true, "prettier.arrowParens": "avoid", // 设置 .vue 文件中,HTML代码的格式化插件 "vetur.format.defaultFormatter.html": "js-beautify-html", "vetur.ignoreProjectWarning": true, "vetur.format.defaultFormatterOptions": { "js-beautify-html": { "wrap_attributes": false }, "prettier": { "printWidth": 300, "trailingComma": "none", "semi": false, "singleQuote": true, "arrowParens": "avoid" } },
15.axios
1.演示axios的基本用法并发现存在的问题
<template> <div class="right-container"> <button @click="postInfo">发起POST请求</button> <button @click="getInfo">发起GET请求</button> </div> </template> <script> import axios from 'axios' export default { methods: { async postInfo() { const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 20 }) console.log(res) }, async getInfo(){ const {data:res} = await axios.get('http://www.liulongbin.top:3006/api/get); console.log(res) } } } </script>
2.把axios挂在到Vue的原型上配置请求根路径
//main.js 一个vue项目的入口文件 import Vue from 'vue' import App from './App.vue' import axios from 'axios' Vue.config.productionTip = false // axios.defaults.baseUrl = '请求根路径' //配置后就可以省略那一串根路径了如:localhost:3000 // 全局配置 axios 的请求根路径 axios.defaults.baseURL = 'http://www.liulongbin.top:3006' // 将axios挂载到Vue实例的原型上,之后每一个.vue组件都可以直接使用axios了,不用在导入了 // 今后在每个.vue组件中如果要发起axios的请求,直接调用this.$http Vue.prototype.$http = axios new Vue({ render: h => h(App) }).$mount('#app')
<!-- Left.vue --> <template> <div class="right-container"> <button @click="postInfo">发起POST请求</button> <button @click="getInfo">发起GET请求</button> </div> </template> <script> // import axios from 'axios' //在main.js中将axios挂载到了VUe实例中,所以不用在导入了 export default { methods: { async postInfo() { // axios.defaults.baseUrl = '请求根路径' //配置后就可以省略那一串根路径了如:localhost:3000 //'/api/post':因为入口文件main.js中已经全局配置了根路径,因此不用再写根路径了 const { data: res } = await this.$http.post('/api/post', { name: 'zs', age: 20 }) console.log(res) }, async getInfo(){ const {data:res} = await this.$http.get('/api/get); console.log(res) } } } </script>
3.了解直接把axios挂载到Vue原型上的缺点
axios挂载到Vue原型上的缺点是无法实现API接口的复用
,所以直接把axios挂载到Vue原型上也不是最终的解决办法
4.最终的解决方法
axios挂载到Vue原型上无法实现API接口的复用,而最终的解决办法就可以实现API接口的复用
//定义一个 request.js 的模块 import axios from 'axios' const 小axios1 = axios.create({ // 小axios1 的请求根路径 baseURL:'http://www.liulongbin.top:3006' }) const 小axios2 = axios.create({ // 小axios2 的请求根路径 baseURL:'http://localhost:3002' }) ...... export default 小axios1 //这样就可以实现 API接口 的复用,如果你要请求 liulongbin 服务器的接口,就可以调用 小axios1 去发起请求 // 但正常来说,一个模块只会封装一个 小axios ,如果要封装多个 小axios ,就需要另起一个 .js 文件 ,定义一个模块
案例:
//request.js模块 import axios from 'axios' // 之后如果要请求这个根路径,就可以导入这个模块,再用 request 发起请求 const request = axios.create({ // 指定请求的根路径 baseURL: 'https://www.escook.cn' }) export default request
//Home.vue // 导入reqest模块 import request from '@/util/request.js' export default { name: 'MyHome', data () { return { ArticleList: [], // 页码值 page: 1, // 每次显示多少条数据 limit: 10 } }, created () { this.initArticleList() }, methods: { // 封装获取文章列表数据的方法 async initArticleList () { // 发起 GET 请求,获取文章的列表数据 const { data: res } = await request.get('/articles', { // 请求参数 // params:params方式会将参数添加到 URL 后面,传递的是字符串(也就是get方式的传值) // data:data方式会将参数添加到请求体(body)中,也可以传递 json 格式的数据 params: { _page: this.page, _limit: this.limit } }) console.log(res) this.ArticleList = res } } }
5.axios发送请求时 params 和 data 的区别
params:
params方式会将参数添加到 URL
后面,传递的是字符串,注意:params的方式会把json格式转为string进行传值,主要用于 get
请求
data:
data方式会将参数添加到 请求体(body)
中,也可以传递 json 格式的数据,主要用于 post
请求
6.封装接口模块
//articleAPI.js模块 // 文章相关的 API 接口,都放在这个模块中 import request from '@/util/request' // export 向外导出一个 API 函数 _page _limit接口需要的参数 export const getArticleListAPI = (_page, _limit) => { //request.get(...) 返回的本身就是一个 Promise 实例 return request.get('/articles', { // 请求参数 // params:params方式会将参数添加到 URL 后面,传递的是字符串(也就是get方式的传值) // data:data方式会将参数添加到请求体(body)中,也可以传递 json 格式的数据 params: { _page, _limit } }) }
//Home.vue组件 // 导入 文章接口 模块 import { getArticleListAPI } from '@/api/articleAPI' data () { return { // 获取到的文章数据转存到data实例中 ArticleList: [], // 页码值 page: 1, // 每次显示多少条数据 limit: 10 } }, //生命周期函数 created () { //在生命周期函数中调用 获取数据 的函数 this.initArticleList() }, methods: { // 封装获取文章列表数据的方法 async initArticleList () { // 发起 GET 请求,获取文章的列表数据 // getArticleListAPI(this.page, this.limit)返回的是一个 Promise 实例,所以需要用 async await 来接收 const { data: res } = await getArticleListAPI(this.page, this.limit) console.log(res) this.ArticleList = res } }
16.路由
1.前端路由的概念与原理
1.什么是路由
路由(router)就是对应关系
2.SPA与前端路由
SPA指的是一个web网站只有唯一的一个HTML页面
,所有组件的展示与切换
都在这唯一的一个页面内完成.此时,不同组件的切换
需要通过前端路由来实现
结论:在SPA项目中,不同功能之间的切换,要依赖于前端路由来完成
3.什么是前端路由
通俗易懂的概念:Hash地址
与组件
之间的对应关系
4.前端路由的工作方式
-
用户
点击了
页面上的路由链接
-
导致了U
RL地址栏
中的Hash值发生了变化
-
前端路由监听到了Hash地址的变化
-
前端路由把当前
Hash地址对应的组件
渲染到浏览器中
5.手动模拟简单的路由
<template> <div class="app-container"> <h1>App 根组件</h1> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> <hr /> <!-- 动态组件 --> <component :is="comName"></component> </div> </template> <script> // 导入组件 import Home from '@/components/Home.vue' import Movie from '@/components/Movie.vue' import About from '@/components/About.vue' export default { name: 'App', // 注册组件 components: { Home, Movie, About }, data() { return { // 在动态组件的位置,要展示组件的名字,值必须是字符串 comName:'Home', } }, // 组件创建时 created(){ // 只要当前的APP组件一被创建,就立即监听 window 对象的 onhashchange 事件 // window.onhashchange:监听页面上hash值的变化 window.onhashchange = () => { // location.hash:获取当前页面的hash值 let hash = location.hash; // console.log(hash); switch(hash){ case '#/home': this.comName = 'Home'; break; case '#/movie': this.comName = 'Movie'; break; case '#/about': this.comName = 'About'; break; } } } } </script> <style lang="less" scoped> .app-container { background-color: #efefef; overflow: hidden; margin: 10px; padding: 15px; > a { margin-right: 10px; } } </style>
2.vue-router的基本使用
1.什么是vue-router
vue-router是vue.js官方给出的路由解决方案,它只能结合vue项目进行使用,能够轻松的管理SPA项目中组件的切换
vue-router的官方文档地址: Vue Router
2.vue-router 安装和配置的步骤
-
安装vue-router包
-
创建路由模块
-
导入并挂载路由模块
-
声明
路由链接
和占位符
3.安装vue-router
在vue2项目中,安装命令如下:
npm i vue-router@3.5.2 -S
4.创建路由模块
在src源代码目录下,新建router/index路由模块,并初始化如下的代码:
//1.导入 vue 和 vueRouter 的包 import Vue from 'vue'; import VueRouter from 'vue-router' //2.调用 Vue.use()函数,把 VueRouter 安装为 Vue 插件 Vue.use(VueRouter) //3.创建路由的实例对象 const router = new VueRouter() //4.向外共享路由的实例对象 export default router
import Vue from 'vue' import App from './App2.vue' // 导入路由模块,拿到路由的实例对象 // 在进行模块化导入的时候,如果给定的时文件夹,则默认导入这个文件夹下,名字叫做index,js的文件------------------ // 因此可以简写为:import router from '@/router' import router from '@/router/index.js' // 导入 bootstrap 样式 import 'bootstrap/dist/css/bootstrap.min.css' // 全局样式 import '@/assets/global.css' Vue.config.productionTip = false new Vue({ render: h => h(App), // 在 vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载 ----------------------------- router:router }).$mount('#app')
5.在路由模块中声明路由的对应关系
//APP.vue <template> <div class="app-container"> <h1>App2 组件</h1> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> <hr /> <!-- vue-router官方提供了一个组件:router-view --> <!-- router-view:占位符(相当于动态组件中的<component>) --> <router-view></router-view> </div> </template> <script> export default { name: 'App', } </script> <style lang="less" scoped> .app-container { background-color: #efefef; overflow: hidden; margin: 10px; padding: 15px; > a { margin-right: 10px; } } </style>
//router/index.js-------------------------------- // 导入vue和vueRouter包 import Vue from 'vue'; import VueRouter from 'vue-router'; // 导入需要的组件 import Home from '@/components/Home.vue' import About from '@/components/About.vue' import Movie from '@/components/Movie.vue' // 调用 Vue.use() 函数把 VueRouter 安装为 Vue 的插件 Vue.use(VueRouter); // 创建路由的实例对象 // APP2主页上的hash值一旦发生变化,就会路由实例检测到,并切换相对应的组件 const router = new VueRouter({ // 路由配置选项 router:[]是一个数组,作用:定义"hash地址"与"组件"的对应关系 routes:[ // {path:'/home',component:要展示的组件}, {path:'/home',component:Home}, {path:'/about',component:About}, {path:'/movie',component:Movie}, ] }); // 向外共享路由的实例对象 export default router;
6.使用router-link替代 a 链接
当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了
<!-- <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a> --> <!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了 --> <router-link to="/home">首页</router-link> <router-link to="/movie">电影</router-link> <router-link to="/about">关于</router-link>
3.vue-router的常见用法
1.redirect路由重新定向
路由重定向
指的是:用户在访问地址A
的时候,强制用户跳转
到地址C,从而特定的组件页面。通过路由规则的redirect
属性,指定一个新的路由地址,可以很方便的设置路由的重定向
const router = new VueRouter({ //在 router 数组汇总,声明路由的匹配规则 routes:[ //当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则 {path:'/',redirect:'/home'}, {path:'/home',component:Home}, ] })
2.嵌套路由
通过路由实现组件的嵌套展示,叫做嵌套路由
3.嵌套路由-通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children
属性声明子路由规则
import Tab1 from '@/components/tabs/Tab1.vue' import Tab2 from '@/components/tabs/Tab2.vue' const router = new VueRouter({ routes:{ //about 页面的路由规则(父级路由规则) path:'/about', component:About, children:[ //通过 children 属性,嵌套声明子级路由规则 //子路由路径不用以 / 开头 {path:'tab1',component:Tab1}, //访问 /about/tab1 时,展示 Tab1 组件 {path:'tab2',component:Tab2}, //访问/about/tab2时,展示 Tab2 组件 ] } })
//案例 import Tab1 from '@/components/tabs/Tab1.vue' import Tab2 from '@/components/tabs/Tab2.vue' // 创建路由的实例对象 // APP2主页上的hash值一旦发生变化,就会路由实例检测到,并切换相对应的组件 const router = new VueRouter({ // 路由配置选项 router:[]是一个数组,作用:定义"hash地址"与"组件"的对应关系 routes:[ // {path:'/home',component:要展示的组件}, {path:'/home',component:Home}, {path:'/movie',component:Movie}, //给About添加路由规则及子路由规则 // 子路由重定向:redirect:'tab1' {path:'/about',component:About,/*redirect:'tab1,'*/children:[ // 子路由规则 // 默认子路由,如果是children数组汇总,某个路由规则的path值为空字符串 // 则这条路由规则叫做默认子路由,相当于子路由重定向 {path:'',component:Tab1}, {path:'tab2',component:Tab2}, ]}, // 使用redirect进行重定向 {path:'/',redirect:'/home'}, ] }); // 向外共享路由的实例对象 export default router;
4.动态路由-动态路由匹配
思考:有如下三个路由链接:
<router-link to="/movie/1">电影1</router-link> <router-link to="/movie/2">电影2</router-link> <router-link to="/movie/3">电影3</router-link>
定义如下3个路由规则,是否可行:
{path:'/movie/1',component:Movie}, {path:'/movie/2',component:Movie}, {path:'/movie/3',component:Movie},
可行,但路由规则的复用性差
4.1 动态路由-动态路由的概念
动态路由是指:把Hash地址中可变的部分
定义为参数项
,从而提高路由规则的复用性
在vue-router中使用英文的冒号 ( : )
来定义路由的参数项:
//路由的动态参数以 : 进行声明,冒号后面的是动态参数的名称 {path:'/movie/:id',component:Movie} //上面那一种将以下3个路由规则合并成了一个,提高了路由规则的复用性 {path:'/movie/1',component:Movie}, {path:'/movie/2',component:Movie}, {path:'/movie/3',component:Movie},
4.2 如动态路由-何得到动态路由的动态参数
this.$route
是路由的"参数对象"
this.$router
是路由的"导航对象"
<!-- 如/movie/:id --> <router-link to="/movie/1">沉默的证人</router-link> <router-link to="/movie/2">香蜜沉沉烬如霜</router-link> <router-link to="/movie/3">余生请多指教</router-link> <!-- 我们可以通过 this.$route.params.参数名 获取到动态参数 --> <p> {{this.$route.params.id}} </p>
4.3 动态路由-为路由规则开启props传参
获取动态路由中动态参数的第二种方法(第一种方法:this.$route.params.参数名)
//router.index中 // props:true:为路由规则开启props传参,是第二种获取动态参数值的方法 {path:'/movie/:id',component:Movie,props:true}, //Movie.vue组件中 <p>为路由规则开启props传参拿到的参数:{{id}}</p> export default { props:['id'] }
4.4 动态路由-拓展$route. query 和$route. fullPath
注意1:在 hash 地址中 / 后面的参数项,叫做"路径参数",在路由"参数对象"中,需要使用 this.$route.params
来访问路径参数,
注意2:在hash地址中, ? 后面的参数项叫做"查询参数,在路由'参数对象'中, 需要使用 this.$route.query
来访问查询参数
如:<router-link to="/movie/1?name=zs age=20">沉默的证人</router-link>
?name=zs age=20 : 查询参数
$route.fullPath 是一个完整的hash地址,既包含路径参数也包含查询参数: /movie/2?name=zs&age=20
5.声明式导航 & 编程式导航
在浏览器中,点击链接
实现导航的方式叫做声明式导航
,例如:
-
在普通网页中点击
<a>链接
,vue项目中点击<router-link>
都属于声明式导航
在浏览器中,调用API方法
实现导航的方式,叫做编程式导航
,例如:
-
普通网页中调用
location.href
(JS的) 跳转新页面的方式属于编程式导航
5.1 vue-router中的编程式导航API
vue-router提供了许多编程式导航的API,其中最常用的导航API分别是:
-
this.$router.
push
('hash地址')-
跳转到指定的hash地址,并
增加
一条历史记录:(this*.$router.push('/movie/3')
)
-
-
this.router.
replace
('hash地址')-
跳转到指定的hash地址,并替换掉当前的历史记录(不会增加历史记录)(
this*.$router.push('/movie/3')
)
-
-
this.$router.
go
(数值 n )-
调用 this.$router.go() 方法,可以在浏览历史中前进和后退,示例代码如下:
<template> <h3>MyMovie组件 --- {{id}} </h3> <button @click="goBack">后退</button> </template> <script> export default{ props:['id'], methods:{ goBack(){ // go(-1):表示后退一层,如果后退的层数超过上限,则原地不动 this.$router.go(-1); }, goTo(){ // go(-1):表示后退一层,如果前进的层数超过上限,则原地不动 this.$router.go(1) } } } </script>
-
5.2 $router.go 的简化用法
在实际开发中,一般只会前进和后退一层页面,因此 vue-router 提供了如下两种便捷方法:
-
$router.back()
-
在历史记录中后退到上一个页面
-
-
$router.forward()
-
在历史记录中前进到下一个页面
-
6.导航守卫
导航守卫可以控制路由的访问权限,示意图如下:
6.1 全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫,因此在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:
//创建路由实例对象 const router = new VueRouter({...}); //调用路由实例对象的 beforeEach 方法,即可声明 "全局前置守卫" //每次只要发生路由导航跳转的时候,就会自动触发这个"回调函数 router.beforeEach( () => {...} )
6.2 守卫方法的3个形参
全局前置守卫的回调函数中接收3个形参,格式为:
const router = new VueRouter({....}) //全局前置守卫 router.beforeEach((to,from,next) => { //to: 是将要访问的路由信息对象 //from 是将要离开的路由的信息对象 //next 是一个函数,调用next()表示方行,允许这次路由导航 })
6.3 next函数的三种调用方式
6.4 控制访问权限
router.beforeEach((to,from,next) => { if(to.path == '/main'){ const token = localStorage.getItem('token'); if(token){ next();//访问的是main页面,且有token值 }slse{ next('/login') //访问的是后台主页,但是没有 token 的值 } }else{ next();//访问的不是后台主页,直接放行 } })
6.5 升级控制访问权限
router.beforeEach((to,from,next) => { const pathArr = ['/main','/main/users'] //includes():查找字符串或数组是否包含某一个元素,返回布尔值 if(pathArr.includes(to.path)){ const token = localStorage.getItem('token'); if(token){ next();//访问的是main页面,且有token值 }slse{ next('/login') //访问的是后台主页,但是没有 token 的值 } }else{ next();//访问的不是后台主页,直接放行 } })
4.后台管理案例
router-admin项目
5.正式项目-移动端黑马头条
1.初始化
1.创建并梳理项目结构
打开cmd:vue create demo-toutiao
进入后选择最后一项-自定义(Manuaily select features)
之后选择: 2.X(vue 2.0)
我们学习的是 # 开头的路由,不是history路由因此要选 NO
之后选择默认: Lint on save(保存后进行格式的检查)
再之后选择: In dedicated config files(把less,ESlint等配置文件放在单独的配置文件中)
最后 Save this as 。。。。。是问你存不存预设随便填就好了
2.components和views的区别
views中存放的组件是通过路由来进行切换的,如果某个组件不是通过路由来进行切换的是可复用的就放在components中
3.安装和配置Vant组件库
npm i vant@latest-v2
一次性导入全部组件
import Vue from 'vue'; import Vant from 'vant'; import 'vant/lib/index.css'; Vue.use(Vant);
4.使用Tabbar组件并开启路由模式
<template> <div> <!-- 路由占位符 --> <router-view></router-view> <!-- Tabar组件 --> <!-- 要给Tabar组件开启路由模式时,就需要为van-tabbar添加一个route属性,开启后会自动管理路由切换高亮 --> <van-tabbar route> <van-tabbar-item icon="wap-home" to="/">首页</van-tabbar-item> <van-tabbar-item icon="manager" to="/user">我的</van-tabbar-item> </van-tabbar> </div> </template>
5.通过路由展示对应的Tabar页面
<!-- App.vue --> <template> <div> <!-- 路由占位符 --> <router-view></router-view> <!-- Tabar组件 --> <!-- 要给Tabar组件开启路由模式时,就需要为van-tabbar添加一个route属性,开启后会自动管理路由切换高亮 --> <van-tabbar route> <van-tabbar-item icon="wap-home" to="/">首页</van-tabbar-item> <van-tabbar-item icon="manager" to="/user">我的</van-tabbar-item> </van-tabbar> </div> </template>
//router -> index.js import Vue from 'vue' import VueRouter from 'vue-router' // 导入组件 import Home from '@/views/Home/MyHome.vue' import User from '@/views/Home/User/User.vue' // 把 VueRouter 安装为 Vue 的插件 Vue.use(VueRouter) // 定义路由规则的数组 const routes = [ { path: '/', component: Home }, { path: '/user', component: User } ] // 创建路由实例的对象 const router = new VueRouter({ routes }) export default router
额外知识
1.解构赋值
// 简单地说,对象的解构赋值就是使用与对象匹配的结构来实现对象属性的赋值 // (实际就是用对象中的属性为变量赋值)。 // 不使用对象解构 let person = {name: 'Jack', age: 22} let personName, personAge personName = person.name personAge = person.age console.log(personName) // 'Jack' console.log(personAge) // 22 // 使用对象解构 let person = {name: 'Jack', age: 22} let {name: personName, age: personAge} = person console.log(personName) console.log(personAge) // 如果想让变量直接使用属性的名称,可以使用简写语法 let person = {name: 'Jack', age: 22} let {name, age} = person console.log(name) // 'Jack' console.log(age) // 22 // 当解构出的属性在对象中不存在时,则对应变量的值就是undefined let person = {name: 'Jack', age: 22} let {name, age, job} = person console.log(name) // 'Jack' console.log(job) // undefined // 在解构的同时定义默认值,适用于解构出的属性不存在于源对象中的情况 let person = {name: 'Jack', age: 22} let {name, age, job='software engineer'} = person console.log(name) console.log(job) // 在函数参数列表中也可以进行解构赋值,对参数的解构赋值不会影响arguments对象 let person = {name: 'Jack', age: 22} function printPerson(foo, {name, age}, bar) { console.log(arguments) console.log(name, age) } printPerson('1st', person, '2nd') // {"0":"1st","1":{"name":"Jack","age":22},"2":"2nd"} 'Jack' 22
6.下拉刷新和上拉刷新
<!-- Home.vue --> <template> <div class="home-container"> <!-- fixed 固定在页面的顶部 --> <van-nav-bar class="topBar" title="GEOMETRY作品" fixed /> <!-- 下拉刷新,屏幕顶部向下拉进行刷新 --> <van-pull-refresh v-model="isLoading" @refresh="onRefresh" :disabled="disabledFlag"> <!-- 上拉刷新 --> <van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"> <!-- 导入 注册 并使用 Article 组件 --> <!-- :comCount => :com-count: Vue官方建议,如果在定义属性时,用的是驼峰命名,那么在用的时候建议用连字符 - 来使用 --> <!-- 如 comCount 建议写成 com-count--> <ArticleInfo v-for="item in ArticleList" :key="item.id" :title="item.title" :author="item.aut_name" :com-count="item.comm_count" :pubdate="item.pubdate" :cover="item.cover"></ArticleInfo> </van-list> </van-pull-refresh> </div> </template> <script> // 导入 文章接口 模块 import { getArticleListAPI } from '@/api/articleAPI' // 导入 ArticleInfo 组件 import ArticleInfo from '@/components/ArticleInfo.vue' export default { name: 'MyHome', data () { return { ArticleList: [], // 页码值 page: 1, // 每次显示多少条数据 limit: 10, // 是否正在加载下一页数据 如果loading为true,则不会反复触发 load 事件 // 每当下一页数据请求回来之后,把loading从 true 改为 false // 为 true 是因为默认第一次不触发 onLoad 事件 loading: true, // 所有数据是否都加载完毕了,如果没有更过数据了,一定要把finished改为true finished: false, // 是否正在下拉屏幕顶部进行刷新 isLoading: true, disabledFlag: false } }, created () { this.initArticleList() }, methods: { // 封装获取文章列表数据的方法 async initArticleList (isRefresh) { // 发起 GET 请求,获取文章的列表数据 const { data: res } = await getArticleListAPI(this.page, this.limit) console.log(res) // ...res:是将 res 中的数据扩展开来添加到 this.ArticleList ,如果不扩展,那么 this.ArticleList 就变成了二维数组 // 拼接新数据 if (isRefresh) { this.ArticleList.unshift(...res) } else { this.ArticleList.push(...res) // loading默认值为true是因为如果为false那么刚加载页面的时候,就会触发onLoad事件 // 加载第二页的数据,但这种是不对的,刚进来的时候应该是不触发的,所以改为true(为true不触发),然而在初始化数据这里,一定要 // 把 loading 改为false,不然之后一直都不会触发 onLoad事 件了 this.loading = false } // 控制是否可以下拉平布顶部进行刷新 true:不能触发加载函数 false:可以触发加载函数 this.isLoading = false if (res.length === 0) { // 证明没有下一页数据了,直接把 finished 改为 true ,表示数据加载完了 this.finished = true // 禁止下拉屏幕顶部刷新 this.disabledFlag = true } }, // 只要 onload 被调用了,就应该请求下一页数据 onLoad () { // 1.页码值 +1 this.page++ // 重新请求接口数据 this.initArticleList() }, // 下拉屏幕顶部的屏幕进行刷新的处理函数 onRefresh () { // 页码值 +1 this.page++ // 重新请求接口获取数据 this.initArticleList(true) } }, components: { ArticleInfo } } </script> <style lang="less" scoped> .home-container{ padding: 60px 0 50px 0; .topBar{ padding: 10px 0; background: #fbe761; } } </style>
2.定制Vant主题 -vant@2.12.47
官网:Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)
1.定制步骤
步骤一 引入样式源文件
定制主题时,需要引入组件对应的 Less 样式文件,支持按需引入和手动引入两种方式。
按需引入样式(推荐)
在 babel.config.js 中配置按需引入样式源文件,注意 babel6 不支持按需引入样式,请手动引入样式。
module.exports = { plugins: [ [ 'import', { libraryName: 'vant', libraryDirectory: 'es', // 指定样式路径 style: (name) => `${name}/style/less`, }, 'vant', ], ], };
手动引入样式
// 引入全部样式 import 'vant/lib/index.less'; // 引入单个组件样式 import 'vant/lib/button/style/less'
步骤二 修改样式变量
使用 Less 提供的 modifyVars 即可对变量进行修改,下面是参考的 webpack 配置。
// webpack.config.js module.exports = { rules: [ { test: /\.less$/, use: [ // ...其他 loader 配置 { loader: 'less-loader', options: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 直接覆盖变量 'text-color': '#111', 'border-color': '#eee', // 或者可以通过 less 文件覆盖(文件路径为绝对路径) hack: `true; @import "your-less-file-path.less";`, }, }, }, }, ], }, ], };
如果 vue-cli 搭建的项目,可以在 vue.config.js
中进行配置。
// vue.config.js module.exports = { css: { loaderOptions: { less: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 直接覆盖变量 'text-color': '#111', 'border-color': '#eee', // 或者可以通过 less 文件覆盖(文件路径为绝对路径) hack: `true; @import "your-less-file-path.less";`, }, }, }, }, }, };
2.具体步骤
1.在入口文件 main.js 中修改引入的文件 index.css 为 index.less
//main,js import Vant from 'vant' // import 'vant/lib/index.css' // 为了能能够覆盖默认的 less 变量,这里一定要把后缀名改为less import 'vant/lib/index.less'
2.如果 vue-cli 搭建的项目,可以在 vue.config.js
中进行配置。
// 这个文件时 vue-cli 创建出来的项目全局配置文件 // 在 vue.config.js 这个配置文件中,可以对整个项目的打包和构建进行全局性的配置 const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, css: { loaderOptions: { less: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 第一种方案是:直接覆盖变量 //这种方案有个很大的缺点:就是每次修改要重新启动服务器才可以,因此很麻烦 'nav-bar-background-color': '#fbe761' // 第二种方案:或者可以通过 less 文件覆盖(文件路径为绝对路径) // hack: `true; @import "your-less-file-path.less";`, } } } } } })
3.less的正确打开方案
less是一个可编程的样式语言
官网:Less 快速入门 | Less.js 中文文档 - Less 中文网 (bootcss.com)
@width:10px; @height:@width + 10px; #header { width:@width; height:@height; } //编译为: #header { width:10px; height:20px; }
4.通过 theme.less 定制主题 ---推荐形式
hack: 'true; @import "less文件的绝对路径";'
首先先新建一个 theme.less
文件
// src => theme.less // 在 theme.less 文件中,覆盖 Vant 官方的 less 变量值 @yellow: #fbe761; // 覆盖 Navbar 的 less 样式 @nav-bar-background-color: @yellow;
// vue.config.js // 这个文件时 vue-cli 创建出来的项目全局配置文件 // 在 vue.config.js 这个配置文件中,可以对整个项目的打包和构建进行全局性的配置 // webpack 在进行打包的时候,底层用到了 node.js // 因此在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块 const path = require('path') const themePath = path.join(__dirname, 'src/theme.less') const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, css: { loaderOptions: { less: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 通过 theme.less 定制主题 ---推荐形式-------------------------- // 或者可以通过 less 文件覆盖(文件路径为绝对路径) hack: `true; @import "${themePath}";` } } } } } })
2.打包发布
在终端中运行 npm run build
进行打包发布
使用 npm run build
打包生成的dist文件,里面的 index.html 只能发布到服务器上,通过 http
协议才能被打开,直接双击打开是没有任何东西的,因为不是http协议,而是 file 协议.因此看不到任何东西
官方说明:
默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/
。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/
,则设置 publicPath
为 /my-app/
。
这个值也可以被设置为空字符串 (''
) 或是相对路径 ('./'
),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。
解决方法:
在 vue.config.js 中新增一个属性: publicPath
// 这个文件时 vue-cli 创建出来的项目全局配置文件 // 在 vue.config.js 这个配置文件中,可以对整个项目的打包和构建进行全局性的配置 // webpack 在进行打包的时候,底层用到了 node.js // 因此在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块 const path = require('path') const themePath = path.join(__dirname, 'src/theme.less') const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 如果想可以直接双击打开发布的文件,就添加此属性,如果想在服务器运行,就注释publicPath属性----------------------- publicPath: '', css: { loaderOptions: { less: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 此种方案 - 直接覆盖变量 // 'nav-bar-background-color': '#fbe761', // 通过 theme.less 定制主题 ---推荐形式-------------------------- // 或者可以通过 less 文件覆盖(文件路径为绝对路径) hack: `true; @import "${themePath}";` } } } } } })
2.铺垫知识 - ES6
1.ES6模块化
1.回顾:node.js中如何实现模块化
node遵循了 CommonJS的模块化规范,其中:
-
导入其他模块使用
require()
方法 -
模块对外共享成员使用
module.exports
对象
模块化好处:
大家都遵守了同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用
,利人利己
2.前端模块化规范的分类
在ES6模块化规范
诞生之前, JavaScript社区已经尝试并提出了 AMD,CMD,CommonJS
等模块化规范
但是,这些有社区提出的模块化标准,还是存在一定的差异性与局限性
,并不是
浏览器与服务器通用的模块化标准
,例如:
-
AMD 和 CMD 适用于浏览器端的JavaScript模块化
-
COmmonJS适用于服务器端的JavaScript模块化
3.什么是ES6模块化规范
ES6模块化规范是浏览器端和服务器端通用的模块化开发规范,它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习 AMD,CMD 或 CommonJS等模块化规范
ES6模块化规范中定义:
-
每个JS文件都是一个独立的模块
-
导入其他模块成员使用
import
关键字 -
向外共享模块成员使用
export
关键字
4.在node.js中体验 ES6 模块化
node.js中默认仅支持CommonJS模块化规范
,若想基于node.js体验和学习ES6模块化语法,可以按照如下两个步骤进行配置:
-
确保安装了
v14.15.1
或更高版本的node.js -
在package.json的根节点汇总添加
"type":"module"
节点
{ "type": "module", "name": "day1", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
5.ES6模块化的基本语法
ES6的模块化主要包含如下3种用法:
-
默认导处
和默认导入
-
按需导出
和按需导入
-
直接导入
并执行
模块中的代码
5.1 默认导出
默认导出的语法是: export default 默认导出的成员
let n1 = 10 // 定义模块私有成员 n1 let n2 = 20 // 定义模块私有成员 n2 (外界访问不到 n2 ,因为它没有被共享出去) function show() {} // 定义模块私有方法 show // 使用 export default 默认导出语法,向外共享 n1 和 show 两个成员 export default { n1, show }
5.2 默认导入
默认导入的语法: import 接收名称 from '模块标识符'
// 默认导入 import m1 from './1.默认导出.js' // 结尾一定要加 .js ,不然会报错 console.log(m1)
5.3 默认导出的注意事项
每个模块中,只允许使用唯一的一次
export.default ,否则会报错
5.4.按需导出
按需导出的语法: export
按需导出的成员
// 向外按需导出变量 s1 export let s1 = 'aaa' // 向外按需导出变量 s2 export let s2 = 'bbb' // 向外按需导出方法 say export function say() {}
5.5 按需导入
按需导入的语法: import {s1} from '模块标识符'
import { s1,say } from './按需导出.js'
5.6 按需导出与按需导入的注意事项
-
每个模块汇总可以使用
多次
按需导出 -
按需
导入的的成员名称
必须和按需导出的名称
保持一致 -
按需导入时,可以使用
as关键字
进行重命名 -
按需导入可以和默认导入一起使用
5.7 直接导入并执行模块中的代码
如果只想单纯的执行某个模块中的代码
,并不需要得到模块中向外共享的成员,此时,可以直接导入并执行模块代码.示例代码如下:
// 在当前模块中执行一个循环操作 for(let i = 0;i < 3; i++){ console.log(i) } // ------------------------------------------------------------ // 直接导入并执行模块代码,不需要得到模块向外共享的成员 import './5.直接运行模块中的代码.js'
2.Promise
1.回调低于
多层回调函数的相互嵌套,就形成了回调低于,示例:
setTimeout(() => { // 第 1 层回调函数 console.log('延时 1 秒后输出') setTimeout(() => { // 第 2 层回调函数 console.log('延时 2 秒后输出') setTimeout(() => { // 第 3 层回调函数 console.log('延时 3 秒后输出') },3000) },2000) }, 1000)
2.回调地狱的缺点
-
代码耦合性太强,牵一发而动全身,
难以维护
-
大量冗余的代码相互嵌套,代码的
可读性变差
3.如何解决回调地狱的问题
为了解决回调地狱
的问题,ES6
中新增了 Promise
的概念
4.Promise的基本概念
-
Promise
是一个构造函数-
我们可以创建Promise的实例:
const p = new Promise()
-
new
出来的Promise
实例对象,代表一个异步操作
-
-
Promise.prototype(原型对象)
上包含了一个.then()
方法-
每一个通过
new Promise()
构造函数得到的实例对象,都可以通过原型链的方法
访问到 .then() 方法,例如:p.then()
-
-
.then()
方法用来预先指定成功和失败
的回调函数
-
p.then(
成功的回调函数,失败的回调函数
) -
p.then(result => {},error => {})
-
调用 .then 方法时,成功的回调函数是必选的,失败的回调函数是可选的
-
4.基于 then-fs 异步读取文件内容
由于node.js官方提供的fs模块仅支持以回调函数的方式读取文件,``不支持promise的调用方式
,因此需要先运行如下的命令.安装 then-fs
这个第三方包,从而支持我们基于promise的方式读取文件的内容
npm i then-fs
调用then-fs提供的readFile()
方法,可以异步的读取文件的内容,它的返回值是Promise的实例对象
.因此可以调用 .then()
方法为每个Promise异步操作指定成功和失败时候的回调函数,示例:
import thenFs from 'then-fs' // 注意: .then() 中的失败回调是可选的,可以省略 thenFs.readFile('./files/1.txt','utf8').then(data => console.log(data),err => console.log(err)) thenFs.readFile('./files/2.txt','utf8').then(data => console.log(data),err => console.log(err)) thenFs.readFile('./files/3.txt','utf8').then(data => console.log(data),err => console.log(err)) // 异步任务按照从上到下顺序执行,但不用等待执行结果才往下执行,而是触发执行后,不需要等待执行结果的返回,就可以继续向下执行其余代码 // 因此异步任务的顺序是谁先执行完就谁先触发回调函数中的操作
注意:上述的代码无法保证文件的读取顺序
,需要做进一步的改进,因为是异步执行,所以不会按照从上到下的顺序,而是发生资源抢占,谁先抢到谁先输出
5. .then方法的特性
如果上一个 .then() 方法
中返回一个新的 Promise 实例对象
,则可以通过下一个 .then() 继续进行处理
.通过 .then() 方法的链式调用
,就解决了回调地狱
的问题
6.基于Promise的链式调用按照顺序读取文件
Promise支持链式调用
,从而就可以解决回调地狱的问题,示例:
// Promise支持链式调用,因此可以解决回调地狱的问题,并按照顺序读取文件的内容 thenFs.readFile('./files/1.txt','utf8') // 1.返回值是 Promise 的实例对象 .then(data => { // 2.通过 .then 为第一个Promise实例指定成功之后的回调函数 console.log(data) return thenFs.readFile('./files/2.txt','utf8') // 3.在第一个 .then 中返回一个新的Promise实例对象 }) .then(data => { // 4.继续调用 此处 .then 是为上一个.then 的返回值(新的Promise实例)指定成功之后的回调函数 console.log(data) return thenFs.readFile('./files/3.txt','utf8') // 5.在第二个 .then 中再返回一个新的 Promise 实例对象 }) .then(data => { // 6.继续调用 此处 .then 是为上一个.then 的返回值(新的Promise实例)指定成功之后的回调函数 console.log(data) })
7.通过 .catch 捕获错误
在Promise 的链式操作
中如果发生了错误
,可以使用 Promise.prototype.catch
方法进行错误捕获和处理:
// Promise支持链式调用,因此可以解决回调地狱的问题,并按照顺序读取文件的内容 thenFs.readFile('./files/1.txt','utf8') // 1.返回值是 Promise 的实例对象 .then(data => { // 2.通过 .then 为第一个Promise实例指定成功之后的回调函数 console.log(data) return thenFs.readFile('./files/2.txt','utf8') // 3.在第一个 .then 中返回一个新的Promise实例对象 }) .then(data => { // 4.继续调用 此处 .then 是为上一个.then 的返回值(新的Promise实例)指定成功之后的回调函数 console.log(data) return thenFs.readFile('./files/3.txt','utf8') // 5.在第二个 .then 中再返回一个新的 Promise 实例对象 }) .then(data => { // 6.继续调用 此处 .then 是为上一个.then 的返回值(新的Promise实例)指定成功之后的回调函数 console.log(data) }) .catch(err => console.log(err.message)) // 捕获上面Promise链式调用发生的错误,并输出错误的信息
8.Promise.all()方法
Promise.all() 方法会发起并行(同时发起)
的Promise异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)
,示例代码:
// 1.定义一个数组,存放3个读取文件的异步操作 const promiseArr = [ thenFs.readFile('./files/1.txt','utf8'), thenFs.readFile('./files/2.txt','utf8'), thenFs.readFile('./files/3.txt','utf8') ] // 2. 将 PromiseAll 的数组,作为Promise.all() 的参数 Promise.all(promiseArr) .then(([r1,r2,r3]) => console.log(r1,r2,r3)) // 2.1 所有文件都读取成功后触发.then .catch(err => console.log(err.message)) // 2.2 捕获到 Promise 异步操作中的错误则触发
注意:数组中的 Promise 示例的顺序,就是最终结果的顺序
9.Promise.race()方法
Promise.race()方法会发起并行
的Promise异步操作,只要任何一个异步操作完成了,就会立即执行下一步的 .then 操作()赛跑机制
,注意:该方法只会执行一个.then,也就是说只会输出最先读取完成的文件,示例代码如下:
import thenFs from 'then-fs' // 1.定义一个数组,存放3个读取文件的异步操作 const promiseArr = [ thenFs.readFile('./files/1.txt','utf8'), thenFs.readFile('./files/2.txt','utf8'), thenFs.readFile('./files/3.txt','utf8') ] // 2. 将 PromiseAll 的数组,作为Promise.all() 的参数 Promise.race(promiseArr).then(data => {console.log(data)}) // 2.1 任何一个异步操作读取成功就会触发.then .catch(err => console.log(err.message)) // 2.2 捕获到 Promise 异步操作中的错误则触发
10.基于Promise封装读取文件的方法
方法的封装要求:
-
方法的名称要定义为
getFIle
-
方法接收一个
形参fpath
,表示要读取文件的路径 -
方法的
返回值
我Promise实例对象
function getFile(fpath){ // 注意:这里的 new Promise() 只是创建了一个形式上的异步操作 return new Promise() }
如果想要创建具体的异步操作
,则需要在new Promise()构造函数期间,传递一个function函数,将具体的异步操作定义到function函数内部
,实例代码:
function getFile(fpath){ // 注意:这里表示一个具体的,读文件异步操作 return new Promise(() => { fs.readFile(fpath,'utf8',(err,dataStr) => {}) }) }
通过.then()指定的成功和失败的回调函数,可以在function的形参中进行接收,实例如下:
function getFile(fpath){ // resolve:调用 getFIles() 方法时,通过.then 指定的 成功 回调函数 // reject:调用 getFIles() 方法时,通过 .then 指定的 失败 回调函数 return new Promise((resolve,reject) => { fs.readFile(fpath,'utf8',(err,dataStr) => {}) }) } // getFile() 调用过程 getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)
Promise 异步操作的结果,可以调用resolve或reject回调函数进行处理,示例:
function getFile(fpath){ return new Promise((resolve,reject) => { fs.readFile(fpath,'utf8',(err,dataStr) => { if(err) return reject(err) // 如果读取失败,则调用失败的回调函数 resolve(dataStr) // 如果读取成功,则调用成功的回调函数 }) }) } // getFile() 调用过程 getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)