目录(点击下面选项跳转知识点)
- 知识概览
- 一、组件化的实现和使用步骤(掌握)
- 二、组件化的基本使用过程(掌握)
- 三、全局组件和局部组件(掌握)
- 四、父组件和子组件的区分(掌握)
- 五、注册组件的语法糖写法(掌握)
- 六、组件模板抽离(分离)的写法(掌握)
- 七、为什么组件中的data必须是函数(理解)
- 八、父子组件通信-父传子(掌握)
- 九、父子组件通信-子级向父级传递(掌握)
- 十、父子组件通信-结合双向绑定案例(掌握)
- 十一、结合双向绑定案例-画图分析(掌握)
- 十二、结合双向绑定案例-watch实现
- 十三、父组件访问子组件-children-refs(掌握)
- 十四、子组件访问父组件-parent-root(理解)
- 组件高级化开端:十五、slot-插槽的基本使用(掌握)
- 十六、slot-具名插槽的作用(掌握)
- 十七、编译作用域的概念(理解)
- 十八、作用域插槽的使用(掌握)
- 十九、前端代码复杂带来的问题(理解)
- 模块化开发的开端:二十、前端模块化雏形和CommonJS(理解)
- 二十一、ES模块化的导入和导出(掌握)
- 二十二、webpack的介绍和安装(理解)
- 二十三、webpack的基本使用过程(掌握)
- 二十四、webpack.config.js配置和package.json配置(掌握)
- 二十五、webpack中使用css文件的配置(掌握)
- 二十六、webpack-less文件的处理(掌握)
- 二十七、webpack-图片文件的处理(掌握)
- 二十八、webpack-ES6转ES5的babel(掌握)
- VueJs模块化组件的开端:二十九、webpack-使用Vue的配置过程(掌握)
- 三十、创建Vue时template和el关系(掌握)
- 三十一、Vue的终极使用方案(重点掌握)【重点在vue文件开发】
- 三十二、webpack-横幅Plugin的使用(掌握)
- 三十三、webpack-HtmlWebpackPlugin的使用(掌握)
- 三十四、webpack-UglifyjsWebpackPlugin的使用(掌握)
- 三十五、webpack-dev-server搭建本地服务器(掌握)
- 三十六、webpack-配置文件的分离(掌握)
知识概览
一、组件化的实现和使用步骤(掌握)
什么是组件化?
- 人在面对复杂问题的处理方式:
- 任何一个人处理信息的逻辑能力是有限的
- 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容
- 但是,我们人有一种天生的能力,就是将问题拆解
- 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
- 组件化也是类似的思想:
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展
- 但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
Vue的组件思想
- 组件化是Vue.js中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何的应用都会被抽象成一颗组件树
组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它
- 尽可能的将页面拆分成一个个小的、可复用的组件
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
注册组件的基本步骤
- 组件的使用分成三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
二、组件化的基本使用过程(掌握)
步骤:
- 创建组件构造器: 调用 Vue.extend() 方法创建组件构造器
- 注册组件:调用 Vue.component() 方法注册组件
- 使用组件:在 Vue 实例的作用范围内使用组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<!--3.使用组件-->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//ES6用``可以代替字符串并不需要+拼接来换行写
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
<p>我是内容,呵呵呵呵</p>
</div>`
});
//注册组件
Vue.component('cpn',cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
});
</script>
</body>
</html>
注册组件步骤解析:
Vue.extend():
- 调用 Vue.extend() 创建的是一个组件构造器
- 通常在创建组件构造器时,传入 template 代表我们自定义组件的模板
- 该模板就是在使用到组件的地方,要显示的HTML代码
- 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲
- 的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
Vue.component():
- 调用 Vue.component() 是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
- 所以需要传递两个参数:1、注册组件的标签名。 2、组件构造器
组件必须挂载在某个Vue实例下,否则它不会生效
我们来看下面我使用了三次 ,而第三次其实并没有生效
三、全局组件和局部组件(掌握)
当我们调用Vue.component()注册组件时,组件的注册是全局的
- 这意味着该组件可以在任意Vue示例下使用
- 但是如果我们注册的组件是挂载在某个实例中,那么就是一个局部组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app2">
<cpn></cpn>
</div>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
<p>我是内容,呵呵呵呵</p>
</div>`
});
// 2.注册组件(全局组件,意味着可以在多个Vue的实例下面使用)
/*Vue.component('cpn',cpnC)*/
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components:{
// cpn使用组件时的标签名,局部注册!!!
cpn: cpnC
}
});
const app2=new Vue({
el:'#app2'
})
</script>
</body>
</html>
通过Vue.component()方法注册的组件是全局组件,通过 components 注册的是私有子组件
四、父组件和子组件的区分(掌握)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)<只能在父组件中使用,想要外部使用,在外部也要进行注册>
const cpnC1=Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
`
})
// 2.创建第一个组件构造器(父组件)
const cpnC2=Vue.extend({
template:`
<div>
<h2>我是标题2</h2>
<p>我是内容,呵呵呵</p>
<cpn1></cpn1>
</div>
`,
components:{
cpn1:cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
});
</script>
</body>
</html>
父子组件的错误用法: 以子组件的形式在 Vue 实例中使用
- 因为当子组件注册到父组件的 components 时,Vue 会编译好父组件的模块
- 该模板的内容已经决定了父组件将要渲染的 HTML(相当于父组件中已经有了子组件的内容了)
- <cpn1></cpn1> 是只能在父组件中被识别的
- 类似这种用法,<cpn1></cpn1> 是会被浏览器忽略的。
<div id="app">
<cpn2></cpn2>
<!--父子组件的错误用法:以子组件的形式在 Vue 实例中使用-->
<!--<cpn1></cpn1>-->
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵呵</p>
<cpn1></cpn1>
</div>
`,
components: {
// 在父组件中注册子组件,这样就可以在父组件里面使用子组件
// 例如上面的<cpn1></cpn1>
cpn1: cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
// cpn1子组件在cpn2父组件中注册,父组件cpn2在Vue实例里面注册
cpn2: cpnC2
}
})
</script>
五、注册组件的语法糖写法(掌握)
Vue 为了简化这个过程,提供了注册的语法糖
主要是省去了调用 Vue.extend() 的步骤,而是可以直接使用一个对象来代替
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.全局组件注册的语法糖
// 不推荐的写法:const cpn = Vue.extend()
//2.注册组件(全局组件)
Vue.component('cpn',{
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
`
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components:{
'cpn2':{
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,哈哈哈</p>
</div>`
}
}
});
</script>
</body>
</html>
六、组件模板抽离(分离)的写法(掌握)
通过语法糖简化了 Vue 组件的注册过程,另外还有一个地方的写法比较麻烦,就是 template 模块写法
- 如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰
- Vue 提供了两种方案来定义HTML模板内容
- 使用 < script > 标签
- 使用 < template > 标签
使用 script 标签:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--1.script标签, 注意:类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
</html>
重点:使用template标签🔥
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--2.template标签-->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
</body>
</html>
七、为什么组件中的data必须是函数(理解)
组件可以访问Vue实例数据吗?
结论:组件不能直接访问Vue实例中的 data
注:即使访问放也不建议写在Vue实例中
组件是一个单独功能模块的封装:
- 这个模块有属于自己的 HTML 模板,也应该有属于自己的数据 data
组件自己的数据存放在哪呢?
- 组件对象也有一个 data 属性(也可以有 methods 属性)
- 只是这个 data 属性必须是一个函数
- 而且这个函数返回一个对象,对象内部保存着数据
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--2.template标签-->
<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: 'abc'
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
// title: '我是标题'
}
})
</script>
为什么 data 在组件中必须是一个函数呢?
- 首先,如果不算是一个函数,Vue 直接就会报错
- 其次,原因是在于 Vue 让每个组件对象都返回一个新的对象,因为如果是同一个对象,组件在多次使用后会相互影响(为了达到相互隔离的效果)
八、父子组件通信-父传子(掌握)
在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的
但是,在开发中,往往一些数据确实需要从上层传递到下层
- 比如在一个页面中,我们从服务器请求到了很多的数据
- 其中的一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示
- 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件) 将数据传递给小组件(子组件)
如何进行父子组件间的通信呢?
- 通过 props 向子组件传递数据
- 通过事件向父组件发送消息
props 基本用法
在组件中,使用选项 props 来声明需要从父级接收到的数据(properties)
props 的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
传数组:
<div id="app">
<!-- 4.使用组件(v-bind动态绑定) -->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<!-- 2.组件构造器的模板分离写法 -->
<template id="cpn">
<div>
<h1>{{cmovies}}}</h1>
<h1>{{cmessage}}</h1>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器(子组件)
const cpn = {
template: '#cpn',
props: ['cmovies', 'cmessage'] //父传子,props
}
// 3.注册组件(将子组件在父组件里面注册)
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
//对象字面量增强写法的属性增强写法
cpn
}
})
</script>
注意:我们在使用组件时,需要用v-bind 动态绑定数据。
传对象:
- 在前面,我们的 props 选项是使用一个数组
- 除了数组之外,我们也可以使用对象,当需要对props 进行类型等验证时,就需要对象写法了
验证支持的数据类型有:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
①类型限制
我们可以在 props 里面限制父组件给子组件传递的数据类型
<!--父组件模板-->
<div id="app">
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<h1>{{cmovies}}}</h1>
<h1>{{cmessage}}</h1>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
props: {
// 1.类型限制
cmovies: Array, // 限制父组件传的是数组类型
cmessage: String, // 限制父组件传的是字符串类型
}
}
// root组件,我们当作父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
//对象字面量增强写法的属性增强写法
cpn
}
})
</script>
②默认值和必传值
- type : 限制的类型
- default : 如果没有传值,给一个默认值
- 注意:类型是对象或者数组时, 默认值必须是一个函数
- required : 必须的,即意味着这个值是必须要传递的,不传就报错
<div id="app">
<!--在这里传值-->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<h1>{{cmovies}}}</h1>
<h1>{{cmessage}}</h1>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
props: {
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String, // 类型限制为 String
default: 'aaaaaaaa', // 如果没有传值,则给一个默认值
required: true // required 必须的,即意味着这个值是必须要传递的,不传就报错
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
},
// root组件,我们当作父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
//对象字面量增强写法的属性增强写法
cpn
}
})
</script>
③自定义类型
当我们有自定义构造函数时,验证也支持自定义的类型
④props驼峰标识
当我们 props 里面的属性是驼峰写法的时,在传入值时需要进行 - 连接
<div id="app">
<!--目前不支持直接写cInfo,驼峰得加 `-` 连接-->
<cpn :c-info="info" :child-my-message="message" ></cpn>
</div>
<template id="cpn">
<div>
<h2>{{cInfo}}</h2>
<h2>{{childMyMessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
// 驼峰写法cInfo
cInfo: {
//类型是对象或者数组时, 默认值必须是一个函数
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
const app = new Vue({
el: '#app',
data: {
info: {
name: 'why',
age: 18,
height: 1.88
},
message: 'aaaaaa'
},
components: {
cpn
}
})
</script>
九、父子组件通信-子级向父级传递(掌握)
- props 用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件去
- 这个时候,我们需要使用自定义事件来完成
什么时候需要自定义事件呢?
- 当子组件需要向父组件传递数据时,就要用到自定义事件了
- 我们之前学习的 v-on 不仅仅可以用于监听 DOM 事件,也可以用于组件间的自定义事件
自定义事件的流程:
- 在子组件中,通过$emit() 来触发事件
- 在父组件中,通过v-on 来监听子组件事件
我们来看一个简单的例子:
-
我们之前做过一个两个按钮 +1 和 -1,点击后修改 counter
-
我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件
-
这样,我们就需要将子组件中的 counter,传给父组件的某个属性,比如total
<body>
<!--父组件模板-->
<div id="app">
<!-- 2.父组件里面接收子组件发出的自定义事件 值是一个方法 -->
<cpn @item-click="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// console.log(item);
// 1.发射事件: 自定义事件
// 第一个参数是自定义事件的名称,第二个参数是自定义事件的参数
this.$emit('item-click', item)//发射
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
},
methods: {
// 3.父组件里面定义方法处理
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
</body>
十、父子组件通信-结合双向绑定案例(掌握)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!--<input type="text" v-model="dnumber1">--><!--不建议绑定props里面的值-->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<!--<input type="text" v-model="dnumber2">-->
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1:1,
num2:0
},
methods:{
num1change(value){
this.num1=Number(value);
},
num2change(value){
this.num2=Number(value);
}
},
components:{
cpn:{
template:'#cpn',
props:{
number1:Number,
number2:Number,
},
data(){
return{
dnumber1:this.number1,
dnumber2:this.number2
}
},
methods:{
num1Input(event){
this.dnumber1=event.target.value;
this.$emit('num1change',this.dnumber1)
},
num2Input(event){
this.dnumber2=event.target.value;
this.$emit('num2change',this.dnumber2)
}
}
}
}
});
</script>
</body>
</html>
十一、结合双向绑定案例-画图分析(掌握)
十二、结合双向绑定案例-watch实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{dnumber1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>props:{{dnumber2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1:1,
num2:0
},
methods:{
num1change(value){
this.num1=value;
},
num2change(value){
this.num2=value;
}
},
components:{
cpn:{
template:'#cpn',
props:{
number1:Number,
number2:Number,
},
data(){
return{
dnumber1:this.number1,
dnumber2:this.number2
}
},
watch:{
dnumber1(newValue){
this.dnumber1=newValue
this.$emit('num1change',this.newValue)
},
dnumber2(newValue){
this.dnumber2=newValue
this.$emit('num2change',this.newValue)
}
}
}
}
});
</script>
</body>
</html>
十三、父组件访问子组件-children-refs(掌握)
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。
-
父组件访问子组件:使用 $children 或 $refs
-
子组件访问父组件:使用$parent
我们先来看下$children (它是一个复数)的访问
- this.$children 是一个数组类型,它包含所有子组件对象
$children 的缺陷:
- 通过 $children 访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值
- 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化
- 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用 $refs
$refs 的使用🔥
- $ refs 和 ref 指令通常是一起使用的
- 首先,我们通过 ref 给某一个子组件绑定一个特定的 ID
- 其次,通过 this.$refs.ID 就可以访问到该组件了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<!--父组件的模板-->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<!--子组件的模板-->
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
// 2.$refs => 对象类型, 默认是一个空的对象 必须在组件上加 ref='bbb'
/*console.log(this.$children)//$children不常使用因为不建议使用下标值取值
this.$children[0].showMessage();
this.$children[0].name;*/
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage(){
console.log('我是子组件的方法')
}
}
},
}
})
</script>
</body>
</html>
十四、子组件访问父组件-parent-root(理解)
如果我们想在子组件中直接访问父组件,可以通过 $parent
-
尽管在 Vue 开发中,我们允许通过 $parent 来访问父组件,但是在真实开发中尽量不要这要做
-
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了
-
如果我们将子组件放入另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题
$parent(了解即可)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<!--父组件的模板-->
<div id="app">
<cpn></cpn>
</div>
<!--子组件的模板-->
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是ccpn子组件</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data(){
return{
name:'我是cpn组件的name'
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
btnClick() {
// 1.访问父组件$parent
console.log(this.$parent)
// 2.访问父组件的name
console.log(this.$parent.name);
}
}
}
}
}
}
})
</script>
</body>
</html>
$root(也用的比较少)
组件化高级
十五、slot-插槽的基本使用(掌握)
为什么使用slot
slot翻译为插槽
- 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
- 插槽的目的是让我们原来的设备具备更多的扩展性
- 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等
组件的插槽
- 组件的插槽也是为了我们封装的组件更加具有扩展性
- 让使用者可以决定组件内部的一些内容到底展示什么
栗子:移动网站中的导航栏
- 移动开发中,几乎每个页面都有导航栏
- 导航栏我们必然会封装成一个插件,比如nav-bar组件
- 一旦有了这个组件,我们就可以在多个页面中复用了
如何封装这类组件呢?slot
如何封装这类组件呢?
- 它们也有很多区别,但是也有很多共性
- 如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装
- 但是,我们封装成一个,好像也不合理;有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字
如何封装合适呢?抽取共性,保留不同
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽
- 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容
- 是搜索框,还是文字,还是菜单。由调用者自己来决定
- 这就是我们为什么要学习组件中的插槽slot的原因
说明图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><button>我是按钮</button></cpn>
<cpn><span>我是span</span></cpn>
<cpn>
<cpn></cpn>
</cpn>
<cpn>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
</cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件,哈哈哈</p>
<slot><button>外界未指定则显示此默认</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components:{
cpn: {
template:'#cpn'
}
}
});
</script>
</body>
</html>
运行结果:
十六、slot-具名插槽的作用(掌握)
当子组件的功能复杂时,子组件的插槽可能并非是一个。
- 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
- 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?p这个时候,我们就需要给插槽起一个名字
如何给插槽起名字呢?
- 给插槽起一个名字,只要给 slot 元素一个 name 属性即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><span slot="center">中间标题</span><button slot="right">右边按钮</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
<!--<slot>哈哈哈</slot>外界没有指明名字则只会替换无名插槽-->
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components:{
cpn: {
template:'#cpn'
}
}
});
</script>
</body>
</html>
十七、编译作用域的概念(理解)
- 在真正学习插槽之前,我们需要先理解一个概念:编译作用域
- 官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
- 我们来考虑下面的代码是否最终是可以渲染出来的:
- < my-cpn v-show=“isShow”></ my-cpn >中,我们使用了isShow属性
- isShow属性包含在组件中,也包含在Vue实例中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn v-show="isShow"></cpn><!--父组件的作用域优先父组件属性-->
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容哈哈哈</p>
<button v-show="isShow">按钮</button><!--子组件的作用域优先子组件属性-->
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
},
components:{
cpn: {
template:'#cpn',
data(){
return{
isShow:false
}
}
}
}
});
</script>
</body>
</html>
答案:最终可以渲染出来,也就是使用的是 Vue 实例的属性
为什么呢?
- 官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在级作用域内编译
- 而我们在使用 < my-cpn v-show=“isShow”></ my-cpn > 的时候,整个组件的使用过程相当于在父组件中出现的
- 那么它的作用域就是父组件,使用的属性也是属于父组件的属性
- 因此 ,isShow 使用的是 Vue 实例中的属性,而不是子组件的属性
十八、作用域插槽的使用(掌握)
一句话总结:父组件替换插槽的标签,但是内容由子组件来提供
我们先提一个需求:
- 子组件包括一组数据,比如:PLanguages:[‘javascript’,‘python’,‘Swift’,‘Go’,‘C++’]
- 需要在多个界面进行展示
- 某些界面是以水平方向一一展示的
- 某些界面是以列表形式展示
- 某些界面直接展示一个数组
- 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
- 利用 slot 作用域插槽就可以了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<!--目的是获取子组件的pLanguages-->
<!--VUE2.5版本以下必须使用template标签,之后版本可以使用任意标签-->
<template v-slot="slot"><!--slot-scope="slot" VUE2.6版本之前使用,2.6之后用的是v-slot-->
<span v-for="item in slot.data">{{item}}-</span><!--难点在于理解slot.data-->
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}}***</span>
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join('***')}}</span><!--去掉最后面的****-->
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components:{
cpn:{
template:'#cpn',
data(){
return{
pLanguages:['javascript','python','Swift','Go','C++','java','C#']
}
}
}
}
});
</script>
</body>
</html>
十九、前端代码复杂带来的问题(理解)
模块化开发引入
JavaScript原始功能
-
在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。
- 那个时代的代码是怎么写的呢?直接将代码写在<script>标签中即可
-
随着ajax异步请求的出现,慢慢形成了前后端的分离
- 客户端需要完成的事情越来越多,代码量也是与日俱增
- 为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护
- 但是这种维护方式,依然不能避免一些灾难性的问题
-
比如全局变量同名问题
-
另外,这种代码的编写方式对js文件的依赖顺序几乎是强制性的
- 但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较痛苦的事情
- 而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生
匿名函数的解决方案
- 我们可以使用匿名函数来解决重名问题
二十、前端模块化雏形和CommonJS(理解)
ES6export指令
- export 指令用于导出变量,比如下面的代码
// info.js
export let name = 'why'
export let age = 18
export let height = 1.88
- 上面的代码还有另外一种写法
// info.js
let name = 'why'
let age = 18
let height = 1.88
export{name,age,height}
二十一、ES模块化的导入和导出(掌握)
导出函数或类
上面我们主要是输出变量,也可以输出函数或者输出类
export function test(content){
console.log(content);
}
export class Person {
constructor(name,age){
this.name = name;
this.age = age
}
run(){
console.log(this.name + '在奔跑');
}
}
上面的代码也可以写成这种形式
function test(content) {
console.log(content);
}
class Person {
constructor(name,age) {
this.name = name;
this.age = age;
}
run() {
console.log(this.name + '在奔跑')
}
}
export {test,Person}
export default
- 某些情况下,一个模块中包含某个功能,我们并不希望给这个功能命名,而是让导入者可以自己来命名
- 这个时候就可以使用 export default
// info.js
export default function() {
console.log('default function')
}
-
我们来到 main.js 中,这样使用就可以了
- 这里的 myFunc 是我自己命名的,你可以根据需要命名它对应的名字
import myFunc form './info.js'
myFunc()
-
另外,需要注意:
- export default 在同一个模块中,不允许同时存在多个
ES6 import的使用
我们使用 export 指令导出了模块对外提供的接口,下面我们就可以通过 import 命令来加载对应的这个模块了
- 首先,我们需要在 HTML 代码中引入两个j文件,并且类型需要设置为 module
<script src = "info.js" type = "module"></script>
<script src = "main.js" type = "module"></script>
import 指令用于导入模块中的内容,比如 main.js 的代码
import {name,age,height} from "./info.js"
console.log(name,age,height);
- 如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦
- p通过*可以导入模块中所有的export变量
- p但是通常情况下我们需要给*起一个别名,方便后续的使用
import * as info from './info.js'
console.log(info.name,info.age,info.height);
二十二、webpack的介绍和安装(理解)
内容概述
- 认识webpack
- webpack的安装
- webpack的起步
- webpack的配置
- loader的使用
- webpack中配置Vue
- plugin的使用
- 搭建本地服务器
认识webpack
- 在ES6之前,我们相要进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发
- 并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包
- 而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。
- 而且不仅仅是 JavaScript 文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用
- 这就是webpack模块化的概念
打包
- 理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了
- 就是将webpack的各种资源模块进行打包整合成一个或多个包Bundle
- 并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作
- 但是打包的操作似乎 grunt/gulp 也可以帮助我们完成,它们有什么不同呢?
和grunt/gulp的对比
grunt / gulp 的核心是 Task
- 我们可以配置一系列的 task,并且定义 task 要处理的事务(例如ES6,ts转化,图片压缩,scss转成css)
- 之后让 grunt / gulp 来依次执行这些 task,而且让整个流程自动化
- 所以 grunt / gulp 也被称为前端自动化任务管理工具
我们来看一个 gulp 的task
- 下面的 task 就是将 src 下面的所有 js 文件转成 ES5 的语法
- 并且最终输出到 dist 文件夹中
什么时候用 grunt / gulp 呢?
- 如果你都工程模块依赖非常简单,甚至是没有用到模块化的概念
- 只需要进行简单的合并、压缩,就使用 grunt / gulp 即可
- 但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack了
安装webpack
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
安装node.js
- 安装的是10.24.1版本
- 所有nodejs历史版本的链接: https://nodejs.org/dist/
- 找到10.24.1,里面是 nodejs V10.24.1所有类型安装文件
- .msi 使用这个不用配置环境变量
- .zip 解压即可用,但是需要配置环境变量
- 我这里下载 .msi 后缀文件
- 双击下载完成的 node-v10.24.1-x64.msi ,点击next
点击next
- npm package manage 表示 npm包管理器
- online documentation shortcuts 在线文档快捷方式
- Add to Path 添加 node 安装路径到环境变量
- 这就是.msi 不需要手动添加环境变量的原因,node已经帮我们配置好了
是否勾选工具去编译 native 模块,我们不勾选
然后等待安装完成!
测试
使用管理员方式打开dos窗口,输入 node -v
显示如图样式,则安装成功
更改淘宝镜像
管理员方式打开DOS窗口,输入
npm config set registry https://registry.npm.taobao.org
点击回车就更换成功了,可以通过
npm config get registry
上面命令去查看是否成功,如果返回是如下图则更换成功
全局安装webpack
- 在DOS命令窗口中输入 npm install webpack@3.6.0 -g 安装webpack(这里指定版本号3.6.0,因为 vue cli2 依赖该版本)
- 其中的 -g 代表是全局安装,全局安装可以在电脑任何一个位置执行webpack指令
- 安装需要3~5分钟,之后出现可以让你输入的命令行就说明安装完成,如下图
- 在DOS命令窗口输入
webpack --version
- 注意是两个横线
如果你看到3.6.0,说明你已经成功安装了webpack3.6.0
二十三、webpack的基本使用过程(掌握)
准备工作
首先创建如下文件和文件夹
-
dist文件夹: 用于存放之后打包的文件
-
src 文件夹: 用于存放我们写的源文件
- main.js 项目的入口文件
- mathUtils.js 定义了一些数学工具函数,可以在其他地方引用,并且使用。
-
index.html 浏览器打开展示的首页html
-
package.json 通过 npm init 生成的,npm 包管理的文件
我们应该使用webpack工具,对多个 js 文件进行打包(注意路径)
webpack 12-认识webpack/src/main.js 12-认识webpack/dist/bundle.js
使用打包后的文件
打包后会在 dist 文件夹下,生成一个 bundle.js 文件
- 此文件是 webpack 处理了项目直接文件依赖后生成的一个 js 文件,我们只需要将这个 js 文件在 index.html 中引入即可
<script src="dist/bundle.js"></script>
示例
例如,我们使用模块化开发两个js文件
之后在 main.js 中进行引入两个js文件
在Termial终端使用 webpack 打包main.js
webpack 12-认识webpack/src/main.js 12-认识webpack/dist/bundle.js
运行测试:
所以我们就知道了,我们以后在 src 下开发,之后让 webpack 打包main.js,然后我们引用打包后的js文件,这就是我们的开发模式。
二十四、webpack.config.js配置和package.json配置(掌握)
每次使用webpack的命令都需要写上入口和出口作为参数,非常麻烦,有没有一种方法可以将者两个参数写到配置中,在运行时,直接读取呢?
- 当然可以,就是创建一个 webpack.config.js 文件
- 通过 entry 节点指定打包的入口
- 通过 output 节点指定打包的出口
webpack.config.js配置作用:
当我们在当前文件夹下输入webpack命令可以自动打包
手动配置path
然后一路回车ok就会生成package.json
这个文件是描述项目信息的!
输入npm install
重新配置我们的出口路径
再输入webpack:
直接打包成功,不需要我们写路径了
而我们开发中不常用直接写webpack命令
一般写:npm run build命令
这里需要我们做命令映射(在package.json中)
再次输入npm run build
打包成功!
之前我们都是安装的全局webpack,现在安装本地的
-
既然已经全局安装了 webpack,为什么还要局部安装呢?这是因为我们需要的项目可能是从网上下载下来的,项目中使用的webpack和我们本地的webpack可能版本不同,这就需要在局部安装webpack了
-
目前,我们使用的webpack是全局的webpack,如果我们想使用局部来打包呢?
-
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。
-
所以通常一个项目,都有自己局部的webpack。
安装本地webpack:
dev为开发时依赖
后面我们会使用运行时依赖
当我们在终端和Terminal中进行打包命令,用的都是全局webpack
当使用我们配置的命令例如npm run build,优先使用本地webpack打包
再次声明webpack的打包是为了模块化开发,将我们的模块化导入导出等等命令翻译为浏览器可以识别的代码
二十五、webpack中使用css文件的配置(掌握)
-
loader是webpack中一个非常核心的概念。
webpack用来做什么呢? -
在我们之前的实例中,我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖。
-
但是,在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
-
对于webpack本身的能力来说,对于这些转化是不支持的。
-
那怎么办呢?给webpack扩展对应的 loader 就可以啦。
loader使用过程:
步骤一:通过 npm 安装需要使用的 loader
步骤二:在 webpack.config.js 中的 modules 关键字下进行配置
在实际开发过程中, webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他 非 .js 后缀名结尾的模块 , webpack 默认处理不了, 需要调用 loader 加载器才可以正常打包 ,否则会报错
loader加载器的作用: 协助 webpack 打包处理特定的文件模块 。比如:
- css-loader 可以打包处理 .css 相关的文件
- less-loader 可以打包处理 .less 相关的文件
- babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法
首先介绍css文件处理准备工作
建立我们的css文件步骤
-
在src目录中,创建一个css文件,其中创建一个normal.css文件。
-
我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
-
normal.css 中的代码非常简单,就是将body设置为red
-
在main.js中导入css的模块依赖
此时我们进行打包是不会成功的
现在开始安装我们的loader
在webpack中文网中有 loader的用法:https://www.webpackjs.com/
css 文件的打包需要用到 style-loader,css-loader
先安装 css-loader
在Terminal中输入以下命令安装css-loader:
npm install --save-dev css-loader
引入依赖我们在main.js中已经做了
//3.依赖css文件
require('./css/normal.css')
在webpack.config.js中导入配置:
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
}
style-loader也需要安装,因为css-loader只负责将css文件加载不会渲染页面,页面渲染还得靠style-loader
在Terminal中输入以下命令安装style-loader:
npm install style-loader --save-dev
其余配置不变
注意点:
- webpack在使用多个loader时,是从右向左加载的,因此先加载的文件放在右边,这里css-loader放在右边,style-loader放在左边
- 其中, test 表示匹配的文件类型,use 表示对应要调用的 loader
css打包时报错:Module build failed: TypeError: this.getOptions is not a function(版本问题)
降低style-loader的版本:(如果不是这个版本问题就降低css-loader)
npm install --save-dev style-loader@0.23.1
测试结果:
运行结果:
二十六、webpack-less文件的处理(掌握)
如果我们希望在项目中使用less、scss、stylus来写样式,webpack是否可以帮助我们处理呢?
-
我们这里以less为例,其他也是一样的。
-
我们还是先创建一个less文件,依然放在css文件夹中
- 继续在中文文档里面查看 less-loader 的用法
第一步:仍然是安装:
npm install --save-dev less-loader less
第二步:在main.js中导入.less文件依赖:
第三步:配置webpack.config.js:
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}
打包测试:
老规矩,报错无法识别函数的改版本:
npm install less@3.9.0 less-loader@4.1.0 --save-dev
运行测试:perfect!!!
当然我们也可以在main.js文件中写我们的文字,使用document.write()或document.writeln()
重新打包运行吧,效果一致
二十七、webpack-图片文件的处理(掌握)
首先,我们在项目中加入两张图片:
- 一张较小的图片test01.jpg(小于8kb),一张较大的图片test02.jpeg(大于8kb)
- 我们会针对这两张图片进行不同的处理
- 我们先考虑在css样式中引用图片的情况,所以更改 normal.css 中的样式,就是使用背景图片
在src包下建立img包并放入我们的两张图片
url-loader
图片处理
- 我们使用 url-loader 来处理,依然先安装 url-loader,然后在 webpack.config.js 中进行配置
npm install --save-dev url-loader
- 导入依赖(之前导入过normal.css了,这里就不用改了)
- 配置(添加规则):
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}
打包打包!
注意你的图片是jpeg格式还有添加约束
打包成功!
运行测试:第一次失败显示
在options中添加esModule:false(移除 ES Modules 下的严格模式)
再次打包运行测试:成功
当我们图片大小超过限制时:
下载file-loader吧:
npm install file-loader --save-dev
当加载图片时,小于limit时,会将图片编译成base64字符串形式
当加载的图片,大于limit时,需要使用file-loader模块进行加载
再次打包运行测试:
改版本:
npm install file-loader@4.0.0 --save-dev
打包
而且还自动帮我们生成了新名字的图片
在dist文件夹中的图片我们是没有写入路径的因此显示失败
此时我们又需要改配置了(对于大于限制的图片才会用上此配置)
运行测试:成功!
我们发现webpack自动帮助我们生成一个非常长的名字,这是一个32位hash值,目的是防止名字重复,但是,真实开发中,我们可能对打包的图片名字有一定的要求,比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复(img/name.hash:8.ext)。
所以,我们可以在options中添加上如下选项:
- img:文件要打包到的文件夹
- name:获取图片原来的名字,放在该位置
- hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
- ext:使用图片原来的扩展名
例子:
打包!!!得到我们想要的规范
运行也没问题:
二十八、webpack-ES6转ES5的babel(掌握)
webpack只能打包处理一部分 高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借助于 babelbabel-loader 进行打包处理。
如果希望将ES6的语法转成ES5,那么就需要使用 babel-loader 进行打包处理(照顾浏览器的兼容性)
安装 babel-loader
npm install -D babel-loader @babel/core @babel/preset-env webpack
配置 webpack.config.js 文件
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
}
再次打包寻找ES6语法:(你找let也没有)
二十九、webpack-使用Vue的配置过程(掌握)
之前我们配置打包的都是普通的js代码
现在开始我们的Vue配置了
后续的项目中我们都是使用Vue.js来进行开发,而且会以特殊的文件来组织vue的组件
- 所以,下面我们来学习一下如何在我们的webpack环境中集成Vuejs
现在,我们希望在项目与中使用Vuejs,那么必然要对其有依赖,所以需要先进行安装
注:因为我们后续是在实际项目中也会使用vue的,使用并不是开发时依赖
npm install vue --save
那么,接下来就可以按照我们之前学习的方式来使用Vue了
回顾一下安装Vue的三种方式:
- 直接下载引入
- CDN引入
- npm 安装(这个才是模块化思想的体现)
要保证node_modules包里有Vue
注:如果没有下载本地webpack的输入下面命令
npm install webpack@3.6.0 --save-dev
下载vue,注意不能在加–dev(开发时依赖),因为vue是我们开发运行时都在使用的
npm install vue --save
下载成功
在main.js中引用Vue:
在index.html中使用:
打包成功并测试:(出现报错)
说明几个问题:
vue在构建最终发布版本的时候构建了两类版本
- runtime-only(运行时only)–>此版本代码中不可以有任何的template
- runtime-compiler(运行时compiler)—>代码中可以有template,因为有compiler可以用于编译template
错误信息告诉我们正在使用runtime-only版本
解决方案:指定版本
在webpack.config.js中进行配置:
resolve:{
//alias:别名
alias:{
'vue$':'vue/dist/vue.esm.js'
}
}
指定导入的vue是哪个文件
注意点:不要把js文件放在head标签里面!!!
运行测试:成功
三十、创建Vue时template和el关系(掌握)
真实开发中是不需要写const app的直接new Vue使用即可
在页面开发中只会有一个index.html(了解)
单页面复用技术:SPA(simple page web application)–>多个页面通过路由跳转:vue-router(前端路由)
而且在index页面中只会存在:<div id=“app”></div>
三十一、Vue的终极使用方案(重点掌握)
在main.js中这样写:
//5.使用vue进行开发
import Vue from 'vue'
const App={
template:`
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data(){
return{
message:'hello webpack',
name:'abc'
}
},
methods:{
btnClick(){
console.log(1111)
}
}
}
new Vue({
el:'#app',
template: '<App/>',
components:{
App
}
})
这样写也不是最优,接下来我们继续优化:
在src包下建立Vue
建立app.js文件
将刚刚的对象写入app.js中,作为导出
然后在main.js中导入
打包测试:成功!
但是这样写,模板和代码没有分离,继续优化!!!重点来了
在src包下创建.vue文件
在vue文件中,模板布局都给我们定义好了
移入我们之前的代码:
<template>
<div>
<h2 class="title">{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
message:'hello webpack',
name:'abc'
}
},
methods:{
btnClick(){
console.log(1111)
}
}
}
</script>
<style scoped>
.title{
color: green;
}
</style>
删掉app.js,在main.js中重新导入:
因为.vue是特殊文件,所以也需要配置对应的loader
需要两个东西:
- vue-loader (加载vue文件)
- vue-template-compiler(对vue文件进行编译)
第一步安装:
npm install --save-dev vue-loader vue-template-compiler
在webpack.config.js中进行配置:
打包报错:
在package.json中修改版本
重新安装
在Terminal中重新打包:
运行测试:成功
利用Vue文件进行组件化开发:
第一步:在vue文件夹下新建立Cpn.vue
第二步:写上我们的代码
第三步:在App.vue中可以注册并使用(其他地方也可以注册使用)
重新打包运行测试:成功!!!
以后开发是以组件树的模式,App.vue就可能是根组件,而且每个组件都是一个独立的vue文件
当我们使用脚手架以后,配置内容再也不需要我们手动写了!!!
三十二、webpack-横幅Plugin的使用(掌握)
plugin是什么?
- plugin是插件的意思,通常是用于对某个现有的架构进行扩展。
- webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等
loader 和 plugin 区别?
- loader主要用于转换某些类型的模块,它是一个转换器。
- plugin是插件,它是对webpack本身的扩展,是一个扩展器。
plugin 使用步骤:
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件
下面,我们就来看看可以通过哪些插件对现有的webpack打包过程进行扩容,让我们的webpack变得更加好用
添加版权的plugin
在webpack中文网中有plugin的用法:https://www.webpackjs.com/
- 该插件的名字叫做 BannerPlugin,属于 webpack 自带的插件。
步骤如下:
在 webpack.config.js 中导入 webpack
const webpack = require('webpack');
配置在 plugins 中
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归ldx所有')
]
}
打包:
三十三、webpack-HtmlWebpackPlugin的使用(掌握)
返回目录
目前,我们的index.html文件是存放在项目的根目录下的。我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。所以,我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用html-webpack-plugin插件
html-webpack-plugin 插件可以为我们做这些事情:
- 自动生成一个index.html文件(可以指定模板来生成)
- 将打包的js文件,自动通过script标签插入到body中
安装
npm install html-webpack-plugin@3.2.0 --save-dev
在 webpack.config.js 中导入 HTML 插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
打包:自动生成index.html代码
优化:
现在的问题就是要让<div id=“app”></div>自动生成
且原index.html文件中的<script type=“text/javascript” src=“bundle.js”></script>可以去掉
在webpack.config.js中配置:
打包:
三十四、webpack-UglifyjsWebpackPlugin的使用(掌握)
在项目发布之前,我们必然需要对js等文件进行压缩处理
- 这里,我们就对打包的js文件进行压缩
- 我们使用一个第三方的插件uglifyjs-webpack-plugin,且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
修改webpack.config.js文件,使用插件:
查看打包后的bunlde.js文件,是已经被压缩过了
三十五、webpack-dev-server搭建本地服务器(掌握)
返回目录
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
类似于 node.js 阶段用到的 nodemon 工具,每当修改了源代码, webpack 会自动进行项目的动态打包构建(没有真实的映射到硬盘,所以最后测试完发布的时候只需要必须执行一次打包命令)
使用之前需要先安装webpack-dev-server
npm install webpack-dev-server@2.9.1 --save-dev
配置:
- contentBase:——————为哪一个文件夹提供本地服务,默认是根文件夹,我们这里填写
- inline:true ————页面实时刷新
- port: ————指定访问的端口号
- historyApiFallback: ————在SPA页面中,依赖HTML5的history模式(介绍路由时再详解)
- host: ‘127.0.0.1’: ————实时打包所使用的主机地址
配置命令:
输入npm run dev指令
测试:
服务器运行时我们进行动态修改代码就行了,最后部署的时候一定要执行打包命令
优化配置,不需要我们手动访问浏览器
测试指令npm run dev,完成(Ctrl+C中断服务器)
三十六、webpack-配置文件的分离(掌握)
场景引入:
这时候就需要我们进行单独分离了,把测试时和打包发布时的配置分离出来
平时小项目且生产环境和开发环境不是很复杂的时候是不需要分离的,而且Vue脚手架也是做了分离的
建立build文件夹和三个js文件
现在的问题是怎么在不同的阶段组合配置文件
下载安装webpack-merge(merge是合并的意思)
npm install webpack-merge
base.config.js中放公共的配置:
/*动态获取路径第一步导入我们的path*/
const path =require('path')/*这个path文件存在node包中,需要我们手动装*/
const webpack = require('webpack');
const HtmlWebpackPlugin=require('html-webpack-plugin');
module.exports={
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,"dist"),/*动态获取我们的路径,resolve方法拼接,__dirname为所依赖的路径*/
filename:'bundle.js',
/*publicPath:'dist/'不再需要*/
},
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader' , 'css-loader']
},
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
},
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
esModule:false,
name: 'img/[name].[hash:8].[ext]'
},
}]
},
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
},
{
test: /\.vue/,
use: ['vue-loader']
}
]
},
resolve:{
//alias:别名
alias:{
'vue$':'vue/dist/vue.esm.js'
}
},
plugins:[
new webpack.BannerPlugin('最终版权归ldx所有'),
new HtmlWebpackPlugin({
template:'index.html'
}),
]
}
dev.config.js:放入测试时的配置:
const webpackMerge=require('webpack-merge')
const baseConfig=require('./base.config')
module.exports= webpackMerge.merge(baseConfig,{//高版本的webpack-merge是一个对象,需要调用其merge方法才能正常运行
devServer:{
contentBase:'./dist',/*为哪一个文件夹提供本地服务,默认是根文件夹,我们这里填写./dist*/
inline:true/*页面实时刷新*/
}
})
pro.config.js:放入打包发布时的配置:
const UglifyJsWebpackPlugin=require('uglifyjs-webpack-plugin')
const webpackMerge=require('webpack-merge')
const baseConfig=require('./base.config.js')
module.exports=webpackMerge.merge(baseConfig,{//高版本的webpack-merge是一个对象,需要调用其merge方法才能正常运行
plugins:[
new UglifyJsWebpackPlugin()
]
})
删除原来的webpack.config.js文件
在package.json中配置命令:
打包:路径问题
打包位置在build下的dist包中,需要我们修改base.config.js文件的输出路径:
再次打包:
路径正确:
可以试试npm run dev:
也成功运行:
动态添加内容试试:
浏览器也是实时刷新: