以下笔记基本跟随B站黑马程序员的结构学习
第一天 前端工程化与webpack
课程目标
前端工程化
- 前端开发并不是简单的使用现成的组件库拖拽式的东平西凑
- 实际开发中是模块化(js/css的模块化)、组件化(复用现有的UI结构、样式、行为)、规范化(使用git等工具,目录结构合理有序)、自动化(自动构建、部署、测试)
- 组件化可以使用Layui和element+等组件库
- 目录结构规范化可以使用脚手架的框架
- 模块化是功能的聚类,组件化是样式的聚类
- 目前主流的前端工程化解决方案是webpack(项目)和parcel(第三方库)
webpack
主要功能:①模块化开发②处理代码压缩(为了提高速度)混淆③处理浏览器js兼容性④性能优化
在vue、react中实际上都是基于webpack进行开发的
创建列表隔行变色项目
npm install jquery -S
-S(--save)命令明确告知npm记录依赖在package.json中,虽然默认会记录
此时如果使用
import $ from 'jquery'
这个es6的语法,将其作为js解释的浏览器会出现以下报错:
此时应该使用webpack
安装配置webpack
安装
使用npm安装webpack和webpack-cli
-D(--save-dev)作用同-S都是进行记录,不过是记录在package.json中的devDependencies
dependencies记录的是开发中需要使用,部署时也要使用的
devDependencies时只在开发中使用的,部署上线并不需要用到
(可以在npmjs.com中查看具体的第三方库应该如何使用)
配置
使用
运行项目后多出了dist目录,其中有main.js,是根据使用的第三方库(如jQuery)和自己编写的js代码自动生成的具有兼容的js
直接在需要处导入main.js即可使用es6命令
不同mode的应用场景
开发模式下并不会压缩代码,所以生成的文件较大
发布模式(production)下会对代码进行压缩,但是run dev所需的事件会稍微长一点(压缩过程)
entry和output
运行npm run dev时,实际上是运行webpack,找到webpack-config.js中的导出项,于上文是mode
在webpack4.x及以上版本中,默认约定如下:
①entry: 默认的打包文件入口为src -> index.js
②output: 默认的输出文件入口为dist -> main.js
具体的修改配置方法:
__dirname代表当前文件的目录,再此就是项目的根目录
插件
修改源代码并不能直接重新渲染网页
在当前项目中,修改源代码并不能直接重新渲染网页,需要重新启动项目才能改变main.js,所以才能改变网页
可以使用webpack插件webpack-dev-server监听源代码然后修改
安装插件同样使用npm安装,然后在package.json中修改dev命令
之前直接查看文件使用的是ftp协议,而现在会打开一个端口(8080)然后在其中查看,这时使用的是http协议
如果这时在8080端口中进入src,会直接展示页面,这时由于浏览器的性质:进入文件夹中自动展示index.html页面
需要特备注意的是,server会自动生成一个在内存中的output文件,虽然dist中有但并不是真正会被监听的,所有的修改是及时保存在内存中的文件中的,而dist中的静态的文件只有重新run之后才会改变
也就是说想要实现直接修改网页的效果,我们需要引用的是内存中的js文件,即/main.js(虽然看不见)
html-webpack-plugin
该插件可以帮助打开8080端口时直接进入指定html页面,配置方式如下:
这个页面时复制在内存中的index.html,同时会自动引入main.js,对src中的html修改不会影响
配置devServer
open属性可以修改是否直接打开页面
host属性可以修改主机地址
port属性可以修改端口号
loader
webpack中默认只能处理js文件,其他格式的模块(比如css)需要用特定的loader才能处理
同时如果js文件中包含了高级js语法,如果配备了babel文件,也会调用loader进行处理
同样的使用npm安装loader(比如css-loader)然后在webpack-config.js中配置特定后缀使用特定loader
css文件的调用
为了更加模块化开发,不应在html中引入css,而是在js中用import引入css文件
如上图,调用css-loader和style-loader对其进行处理,注意rules从后往前进行处理,先通过css,处理的结果再交给style,最后的结果会合并到main.js中
less文件
less.-loader -> css-loader ->style-loader
注意要下载less.loader和less,因为前者依赖于后者
base64
在普通的引用图片渲染时,要等到所有标签先渲染完了,浏览器才会请求图片,但是使用base64格式就可以让图片直接被渲染出来,在页面很多小图标时可以避免很多不必要的网络请求
具体的业务场景比如:使用import导入图片,然后动态的给img src赋值
这时候可以这样
import logo from './images/logo.jpg'
$('.box').attr('src',logo)
对jpg进行处理需要使用url-loader和file-loader,设置limit让只有小于limit的图片转为base64格式
在配置url-loader时用&区分多个参数
module: {
rules: [
{test: /\.jpg|png|gif$/, use: 'url-loader?limit=22229&outputPath=images'}
]
}
处理样式的过程
在import文件时返回的是undefined,但是最后生成的main.js(或者bundle.js)会包含引入的文件
如果查看最后的main.js会发现是MODULE CSS_LOADER_EXPORT .push(样式内容)
所以在很多情况下,import接受到的内容是undefined,比如css。这时其实没有必要命名,直接使用即可
import './css/index.css'
js高级语法(babel)
某些webpack无法处理的高级js语法(比如@装饰器),可以使用babel-loader进行处理转换为低级的js代码
安装babel-loader,babel/core,babel/@plugin-proposal-decorators三个loader
用排除项排除第三方包的转换来节约转换速度,且第三方包也不需要考虑兼容性问题,只需要转换自己写的js
注意,使用babel-loader时还需要创建babel-config.js,进行如下配置声明babel可用的插件babel/plugin-proposal-decorators
module.exports = {
plugins: [[]'@babel/plugin-proposal-decorators', {legacy: true}]
}
装饰器简单介绍
function info(target) {
target.info = 'person info'
}
@info
class Person {}
console.log(Person.info) //person info
info装饰器为person类赋予了一个info属性
打包发布
发布项目就是前端把项目生成一份最终的结果(包含所有资源),发送给后端,后端再进行上线
现在主要需要解决的是:bundle.js等现在存在于内存中,需要生成实际在物理磁盘中的文件
可以使用clean-webpack-plugin自动的删除dist目录,然后修改之前的代码生成js目录js->dist这样不至于有多个文件且结构清晰
关于插件的用法可以在npm官网查询
sourceMap
sourceMap是一个信息文件,里面存放着位置信息,也就是储存着webpack压缩混淆后的代码转换前的位置
可以让报错工具直接显示原始代码而不是bundle,js中的代码
开发环境下默认的sourcemap记录的是生成后的代码位置,如果要让其变为源代码的行数,配置
module.export = {
mode:'development'
devtool: 'eval-source-map'
// 其他配置项
}
在发布时出于安全性考虑建议关闭sourcemap,防止源代码泄露
也可以使用nosources-source-map只定位行号不给出源码
CLI
在实际开发中并不需要自己配置webpack,可以使用脚手架cli一键生成带有webpack的项目
第二天 Vue基础入门
课程目标
Vue简介
- 是一个框架
- 框架是一套解决方案,根据特定的结构处理问题
- 数据驱动视图
- vue监听数据变化,自动重新渲染页面结构,不需要手动操作DOM元素
- 数据驱动视图是单向的数据绑定
- 双向数据绑定(v-model)
- 在表单等时,用户填写内容同步到后台
MVVM
Vue实现上述特性的原理时MVVM,即Model,View,ViewModel
Model表示当前页面渲染时所依赖的数据源
View表示当前页面的DOM结构
ViewModel表示Vue的实例,时MVVM的核心
-
DOM Listeners监听DOM结构(view -> model)
-
Data Bindings绑定数据到页面(model -> view)
基本使用
-
导入vue.js的脚本文件
- 导入了vue.js的文件之后,在window下生成vue实例的构造函数
-
在页面中声明一个被vue控制的DOM区域
-
<div id='app'>{{msg}}</div>
-
-
创建vm实例对象
-
//vue 2.x const vm = new Vue({ el: '#app' msg: 'hello vue' }) //vue 3.x const Counter = { data() { return { msg:'hello vue' } } } Vue.createApp(Counter).mount('#app')
-
指令
指令是模板语法,用于辅助开发者渲染页面结构
可以分为6类:
内容渲染指令
渲染DOM元素中的文本内容
v-text
会覆盖元素内部原有的文本内容
<p v-text="username">被覆盖</p>
{{}} 插值表达式(Mustache)
<p>{{username}}不被覆盖</p>
v-html
<p v-html="vhtml">被覆盖</p>
<script>
const vm = new Vue({
el: "#app",
data: {
//其他数据...
vhtml: '<h1 style="color: red">html标签还能带有样式</h1>'
}
})
</script>
会生成指定的html元素到渲染区域,同时覆盖掉原有内容
属性绑定指令
上述的渲染指令只能用在内容节点,不能在属性中使用,需要的话要使用v-bind
<input type="text" v-bind:placeholder="tips">
可以简写为冒号
<input type="text" :placeholder="tips">
实际上,在指令中都可以编写js代码
<p>{{tips}}的反转结果是{{tips.split('').reverse().join('')}}</p>
<div :title="'box'+index">这是一个div</div>
事件绑定指令
使用v-on(简写@)可以绑定一个处理函数到事件上
<button type="button" @click='doPlus(1)'>增加1</button>
<p>{{count}}</p>
<script>
const vm = new Vue({
el: "#app",
data: {
count: 0,
},
methods: {
doPlus(n) {
this.count += n
}
}
})
</script>
在绑定事件的处理函数调用时可以使用()也可以不使用,在需要参数时必须要,无参数时不一定需要
事件名一般为原生DOM对象的原生事件名去掉on改为使用@。如onkeyup-> @keyup
同时可以传入事件作为参数,使用target找到事件触发的目标,对其进行操作
oddTurnBlue(e) {
this.count += 1
if(this.count % 2 != 0) {
e.target.style.backgroundColor = 'blue'
}else {
e.target.style.backgroundColor = ''
}
}
同时vue提供了一个内置变量$event,可以同时传入其他参数
<button type="button" @click='oddTurnBlue(10,$event)'>count奇数变蓝</button>
oddTurnBlue(n, e) {
this.count += n
if(this.count % 2 != 0) {
e.target.style.backgroundColor = 'blue'
}else {
e.target.style.backgroundColor = ''
}
}
事件修饰符
对事件可以使用各种修饰符,比如prevent阻止默认行为(比如跳转和提交)和stop阻止事件冒泡(冒泡:子DOM节点和父DOM节点都有某个事件,触发了子再触发父。阻止冒泡就是不触发父节点的事件)
<a href='www.baidu.com' @click.prevent='jump'>不进行跳转而进行jump</a>
监听键盘事件时,可以监听具体的键盘,比如@keyup.enter='enter'
双向绑定指令
v-model双向数据绑定指令可以在不操作DOM的前提下获取表单的数据,而v-bind中页面变化不会改变数据源
<p>用户的名字为: {{username}}</p>
<input type="text" v-model="username" placeholder="请输入用户名">
同时可以设置加载模式
- .number 只得到数值
- .trim 自动去除头尾空格
- .lazy 懒加载,只有focus移走才会改变
条件渲染指令
条件渲染语句有v-if和v-show,辅助开发者控制dom的显示和隐藏
<p v-if='flag'>flag为true展示,false隐藏</p>
<p v-show='flag'>flag为true则展示,false隐藏</p>
区别在于v-if=false时直接创建或移除整个dom节点,在元素中无法查找到,而v-show=false则是设置为display:none
如果要频繁切换元素的显示状态,用v-show性能会更好
而如果默认情况时不需要展示该元素时,为了直接不创建,用v-if会性能更好
同时可以使用v-else,v-else-if充当if的else-if块,可以连续使用
列表渲染指令
v-for可以基于一个数组来渲染一个列表结构,使用item in items结构
<ul>
<li v-for="item in items">姓名是: {{item.name}}</li>
</ul>
items: [
{id: 1,name: 'vimerio'},
{id: 2,name: 'chen'}
]
同时还可以指定数组中元素的索引
<tbody>
<tr v-for="(item, index) in items">
<td>{{item.name}}</td>
<td>{{item.index}}</td>
</tr>
</tbody>
官方推荐对v-for进行key属性的绑定,且key应该为id,key的值不可以重复
不应该使用index作为key的值,因为索引不具有唯一性(唯一性不只是不重复,而是指与实体具有强的绑定关系)unshift,push等都会改变数组
比如在一个列表中允许用户选中第二个item给与其selected=true的属性,若为索引则vue会绑定属性在index=1的item上,但是这时用户新增了一个item在最前方(lsit.unshift()),于是索引集体向后移动一位,用户选中的项目改变了,这就是列表状态的紊乱
使用key可以提升性能,防止列表状态紊乱
<tbody>
<tr v-for="(item, index) in items" :key='item.id'>
<td>{{item.name}}</td>
<td>{{item.index}}</td>
</tr>
</tbody>
案例——品牌列表
html&css
整体界面使用bootstrap制作,panel中使用panel-heading制作标题(‘添加品牌’),panel-body中使用input-group链接品牌名称span和input,同时设置panel-body为form-inline让button与其在同一行,button设置为primary蓝色。
表格用table,具体样式没啥好说的。
js
整体功能的实现也比较简单,这里说两个坑
label需要动态指定
需要动态指定("'cb'+item.id")input的id和label的for,不然就会导致label点击所产生的的更改都在第一个标签上出现。
删除功能
这里应该使用filter,返回包含所有满足条件的元素的新数组。在此处条件为item.id !== id,也就是剔除掉了item.id == id 的元素,进而实现删除。
第三天 Vue基础入门
学习目标
过滤器
vue3已经弃用,不需要掌握
简单的来说就是{{data | filter}},在插值表达式中定义一个filter函数来处理data,
可以分为私有过滤器和用vue实例化的全局过滤器
侦听器
watch侦听器可以让开发者监听数据的变化,只要监听值变化就会触发watch中的函数,函数一定要与数据名同名
const cm = new Vue({
el: "#app",
data: {username: ''},
watch: {
//监听username的变化
//newVal是变化后的新值,oldVal是变化前的旧值
username(newVal, oldVal) {
console.log(newVal, oldval)
}
}
})
侦听器可以判断用户名是否被占用,调用jQuery中的Ajax发起请求,判断newVal是否被占用
$.get('https://www.xxx.cn/api/finduser/'+newVal)
如果希望页面渲染之后立马执行,需要把监听器设置为对象而不是函数,同时设置immediate: true
watch: {
//定义对象格式侦听器
username: {
handle(newVal, oldVal) {
console.log(newVal, oldval)
},
//进入页面立即触发(默认值为false)
immediate: true
}
}
如果侦听的是对象,对象中间属性的变化并不会被方法侦听器侦听到,同样需要对象侦听器进行深度监听,监听对象中每个属性的变化,不过也可以用方法侦听器来监听单个属性的变化
data: {
info: {
username: ''
}
}
watch: {
//定义对象格式侦听器
username: {
handle(newVal, oldVal) {
console.log(newVal, oldval)
},
//开启深度监听,只要对象中任何一个属性发生变化了,都会触发侦听器handle
deep: true
},
'info.username'(newVal, oldVal) {
console.log(newVal, oldval)
}
}
计算属性computed
计算属性是通过一系列运算之后最终得到的一个属性值,这个动态计算出来的值可以被模板结构或者methods方法使用,提高复用性
计算属性在定义时要定义为方法格式 name() {},但是使用时是直接当做属性使用
使用``模板字符串时,中间的属性要用${}框起来
data: {
r:0, g: 0, b: 0
},
computed: {
rgb() { return `rgb(${this.r}, ${this.g}, ${this.b})`}
},
methods: {
show() {console.log(this.rgb)}
}
axios
axios是一个专注于网络请求的库,基本用法如下:
axios({
method: '请求的类型',
url: "请求的url地址",
}).then(result) => {
//.then用来指定请求成功后的回调函数
//形参中的result时请求成功后的结果
}
调用axios得到的返回值时一个promise对象,可以使用.then来进行处理成功事件,.catch处理失败事件
axios拿到服务器数据之后会进行包装,包含很多项,其中data时服务器真实的数据
axios传参
axios({
method: '请求的类型',
url: "请求的url地址",
//url中的查询参数(get参数)
params: {},
//请求体参数(post参数)
data: {}
}).then(result) => {
//.then用来指定请求成功后的回调函数
//形参中的result时请求成功后的结果
}
使用详情参见:axios详解
vue-cli
vue-cli是基于webpack的脚手架工具,可以帮助开发者快速生成项目,不用配置webpack
进入命令行快速创建:vue create 项目名称
第三项选择手动安装功能
css预处理器可以处理less等,linter是约定代码风格的,不是团队使用可以不装
生成项目之后,目录结构如下:
其中main.js是项目的入口文件,APP.vue是项目的根文件,由于是单页面应用SPA,所以只有一个index.html
vue项目的运行流程:通过main.js把APP.vue渲染到index.html中
vue组件
以.vue结尾,每个组件可以分为三部分:template模板,script功能,style样式
在template中只能有一个根节点
其中script需要导出,组件中的data应该是一个函数,return出数据
如果要启用less语法,需要指定style的lang为less:<style lang="less"></style>
<script>
export default {
data() {
return {
username: ''
}
}
}
</script>
js第四天 组件与生命周期
学习目标
组件之间的关系
组件在封装好之后,彼此之间互相独立,不存在关系
在使用组件时,根据彼此的嵌套关系,形成了父子关系、兄弟关系
使用组件的步骤
-
在根组件中用import语法导入需要的组件(vue自动配置@代表src目录)
import Son from '@/components/Son.vue' -
使用component节点注册组件
export default { name: 'App', components: { Son } } -
以标签形式直接使用
<div id='#app'> <Son></Son> </div>
私有组件和全局组件
通过在一个组件的component节点下注册组件,子组件只能在父组件内部被使用,如果有组件使用频率高,需要在很多的组件下注册十分繁琐,所以可以注册一个全局组件,不用私有注册就可以全局使用
在main.js入口文件中:
import Count from '@/components/Count.vue'
//参数1是注册名,可以作为标签名,参数2是组件名
Vue.component('MyCount', Count)
组件的props属性
props是组件的自定义属性,在封装通用组件的时候,使用props可以让同一个组件满足不同的需求,提高复用性
export default {
name: 'App',
props: ['自定义属性A','自定义属性B','其他自定义属性...']
data() {
return {
}
}
}
定义了自定义属性后,可以在组件同名标签中使用它,并且给它传递值,这个值可以被当做类似于data中的值被直接使用在template的插值表达式中,比如:
<template>
<p>count 的值是 {{ 自定义属性A }}</p>
</template>
这时在父组件中使用时可以添加标签的属性传递值
<MyCount 自定义属性A ='9'></MyCount>
如果想要传递回一个number值,可以使用v-bind绑定属性,因为-bind中默认为js语句,而js语句中直接输入9,返回的是number9而不是字符串"9"
props一般是只读的,不要更改props的值,因为这个值是会被随意改变的
如果要使用props的值,应该将props的值转存到data中,比如:
export default {
name: 'App',
props: ['inita','initb']
data() {
return {
count: this.inita,
pace: this.initb
}
}
}
如果组件的使用者并没有传入值,需要给出一个default值来防止出现undefined+1这种情况
同时还可以定义传入的值的类型type
还可以定义必传项required
需要定义这些属性时需要将props写成对象
props: {
inita: {
default: 0,
type: Number,
required: true
},
initb: {
default: 1,
type: Number
}
}
样式冲突
默认情况下写在.vue中的style会全局生效,因为看似样式只写在了组件中,但是是在整个index.html中使用的,这就可能会造成多个组件中的样式冲突问题
如果要限制一个组件中的style只能被该组件使用,应该在style标签中添加scoped属性,scoped的原理就是给这个组件的所有元素生成一个独一无二的属性(data-v)然后通过属性选择器给它们添加样式
<style scoped="scoped"></style>
但是scoped是有缺陷的,如果想要在scoped的父组件中修改子组件的样式,将不会生效,这时需要使用deep
//子组件中包含h5
<style scoped="scoped">
/deep/ h5 {
color: pink
}
</style>
vue组件的实例对象
浏览器是无法自动解析vue结尾的文件的,vue组件是通过package.json中的vue-template-compiler(vue模板编译器)解析为js文件给浏览器执行的
可以把.vue中的内容理解为一个构造函数,而组件的使用者用标签使用组件时生成了一个组件的实例对象
组件的生命周期
一个组件的生命周期包括个阶段,可以在生命周期的不同阶段进行各种操作
生命周期函数是vue的内置函数,会在特定时间点自动运行
-
组件创建阶段
(Init Events&Lifecycle:这个阶段组建的props/data/methods尚未被创建,处于不可用状态)
- beforeCreate
(Init injection&reactivity:这个阶段初始化props/data/methods)
2.created:组件的prop/data/methods已经被创建好,但是模板结构尚未生成(尚未挂载)。
这个阶段一般会发Ajax请求拿数据
(Compile template into render function/Compile el's outerHTML as template)
3.beforeMount:基于数据和模板,在内存中编译生成HTML结构,将要把内存中编译好的HTML结构渲染到 浏览器,此时还没有当前组件的dom结构
(Create vm.$el and replace 'el' with it)
4.mounted:浏览器包含当前组件的dom结构
-
组件运行阶段
(when data changes)
- beforeUpdate:将要根据变化的数据重新渲染组件的模板(数据已经更新,DOM还是旧的)
(Virtual DOM re-render and patch)
2.updated:想要获取更新后的dom节点,需要使用这个函数
-
组件销毁阶段(v-if=false是常见的组件销毁)
-
beforeDestroy:
(Teardown watchers, child components and event listeners)
2.destroyed:
-
组件间的数据共享
父子组件数据共享
父向子——props
使用自定义属性props,父组件定义props的值就可以向子组件传值
子向父——自定义事件
在子组件中定义一个数据data,父组件也需要定义一个data中的数据来接受子组件的值,然后使用自定义事件this.$emit传值
在此,自定义了一个事件numchange,当事件numchange触发调用getNewCount
事件numchange是被this.$emit触发的,触发时传递了this.count,父组件在接受时用形参val捕获count并且转存到countFromSon中
//子组件
methods: {
this.count += 1;
this.$emit('numchange',this.count)
}
//父组件
<Son @numchange='getNewCount'></son>
...
methods: {
getNewCount(val) {
this.countFromSon = val
}
}
兄弟组件数据共享——EventBus
生成一个新的vue实例作为bus,发送数据方调用bus.on,并且提供捕获数据的形参以及接收数据的data
第五天 ref & 购物车案例
学习目标
ref引用
因为在vue中使用MVVM,所以极少有操作dom的需求,并不需要安装jQuery
但是实在需要操作dom时,可以使用ref引用,在不调用dom的api的情况下获得dom的引用
在每一个vue组件实例中,都包含一个内置的$refs对象,里面存储着对应dom元素或组件的引用,默认情况指向一个空对象
当在组件模板中指定某个标签的ref属性值之后,refs: {myh1: h1}`
<template>
<div>
<h1 ref='myh1'></h1>
</div>
</template>
这时候就可以通过:this.$refs.myh1来获取这个dom节点的引用,对其进行包括样式修改的一系列修改
引用组件实例也是一样的,在父组件模板中,子组件本来就是以一个自定义标签的形式出现的,这个自定义标签同样也可以有ref属性
其实这样实现父子组件的数据共享会更快一些
**注意:**当更新数据之后想要获取更新后的dom节点,需要调用this.$nextTick(callback函数)来执行,因为在组件的生命周期中,beforeupdate时dom还没有重新渲染(如果改变的是v-if的值,dom会直接undefined),需要等到dom重新渲染好之后再执行,同时这个函数中应该使用箭头函数来避免this的错误
购物车案例
感觉没有非常值得记录的知识点,主要就是熟悉一下之前的内容
基本用到的组件有四个:header、footer、goods、counter,数据是用axios拿的
header就一框,counter写了一万次了,真正要写的就是goods里面的列表渲染和footer里面的按钮事件
goods中通过id来记录state从而改变选中状态 this.$emit('stateChange',{id,value}),就像之前有一次讲过label的绑定问题(见第四天)。候选框本事有一个@change事件,给它一个处理函数就可以了
最后的总价用数组的filter和reduce去做会比较快,全选按钮用every做比较快
第六天 vue组件的高级用法
学习目标
动态组件
动态组件指的是动态切换组件的显示与隐藏
基本用法
使用vue内置的<component>组件作为占位符,用v-bind绑定一个data,在其中切换组件
<component :is='comName'>
<button @click="comName = 'Left'"> 展示Left</button>
<button @click="comName = 'Right'"> 展示Right</button>
</component>
keep-alive
在组件切换时,组件中的data不会记录,因为组件切换时,原有组件被销毁,即使之后切换回来一个同样的组件,也只是新的组件实例而不是原来的组件
<keep-alive>
<component :is='comName'>
<button @click="comName = 'Left'"> 展示Left</button>
<button @click="comName = 'Right'"> 展示Right</button>
</component>
</keep-alive>
keep-alive有对应的生命周期:缓存和激活
可以用deactivated和activated生命周期函数来监听缓存和激活事件
同时,可以使用keep-alive标签的include属性(默认全部缓存)来指定只有部分组件被缓存 <keep-alive include='Left,Right'>
当然,也可以使用exclude属性来排除不需要被缓存的组件,但是不能与include属性同时使用 <keep-alive exclude='Right'>
插槽slot
插槽是组件封装者在封装组件时,允许开发者把不确定的、希望用户指定的部分定义为插槽
可以在组件template中使用 <slot>标签进行占位,让用户自己定义想要使用的标签,同时直接写在slot标签中的内容会作为后备内容,如果用户没有在插槽中填充任何内容,就会启用后备内容
插槽可以避免组件的过度嵌套导致数据传输不方便,可以在子组件A中定义插槽,父组件B使用子组件A时在插槽处使用另一个组件C,避免把组件C作为B的子组件
具名插槽
插槽需要有一个name属性,默认为default,同时如果填充内容没有指定v-slot指令绑定name的话,默认填充到default插槽中,这也就是说可以通过name和v-slot的配合来使指定内容渲染到指定插槽
v-slot指令只能使用在template标签中,所以要用template标签包裹想要指定渲染的内容外
<template v-slot:default>
<p>指定渲染到default插槽的内容</p>
</template>
v-slot可以简写为#
作用域(scope)插槽
在插槽中可以向用户传递一些信息,通过属性:值的方法写入slot标签传递,用户在使用时用对象接收信息
实际上就是一类子向父传值
<template #default='scope'>
<p>指定渲染到default插槽的内容</p>
<p>{{ scope }}</p>
</template>
自定义指令
开发者自己定义的指令,可以分为私有自定义指令(组件内的)和全局自定义指令
私有自定义指令
定义私有自定义指令v-color:
xxx.vue
directives: {
color: {
bind(el) {
el.style.color= 'red'
}
}
}
当指令被绑定到元素身上时,就会触发bind函数,获取当前元素并存为el,然后就可以对el元素进行操作
如果需要指定值时,应该使用binding.value,这时就可以使用v-color=" 'red' "
//xxx.vue
directives: {
color: {
bind(el,binding) {
el.style.color= binding.value
}
}
}
但是bind函数只会在第一次被绑定到元素上时被触发,如果想要动态的更改binding.value后改变指令效果需要使用update函数,写法同bind,写在bind函数下即可
如果bind和update中的逻辑完全相同,可以把对象格式的color改为函数格式的:
directives: {
color(el,binding) {
el.style.color= binding.value
}
}
全局自定义指令
//main.js
Vue.directive('color', function(el,binding) {
el.style.color = binding.value
})
ESLint
代码约束工具,非团体可以不用
axios简便用法
如果多个组件都需要发起axios请求的话,可以在main.js中对vue的原型进行axios注册
Vue.prototype.$http = axios
然后就可以在各个组件中适用this.axios而不用反复import axios
同时可以在main.js中配置请求根路径
axios.default.baseURL = '请求根路径'
但是这样不利于api接口的复用
第七天 路由vue-router
学习目标
路由概念
路由就是hash地址(#锚链接)与组件的对应关系
锚链接会导致浏览历史的产生(可以前进后退)但是不会刷新页面
前端路由的工作方式:
- 用户点击路由链接,导致url地址栏中的hash地址改变(onhashchange事件)
- 前端路由监听到了hash地址的变化
- 前端路由把hash地址对应的组件渲染到浏览器中
路由基本用法
基本配置:
-
安装vue-router包
npm install vue-router -S -
创建路由模块
-
创建
src/router/index.js -
-
导入并挂载路由模块 在main.js中render函数下声明router
-
声明路由链接和占位符
-
在app.vue中使用
<router-view>作为占位符 -
在router/index.js中配置链接
在构造函数中生成名为routers的数组,放入地址与组件的对应关系(路由规则),要记住需要导入组件
routers: [ {path: '/home', component: Home}, {path: '/about', component: About}, ]
-
在使用组件时,可以用a标签跳转到锚链接,但是最好使用router-link标签代替a标签
<router-link to='/home'>首页</router-link>
路由重定向
用户在访问地址A的时候,强制用户跳转到地址B,从而展示特定的组件页面
通过redirect实现
routers: [
{path: '/', redirect: '/home'}
{path: '/home', component: Home},
{path: '/about', component: About},
]
嵌套路由
通过路由实现组件的嵌套展示,简单的说就是通过路由跳转多次,比如app->about->tab1
注意子级路由链接要带上父组件的hash地址,比如tab1的地址应写为:<router-link to='/about/tab1'>
声明子路由规则时需要使用children属性,子路由规则path可以不加斜线
routers: [
{path: '/', redirect: '/home'}
{path: '/home', component: Home},
{path: '/about', component: About,redirect: '/about/tab1',children: [
{path: 'tab1', component: Tab1},
{path: 'tab2', component: Tab2}
]
},
]
除了直接重定向,还可以使用默认子路由,如果children中某个规则的path值为空字符串,则其为默认子路由
routers: [
{path: '/', redirect: '/home'}
{path: '/home', component: Home},
{path: '/about', component: About,children: [
{path: '', component: Tab1},
{path: 'tab2', component: Tab2}
]
},
]
动态路由
如果很多类似的路由规则 /movie/1-100,如果重复定义规则则太过繁琐,可以把hash地址中可变的部分定义为参数项,从而提高复用性。
在router-link中无论是movie/1还是movie/2都会展示Movie组件,在Movie组件中可以根据id的值来展示对应电影的详情信息
**注意:**在hash地址中,/后面的参数项叫做 路径参数,而?后面的参数项叫做 查询参数
路由参数可以通过两种方式获取:
1.通过Movie组件的this.$route.params.id来获取id值,从而进行id的传输进而展示特定的内容
2.给当前路由规则开启props属性: props: ture,然后在组件的props中接收id
而查询参数则可以:
1.通过Movie组件的this.$route.params.query来获取
导航
点击链接导致组件切换被称为 导航
导航可分为声明式导航和编程式导航
**声明式导航:**在浏览器中,点击链接实现导航(a标签或router-link标签)
**编程式导航:**在浏览器中,调用api实现导航(比如使用location.href)
编程式导航
this.$router.push('hash地址')
- 跳转到指定hash地址,并且有浏览记录(可以前进后退)
this.$router.replace('hash地址')
- 跳转到指定hash地址,并且替换掉当前的历史记录(无法后退回原来页面)
this.$router.go(数值n)
- 在浏览历史中进行n步的前进(n为负数则为后退)
- this.router.forward
- this.router.back
导航守卫
导航守卫可以控制路由的访问权限
全局前置守卫(全局生效,跳转前生效)
在index.js的router实例对象中调用beforeEach方法,每次发生导航跳转时都会调用回调函数
如果要让用户跳转到别的页面(不是to中的页面),可以用next('hash地址'),比如未登录的用户跳转到next('/login')
如果强制停留在当前页面,可以使用 next(false)
第八天 头条项目
学习目标
1.项目结构
可以看到src中多了个views文件夹,其中也是组件
如果某个组件是通过路由来进行切换的,放在views
而可复用的组件,则放在components中
2.vant安装和配置
npm i vant -S进行安装
选择方式三安装,最快捷,虽然会很大,但是上线的时候可以采取措施让其优化
3.使用tabbar组件并开启路由模式
路由相关的组件页面放在views中,所以Home和User都放在views中
在app组件中生成一个tabbar并声明一个router-view标签来存放路由跳转的内容
开启tabbar的路由模式会自动切换高亮
4.制作home的header
使用navbar组件制作header
由于navbar和tabbar都是fixed,脱离标准文档流,所以应该设置一下上下边距以免内容无法显示
审查元素,找到控制颜色的元素,进行样式覆盖,注意十六进制颜色不要写成字符串,同时由于__title没有data-v,data-v实际上加给了hom-container,所以需要使用deep
如需要对一个组件所有实例都改变样式,需要定制主题
5.封装request模块
可以创建request模块,创建axios实例、配置baseURL并且输出,避免网址的重复输入
6.封装initArticleList方法
由于进入首页一开始就要拿到文章并且渲染,所以应该在created生命周期中写入initArticleList方法
async和await一起使用,将一个promise对象的值抽取出来,同时使用解构赋值{ data:res }只讲获得的数据的data分离保存,并且给与res的新名称,通过params参数指定查询参数