一、前端工程化
JS的复用就是模块化
UI样式复用就是组件化(LayUI里面的都是组件化)
所以说前端工程化就是有规范的写,不能你一个样式我一个样式,不够统一不够专业!
二、WebPack
是什么
前端工程化的具体实现方案
基本使用
实现奇偶行变色
1.初始化包管理工具
通过npm init -y生成
2.安装jquery
-S的意思是–save
npm install jquery -S
3.在项目中安装webpack
-D就是把包放到devDependencies下,打包运行的时候不会被打包
-D为–save-dev简写
npm install [email protected] [email protected] -D
4.在项目中配置webpack
在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:
module.exports = {
mode: 'development'
};
在 package.json 的 scripts 节点下,新增 dev 脚本如下:
tips:webpack要和src同目录
5.运行代码
通过npm run dev运行
这里的webpack将用jQuery写的操作和一些新的写法转换成了能被浏览器兼容的js文件(dist下的main.js)
实现效果如下:
mode 的可选值
development
开发环境
不会对打包生成的文件进行代码压缩和性能优化
打包速度快,适合在开发阶段使用
production
生产环境
会对打包生成的文件进行代码压缩和性能优化
打包速度很慢,仅适合在项目发布阶段使用
webpack.config.js 文件的作用
webpack.config.js 是 webpack 的配置文件。
webpack 在真正开始打包构建之前,会先读取这个配置文件, 从而基于给定的配置,对项目进行打包。
注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关 的语法和模块进行 webpack 的个性化配置。
webpack 中的默认约定
在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:
① 默认的打包入口文件为 src -> index.js
② 默认的输出文件路径为 dist -> main.js
注意:可以在 webpack.config.js 中修改打包的默认约定
自定义打包的入口与出口
在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。
示例代码如下:
const path = require('path')//导入node.js中专门操作路径的模块
module.exports = {
mode: 'production',
entry: path.join(__dirname,'./src/index.js'),//打包的入口文件
output: {
path: path.join(__dirname,'./dist'),//存放路径
filename: "nice.js"//输入的文件名
}
};
webpack中的插件
webpack 插件的作用
通过安装和配置第三方的插件,可以拓展 webpack 的能力,从而让 webpack 用起来更方便。最常用的webpack 插件有如下两个:
① webpack-dev-server
类似于 node.js 阶段用到的 nodemon 工具(修改代码自动重新打包和构建)
每当修改了源代码,webpack 会自动进行项目的打包和构建(最常用)
② html-webpack-plugin
webpack 中的 HTML 插件(类似于一个模板引擎插件)
可以通过此插件自定制 index.html 页面的内容
安装之后我们还不能直接访问,因为打包生成的文件存放到了内存中
生成到内存中的文件该如何访问?
webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。
可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件
例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件
所以我们只有把index页面放到根目录它才能和bundle.js联合使用,才能实现实时更新
怎么实现呢?拷贝过去一份不就行了!我们用下面的方法来实现
html-webpack-plugin
html-webpack-plugin 是 webpack 中的 HTML 插件,可以通过此插件自定制 index.html 页面的内容。
需求:通过 html-webpack-plugin 插件,将 src 目录下的 index.html 首页,复制到项目根目录中一份!
安装
npm install [email protected] -D
配置
const path = require('path')//导入node.js中专门操作路径的模块
const HtmlPlugin = require('html-webpack-plugin')
// 创建html插件的实例对象
const htmlPlugin = new HtmlPlugin({
template: './src/index.html',//源文件存放路径
filename: './index.html'//生成的文件存放路径
})
module.exports = {
mode: 'development',
plugins: [htmlPlugin]//通过plugins节点,使htmlPlugin插件生效
};
总结
- 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中
- HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件
devServer 节点
在 webpack.config.js 配置文件中,可以通过 devServer 节点对 webpack-dev-server 插件进行更多的配置, 示例代码如下:
module.exports = {
mode: 'development',
plugins: [htmlPlugin],//通过plugins节点,使htmlPlugin插件生效
devServer: {
open: true,
// host: '',
port: 80
}
};
mac中要加sudo才能监听
webpack 中的 loader
loader 概述
在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块, webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错!
loader 加载器的作用:协助 webpack 打包处理特定的文件模块。比如:
- css-loader 可以打包处理 .css 相关的文件
- less-loader 可以打包处理 .less 相关的文件
- babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法
loader 的调用过程
打包处理 css 文件
- 运行 npm i [email protected] [email protected] -D 命令,安装处理 css 文件的 loader
- 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module: {
rules: [
{
test: /\.css$/, use: ['style-loader', 'css-loader']
}
]
}
打包处理 less 文件
less文件可以让CSS写的更简单,这里是让无序列表的间隙消失
html,
body,
ul{
margin: 0;
padding: 0;
li{
line-height: 30px;
padding-left: 20px;
font-size: 12px;
}
}
之后在index.js文件中引入改less文件即可
如果不设置less文件加载器,也会报错.所以运行 npm i [email protected] [email protected] -D 命令
在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:
module: {
rules: [
{test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']}
]
}
效果不放了因为太刺眼了
打包发布
通过前面的学习我们也能知道,你输入npm run dev 的时候它就会生成一个dist目录,这个dist目录其实就是打包好的,可以直接点index文件去访问的
现在项目要上线了,我们要减少dist文件的体积,其实就是运行npm run 生产环境的配置
在package.json配置一下:
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production"
},
运行程序:
npm run build
总结
webpack了解概念即可,后面使用脚手架这东西都是配置好的
- 运行程序npm run dev
- 发布程序npm run build
如果不是这个两个命令,那就去看它的package咋写的替换run 后面的单词即可
三、Vue基础入门
Vue简介
什么是Vue
构建用户界面的框架
- 构建用户界面
用 vue 往 html 页面中填充数据,非常的方便
- 框架
框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
要学习 vue,就是在学习 vue 框架中规定的用法!
vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
Vue 的两个特性
数据驱动视图:
- 数据的变化会驱动视图自动更新
- 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
双向数据绑定:
在网页中,form 表单负责采集数据,Ajax 负责提交数据。
- js 数据的变化,会被自动渲染到页面上
- 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中
注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)
MVVM的原理
总结
课件其实已经总结的很好了,MVVM最重要的就是VUE的实例VM(ViewModel)
- 它监听DOM的变化,当表单中的值发生变化时,VM会把数据同步到Model源中
- 它监听Model的变化,当Model发生变化时,它会把最新数据同步到View里
Vue的基本使用
Vue的基本使用步骤
- 导入vue.js文件
- 在页面声明一个将要被vue所控制的DOM区域
- 创建vm实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<div id="app">{{username}}</div>
<script src="./lib/vue-2.6.12.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
username: "zs",
},
});
</script>
</body>
</html>
基本代码与 MVVM 的对应关系
Vue指令
内容渲染指令
-
v-text
指令的缺点:会覆盖元素内部原有的内容! -
{{ }}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容! -
v-html
指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!
属性绑定指令
注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!
- 在 vue 中,可以使用
v-bind:
指令,为元素的属性动态绑定值; - 简写是英文的
:
- 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:
<div :title="'box' + index">这是一个 div</div>
事件绑定
v-on:
简写是@
- 语法格式为:
<button @click="add"></button>
methods: {
add() {
// 如果在方法中要修改 data 中的数据,可以通过 this 访问到
this.count += 1
}
}
$event
的应用场景:如果默认的事件对象 e 被覆盖了,则可以手动传递一个 $event。例如:
<button @click="add(3, $event)"></button>
methods: {
add(n, e) {
// 如果在方法中要修改 data 中的数据,可以通过 this 访问到
this.count += 1
}
}
- 事件修饰符:
.prevent
<a @click.prevent="xxx">链接</a>
.stop
<button @click.stop="xxx">按钮</button>
v-model 指令
- input 输入框
- type=“radio”
- type=“checkbox”
- type=“xxxx”
- textarea
- select
条件渲染指令
v-show
的原理是:动态为元素添加或移除display: none
样式,来实现元素的显示和隐藏
- 如果要频繁的切换元素的显示状态,用 v-show 性能会更好
v-if
的原理是:每次动态创建或移除元素,实现元素的显示和隐藏
- 如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好
在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!
v-if 指令在使用的时候,有两种方式:
- 直接给定一个布尔值 true 或 false
<p v-if="true">被 v-if 控制的元素</p>
- 给 v-if 提供一个判断条件,根据判断的结果是 true 或 false,来控制元素的显示和隐藏
<p v-if="type === 'A'">良好</p>
过滤器
Vue3已经放弃,Vue2可以用
怎么理解过滤器?就跟linux里的grep命令一样,就是筛选,它定义在filters里面
我们来看看代码示例:
- 要定义到 filters 节点下,本质是一个函数
- 在过滤器函数中,一定要有 return 值
- 在过滤器的形参中,可以获取到“管道符”前面待处理的那个值
- 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>message的值是{{message | capi}}</p>
</div>
<script src="/lib/vue-2.6.12.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 'hello vue.js'
},
filters:{
//过滤器函数形参中的val,永远都是"管道符"前面的那个值
capi(val){
const first = val.charAt(0).toUpperCase()
const other = val.slice(1)
return first + other
}
}
})
</script>
</body>
</html>
全局过滤器
全局过滤器 - 独立于每个 vm 实例之外
Vue. filter()方法接收两个参数:
第1 个参数,是全局过滤器的”名字”
第2 个参数,是全局过滤器的”处理函数”
Vue.filter('capi',(val) =>{
return val.charAt(0).toUpperCase() + val.slice(1) + '~~~~~~~~~~~~~'
})
连续调用多个过滤器
把 message 的值,交给 filterA 进行处理
把 filterA 处理的结果,再交给 filterB 进行处理
最终把 filterB 处理的结果,作为最终的值這染到页面上
{{ message 1 filterA I filterB }}
{{ message | filterA | filterB }}
过滤器传参
过滤器的本质是 JavaScript 函数,因此可以接收参数,格式如下:
示例代码如下:
入门实例
实现增删查功能
实现思路:
- 增加功能
首先要收集品牌名称,然后阻止表单的默认提交行为,改为调用自定义方法add
在add中定义要添加的对象,name为收集到的brand,状态为status设置为默认值即可,创建时间也设置一个默认值即可
id的话想要保证自增,所以可以自定义一个id,然后每添加一个对象id+1
对象创建好之后,直接push进list列表即可
- 删除功能
从list列表中过滤对象,通过传入的对象id即可过滤
- 查询功能
通过v-for去遍历data区域的list数组
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>品牌列表案例</title>
<link rel="stylesheet" href="./lib/bootstrap.css">
<link rel="stylesheet" href="./css/brandlist.css">
</head>
<body>
<div id="app">
<!-- 卡片区域 -->
<div class="card">
<div class="card-header">
添加品牌
</div>
<div class="card-body">
<!-- 添加品牌的表单区域 -->
<!-- 表单有默认的提交行为,通过@submit.prevent可以阻止它的默认行为-->
<form @submit.prevent="add">
<div class="form-row align-items-center">
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">品牌名称</div>
</div>
<input type="text" class="form-control" placeholder="请输入品牌名称" v-model.trim="brand">
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2">添加</button>
</div>
</div>
</form>
</div>
</div>
<!-- 表格区域 -->
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">品牌名称</th>
<th scope="col">状态</th>
<th scope="col">创建时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in list" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" :id="'cd' + item.id" v-model="item.status">
<label class="custom-control-label" :for="'cd' + item.id" v-if="item.status">已启用</label>
<label class="custom-control-label" :for="'cd' + item.id" v-else>已禁用</label>
</div>
</td>
<td>{{item.time}}</td>
<td>
<a href="javascript:;" @click="remove(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
//品牌的列表数据
brand: "",
list: [
{id: 1, name: '宝马', status: true, time: new Date()},
{id: 2, name: '奔驰', status: false, time: new Date()},
{id: 3, name: '奥迪', status: true, time: new Date()},
],
nextId: 4,
},
methods: {
remove(id) {
// console.log(id)
this.list = this.list.filter(item => item.id != id)
},
add() {
// console.log(this.brand)
// 如果判断到brand的值为空字符串,则return出去
if (this.brand === '') return alert("必须填写品牌名称")
// 如果没有return出去,应该去执行添加的逻辑
// 先把要添加的品牌对象,整理出来
const obj = {
id: this.nextId,
name: this.brand,
status: true,
time: new Date()
}
this.list.push(obj)
this.brand = ''
this.nextId += 1
}
}
})
</script>
</body>
</html>
四、Vue基础进阶
watch 侦听器
什么是侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作
侦听器的格式
- 方法格式的侦听器
- 缺点1:无法在刚进入页面的时候,自动触发!!!
- 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
- 对象格式的侦听器
- 好处1:可以通过 immediate 选项,让侦听器自动触发!!!
- 好处2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!
侦听器测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="username">
</div>
<script src="/lib/vue-2.6.12.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
username: '',
},
watch: {
username(newOlder, oldValue) {
console.log('监听到了username发生了变化', newOlder, oldValue)
},
}
})
</script>
</body>
</html>
计算属性
是什么?
计算属性指的是通过一系列运算之后,最终得到一个属性值。
这个动态计算出来的属性值可以被模板结构或 methods 方法使用。示例代码如下:
特点
- 定义的时候,要被定义为“方法”
- 在使用计算属性的时候,当普通的属性使用即可
好处
- 实现了代码的复用
- 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!
axios
axios(发音:艾克C奥斯) 是一个专注于网络请求的库!
它封装了Ajax(通过 Ajax 可以异步从服务器请求数据并将数据更新到网页中,整个过程不需要重载(刷新)整个网页,可以将网页的内容更快的呈现给用户。)
axios 的基本使用
发起 GET 请求
axios({
// 请求方式
method: 'GET',
// 请求的地址
url: 'http://www.liulongbin.top:3006/api/getbooks',
// URL 中的查询参数
params: {
id: 1
},
//请求体参数
data:{}
}).then(function (result) {
console.log(result)
})
简写:
const resu = axios.get('https://www.escook.cn/api/cart')
resu.then(function (books) {
console.log(books)
})
axios
返回的是一个Promise
对象,在Promise对象中并不是服务器返回的结果,而是axios对服务器返回的结果又进行了一次封装,如下:
简化如下:
发起 POST 请求
document.querySelector('#btnPost').addEventListener('click', async function () {
// 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
// await 只能用在被 async “修饰”的方法中
const { data: res } = await axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: {
name: 'zs',
age: 20
}
})
console.log(res)
})
使用解构赋值
基于axios
vue-cli
什么是单页面应用程序?
SPA(Single Page Application)
指的就是一个Web网站中只有唯一的一个HTML页面,所有的功能与交互都是在这唯一的一个页面完成
一般Vue项目打包之后会生成一个dist文件,这个dist文件就是下面这个样式的
什么是vue-cli?
快速生成一个vue项目的开发环境,程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。
安装和使用
- 在终端下运行如下的命令,创建指定名称的项目:
vue cerate 项目的名称
- vue 项目中 src 目录的构成:
assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
App.vue 是项目的根组件。
vue项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过main.js
把App.vue
渲染到 index.html
的指定区域中。
- App.vue 用来编写待渲染的模板结构
- index.html 中需要预留一个 el 区域
- main.js 把 App.vue 渲染到了 index.html 所预留的区域中
渲染之后,会代替index页面原有的代码
vue组件
组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件
,从而方便项目的开发和维护。
vue 中的组件化开发
vue 是一个支持组件化开发的前端框架。 vue 中规定:组件的后缀名是 .vue
。之前接触到的 App.vue 文件本质上就是一个 vue 的组件
。
vue 组件的三个组成部分
每个.vue组件都由3部分构成,分别是:
template -> 组件的模版结构
Script -> 组件的JavaScript行为
Style -> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分
template
vue 规定:每个组件对应的模板结构,需要定义到<template>
节点中
<template>
<!--当前组件的DOM结构,需要定义到template标签的内部 -->
</template>
tips:
- template是vue提供的容器标签,只起到包裹性质的作用,他不会被渲染为真正的DOM元素
- template 中只能包含唯一的根节点
script
vue 规定:开发者可以在 <script>
节点中封装组件的 JavaScript 业务逻辑。
.vue 组件中的 data 必须是函数
style
实例
<template>
<div class="wahahaha">
<h3>这是用户自定义的vue组件--{{ username }}</h3>
</div>
</template>
<!--固定写法-->
<script>
export default {
data() {
return {
username: 'zs'
}
}
}
</script>
<style>
.wahahaha{
background-color: pink;
}
</style>
启用less语法以及唯一根节点
- 组件中只能有一个根节点,也就是说不能写多个一级div,只能在一级div里定义多个低级div
- less语法就是让css的编写更简单
<template>
<div>
<div id="wahahaha">
<h3>这是用户自定义的vue组件--{{ username }}</h3>
<button @click="changeName">点我改变名字</button>
</div>
<div>123</div>
</div>
</template>
<!--固定写法-->
<script>
export default {
data() {
return {
username: 'zs'
}
},
methods: {
changeName() {
console.log(this),
this.username = 'Wahahah'
}
},
// 当前组件的监听器
watch: {},
// 当前组件的计算属性
computed: {}
}
</script>
<style lang="less">
#wahahaha {
background-color: pink;
h3 {
color: azure;
}
}
</style>
组件之间的父子关系
使用组件的三个步骤
实例
Hello.vue是我后来加
通过 components 注册的是私有子组件
例如:
- 在组件 A 的 components 节点下,注册了组件 F。
- 则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
注册全局组件
在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。
实例如下:
组件的props
props 是组件的**自定义属性
**,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
- props中的数据,可以直接在模版结构中使用
实例
left组件
- 使用v-bind可以给属性赋值
- 这里init自定义属性为字符串类型,通过v-bind代表你后面写的是js表达式,数字在js中就是Number型
<template>
<div class="left-container">
<h3>我是left</h3>
<MyCount :init="9"></MyCount>
</div>
</template>
<script>
export default {
name: "Left",
}
</script>
<style>
.left-container {
padding: 0 20px 20px;
background-color: #42b983;
min-height: 250px;
flex: 1;
}
</style>
count组件
<template>
<div>
<p>count的值是:{{ init }}</p>
<button @click="init += 1">Hi,点我给count+1</button>
</div>
</template>
<script>
export default {
props: ['init'],
name: "Count",
data() {
return {
count: 0,
}
}
}
</script>
<style scoped>
</style>
如图所示:
props 是只读的
要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!
所以props值的作用就是做一个初始化,最好是去操作data里的数据,所以这里可以通过this.init去把初始值赋给data里的count
this代表当前组件对象
<template>
<div>
<p>count的值是:{{ count }}</p>
<button @click="count += 1">Hi,点我给count+1</button>
</div>
</template>
<script>
export default {
props: ['init'],
name: "Count",
data() {
return {
count: this.init
}
}
}
</script>
<style scoped>
</style>
props 的 default 默认值
在声明自定义属性时,可以通过 default 来定义属性的默认值。示例代码如下:
props:{
自定义属性A:{},
自定义属性B:{},
自定义属性C:{},
}
export default {
props: {
init: {
default: 0,
}
},
name: "Count",
data() {
return {
count: this.init
}
}
}
props 的 type 值类型
在声明自定义属性时,可以通过 type 来定义属性的值类型。示例代码如下:
export default {
props: {
init: {
default: 0,
}
},
name: "Count",
data() {
return {
count: this.init,
type: Number
}
}
}
props 的 required 必填项
就是说,你自定义的这个属性是必填项,不填就会报错
组件之间的样式冲突问题
scoped
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
- 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:
/deep/ 样式穿透
什么时候会用到
如果代码引入了第三方组件库,想修改它的样式不能去直接修改它的源码,找到要修改的组件位置通过/deep/做样式穿透
五、生命周期&数据共享
生命周期 & 生命周期函数
生命周期
(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段
。
生命周期函数
:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
组件生命周期函数的分类
生命周期详解
简洁版本
回调函数是你留个处理方法给事件,事件发生了以后会自动执行你留下调处理方法
钩子函数是好比找了个代理,监视事件是否发生,如果发生了这个代理就执行你的事件处理方法;在这个过程中,代理就是钩子函数
在某种意义上,回调函数做的处理过程跟钩子函数中要调用调方法一样
但是有一点: 钩子函数一般是由事件发生者提供的。直白了说,它留下一个钩子,这个钩子的作用就是钩住你的回调方法
==钩子函数是在一个事件触发的时候,在系统级捕获到了他,然后做一些操作。==一段用以处理系统消息的程序,用以处理系统消息的程序,是说钩子函数是用于处理系统消息的
详细版本
最重要的三个阶段
- created
组件的 porps/data/methods已创建好,都处于可用的状态。
但是组件的模板结构尚未生成! - mounted
已经把内存中的 HTML 结构,成功的渲染到了浏览器之中。此时浏览器中己然包含了当前组件的 DOM 结构。
- update
组件的最新数据,可以操作最新的DOM
结构
组件之间的关系
在项目开发中,组件之间的最常见的关系分为如下两种:
① 父子关系
② 兄弟关系
父子组件之间的数据共享
父子组件之间的数据共享又分为:
① 父 -> 子共享数据
② 子 -> 父共享数据
父组件向子组件共享数据
父组件向子组件共享数据需要使用自定义属性
如下:
父组件
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<div class="box">
<Left></Left>
<Right :username=userinfo :passwd=passwd></Right>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import Left from "@/components/Left";
import Right from "@/components/Right";
export default {
name: 'App',
components: {
HelloWorld,
Left,
Right
},
data() {
return {
userinfo: {name: 'zs', age: 12},
passwd: 123
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.box {
display: flex;
}
</style>
子组件
<template>
<div class="rightContainer">
<h2>
我是right
</h2>
<h2>您的姓名是:{{username}}</h2>
<h2>您的密码是:{{passwd}}</h2>
</div>
</template>
<script>
export default {
name: "Right",
props: ['username','passwd']
}
</script>
<style scoped>
.rightContainer {
background-color: aquamarine;
flex: 1;
height: 300px;
}
</style>
子组件向父组件共享数据
子组件向父组件共享数据使用自定义事件。
如下:
兄弟组件之间的数据共享
在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus
EventBus 的使用步骤
- 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
- 在数据发送方,调用 bus.$emit(‘事件名称’, 要发送的数据) 方法触发自定义事件
- 在数据接收方,调用 bus.$on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件
ref 引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
什么是 ref 引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs
对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,
组件的 $refs 指向一个空对象。
使用 ref 引用 DOM 元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
使用 ref 引用组件实例
控制文本框和按钮的按需切换
让文本框自动获得焦点
当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。示例代码如下:
this.$nextTick(cb) 方法
组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
数组中的方法
some循环
- 简单来说some循环就是遇到终止条件可以停止继续寻找
- foreach即使遇到终止条件也会继续寻找
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = ['詹姆斯', '科比', '2届MVP得主库里', '1.5']
arr.forEach((item, index) => {
console.log('ok')
if (item === '2届MVP得主库里') {
console.log(index)
}
})
console.log('arr.some使用效果如下:')
arr.some((item, index) => {
console.log('ok')
if (item === '2届MVP得主库里') {
console.log(index)
return true
}
})
</script>
</body>
</html>
foreach循环
foreach就是循环每一项,some是循环到满足条件就停下来
every循环
判断数组中的数据是否被全选了使用every循环
reduce的基本用法
不用reduce计算总和
用reduce计算总和
reduce的简化写法
购物车案例
导入,注册,使用组件
这里的组件是写好的,我们直接导入,注册,使用
Tips:
- 导入的组件名称一般都给它设置为首字母大写.这样和普通标签能有一个区分
基于axios请求列表数据
axios是对ajax的封装,向后端发送请求就用它
这里是先定义一个封装请求列表数据的方法,然后在vue的created的生命周期阶段调用
created() {
this.initCartList()
},
methods: {
//封装请求列表数据的方法
async initCartList() {
//调用axios的get方法,请求列表数据
const {data: res} = await axios.get('https://www.escook.cn/api/cart')
// 只要请求回来的数据,在页面渲染期间要用到,则必须转存到data里
this.list = res.list
}
},
这里的await是用来解构赋值
的它一般和async联合使用
那么
解构赋值
是什么呢?
就是前段通过axios向后端发送请求之后,后端返回的数据会先经过axios进行一次封装,不能直接拿到真实数据
如下:
所以为了直接拿到真实数据,我们需要解构,从回复的数据中取出真实数据
{{data:res}}这是啥意思呢?
axios
返回的是一个Promise
对象,在Promise对象中并不是服务器返回的结果,而是axios对服务器返回的结果又进行了一次封装,如下:
通过{{data:res}}
意思就是取出Promise对象中的data区域数据,并把名字重命名为res
渲染Goods组件
1.商品数据其实就是一个个Goods组件
导入,注册,使用组件
<Goods></Goods>
2.给组件赋值,这里通过v-for来实现
首先看看请求到的结果
list数组里不就是如下一个个的对象吗,然后赋值的时候通过对象.属性即可取得对应的值
这里的:key,:id,:title等都是为子组件的自定义属性,父组件向子组件传递数据就是通过自定义属性来进行传递的
<!-- 这里为啥一直要写v-bind呢因为在等号后面我们写的是js表达式,如果不写v-bind则相当于我们传给它了一个字符串!-->
<Goods
v-for="item in list"
:key="item.id"
:id="item.id"
:title="item.goods_name"
:img="item.goods_img"
:price="item.goods_price"
:state="item.goods_state "
>
</Goods>
3.修改商品的勾选状态
修改商品的勾选状态为子组件向父组件
共享数据
通过自定义事件来实现
3.1 在子组件中为复选框绑定自定义事件
复选框状态每发生一次改变就会触发自定义事件
<input type="checkbox" class="custom-control-input" :id="'cb' + id" :checked="state"
@change="stateChange"/>
3.2 在子组件中自定义事件如下
自定义时间名字为state-change
,传递给父组件的数据为id和value
- id表示当前商品
- value表示最新的勾选状态
methods: {
stateChange(e) {
// console.log(e)
console.log(e.target.checked)
const newState = e.target.checked
// 触发自定义事件
this.$emit('state-change', {id: this.id, value: newState})
}
}
3.3 父组件绑定自定义事件
通过参数e来接受子组件传来的数据
通过some循环找到改变状态的子组件判断的条件是id,找到之后给组件的状态赋值为子组件传来的真实数据
<Goods @state-change="getNewState"></Goods>
methods: {
getNewState(e) {
console.log(e)
this.list.some(item => {
if (item.id === e.id) {
item.goods_state = e.value
}
})
}
},
渲染Footer组件
引入footer组件
footer组件做二件事:
- 全选
- 计算总价
全选为子组件向父组件
共享数据 => 自定义事件
计算总价为父组件向子组件
共享数据 => 自定义属性
<Footer
:is-full="fullState"
@fullChange="getFullState"
:total-prices="amt"
></Footer>
全选功能的实现
//子组件
<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" @change="fullChange"/>
methods: {
fullChange(e) {
// console.log(e.target.checked)
// 触发自定义事件
this.$emit('fullChange', e.target.checked)
}
}
// 动态计算全选的状态是true还是false
fullState() {
return this.list.every(item => item.goods_state)
},
//父组件
// 动态计算全选的状态是true还是false
fullState() {
return this.list.every(item => item.goods_state)
},
计算总价功能的实现
子组件
props: {
isFull: {
type: Boolean,
default: true
},
totalPrices: {
type: Number,
default: 0
}
},
<div>
<span>合计:</span>
<span class="total-price">¥{{ totalPrices.toFixed(2) }}</span>
</div>
<!-- 结算按钮 -->
<button type="button" class="btn btn-primary btn-settle">结算({{ totalPrices.toFixed() }})</button>
父组件
computed: {
//总价格
amt() {
// 先filter过滤,在reduce累加
return this.list
.filter(item => item.goods_state)
//只有一行的箭头函数,可以省略return和{}花括号
.reduce((total, item) =>
(total += item.goods_price * item.goods_count),0)
}
},
购买数量实现
和计算总价实现一个思路
- 从集合中过滤出选中的商品
- 取得选中商品的数量
取得数量之后,还需要和子组件共享数据,所以就用自定义属性来传递数据
computed: {
//总数量
totalQuantity() {
return this.list
.filter(item => item.goods_state)
.reduce((total, item) => (total += item.goods_count), 0)
}
子组件
<!-- 结算按钮 -->
<button type="button" class="btn btn-primary btn-settle">结算({{ totalNum}})</button>
props: {
totalNum:{
type:Number,
default:0
}
},
六、动态组件&插槽&自定义指令&axios
动态组件
什么是动态组件
动态组件指的是动态切换组件的显示与隐藏
如何实现动态组件渲染
vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。示例代码如下:
使用 keep-alive 保持状态
默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 组件保持动态组 件的状态。示例代码如下:
keep-alive 对应的生命周期函数
当组件被缓存
时,会自动触发组件的 deactivated
生命周期函数。
当组件被激活
时,会自动触发组件的 activated
生命周期函数。
keep-alive 的 include 属性
include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:
插槽
插槽(Slot)
是 vue 为组件的封装者
提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的 部分定义为插槽。
可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
体验插槽的基础用法
在封装组件时,可以通过 元素定义插槽,从而为用户预留内容占位符。示例代码如下:
vue 官方规定:每一个 slot 插槽,都要有一个 name 名称–;
如果省略了 slot 的 name 属性,则有一个默认名称叫做 default
v-slot只能放在组件上和template上,放在其他上面会报错
默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default 的插槽之中
为具名插槽提供内容
在向具名插槽提供内容的时候,我们可以在一个 <template>(包裹作用)
元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。示例代码如下:
具名插槽的简写形式
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。
例如 v-slot:header
可以被重写为 #header:
作用域插槽
在封装组件的过程中,可以为预留的插槽绑定 props 数据,这种带有 props 数据的 叫做作用 域插槽
。
示例代码如下:
使用作用域插槽
可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。示例代码如下:
实例
解构插槽 Prop
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。
示例代码如下:
实例
自定义指令
什么是自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。
自定义指令的分类
vue 中的自定义指令分为两类,分别是:
- 私有自定义指令
- 全局自定义指令
私有自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:
使用自定义指令
在使用自定义指令时,需要加上 v- 前缀。示例代码如下
为自定义指令动态绑定参数值
为自定义指令动态绑定参数值
在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值:
通过 binding 获取指令的参数值
在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:
update 函数
bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函 数会在每次 DOM 更新时被调用。示例代码如下:
函数简写
如果 insert 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:
全局自定义指令
全局共享的自定义指令需要通过“Vue.directive()”进行声明
示例代码如下:
把axios挂载到vue原型上
缺点是无法对api接口实现复用!
在其他组件上直接通过this.http.post来发请求就行了
七、路由
是什么?
一组对应关系,在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成
前端路由的工作方式
实现简易的前端路由
步骤1:通过
<component>
标签,结合comName
动态渲染组件。示例代码如下:
步骤2:在 App.vue 组件中,为 链接添加对应的 hash 值:
步骤3:在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:
vue-router 的基本用法
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目 中组件的切换。
vue-router 的官方文档地址:https://router.vuejs.org/zh/
vue-router安装
在 vue2 的项目中,安装 vue-router 的命令如下:
npm i vue - [email protected] -S
创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:
导入并挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:
声明路由链接和占位符
声明路由的匹配规则
路由重定向
路由重定向
指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
嵌套路由
声明子路由链接和子路由占位符
通过 children 属性声明子路由规则
动态路由匹配
动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:
$route.params 参数对象
使用 props 接收路由参数
声明式导航 & 编程式导航
vue-router 中的编程式导航 API
$router.push
$router.replace
$router.go
$router.go 的简化用法
导航守卫
全局前置守卫
什么是回调函数?
回调函数是一个参数,将这个函数作为参数传到另一个函数里面
,当那个函数执行完之后,在执行传进去这个函数,这个过程就叫回调!白话文:主函数的事先干完,回头在调用传进来的那个函数.
守卫方法的 3 个形参
next 函数的 3 种调用方式
控制后台主页的访问权限
八、补充知识
URL种的#是什么意思?
#代表网页中的一个位置。
比如,http://www.example.com/index.html#print就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。
在VUE中,路由的实现方式有两种,其中一种就是通过#标识符进行页面内导航从而实现路由切换。
HTTP请求不包括#
#是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#。
比如,访问下面的网址,http://www.example.com/index.html#print,浏览器实际发出的请求是这样的:
GET /index.html HTTP/1.1
Host: www.example.com
#后的字符
在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端。
比如,下面URL的原意是指定一个颜色值:http://www.example.com/?color=#fff,但是,浏览器实际发出的请求是:
GET /?color= HTTP/1.1
Host: www.example.com
改变#不触发网页重载
单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
比如,从http://www.example.com/index.html#location1改成http://www.example.com/index.html#location2,浏览器不会重新向服务器请求index.html。
说明同第一个,例如单页面应用SPA一样,路由的切换,不会重新加载页面,只是切换位置,或者切换组件;第二种应用就是在单个页面里面通过name和id切换当前显示的位置。
改变#会改变浏览器的访问历史
每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。这对于ajax应用程序特别有用,可以用不同的#值,表示不同的访问状态,然后向用户给出可以访问某个状态的链接。值得注意的是,上述规则对IE 6和IE 7不成立,它们不会因为#的改变而增加历史记录。
说明:通过#的切换,是算在浏览器的访问历史中的,前进和后退都是生效的。
?说明
连接作用
通过?来带参数,连接域名和参数,经常会用到。
http://www.xxx.com/Show.asp?id=77&nameid=2905210001&page=1
清除缓存
http://www.xxxxx.com/index.html
http://www.xxxxx.com/index.html?test123123
两个url打开的页面一样,但是后面这个有问号,说明不调用缓存的内容,而认为是一个新地址,重新读取。
因为在做http请求的时候,如果浏览器检测到你的地址完全没变,会从缓存里读取先前请求过的数据,不再发送请求。有些时候是页面资源的加载,有些时候是API的get请求,都有可能。加上这个,会让浏览器认为这是一个新的地址,从而保证重新获取资源。
&说明
不同参数的间隔符
http://www.xxx.com/Show.asp?id=77&nameid=2905210001&page=1