@TOC
Vue简介
vue.js简介:
- Vue (读音 /vjuː/,类似于 view),不要读错。
- Vue是一个渐进式的框架,什么是渐进式的呢? ①渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验。 ②或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统。 ③比如Core+Vue-router+Vuex,也可以满足你各种各样的需求。
- Vue有很多特点和Web开发中常见的高级功能 解耦视图和数据: ①可复用的组件 ②前端路由技术 ③状态管理 ④虚拟DOM
- 学习Vuejs的前提? ①从零学习Vue开发,并不需要你具备其他类似于Angular、React,甚至是jQuery的经验。 但是你需要具备一定的HTML、CSS、JavaScript基础。
Vue.js安装:
- 使用一个框架,我们第一步要做什么呢?安装下载它。安装Vue的方式有很多:
方式一:直接CDN引入
你可以选择引入开发环境版本还是生产环境版本
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
方式二:下载和引入
开发环境 https://vuejs.org/js/vue.js
生产环境 https://vuejs.org/js/vue.min.js
方式三:NPM安装
后续通过webpack和CLI的使用,我们使用该方式。
Vue中的MVVM:
- 什么是MVVM呢? ①View层: <1>视图层 <2>在我们前端开发中,通常就是DOM层。 <3>主要的作用是给用户展示各种信息。 ②Model层: <1>数据层 <2>数据可能是我们固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据。 <3>在我们计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单。 ③VueModel层: <1>视图模型层 <2>视图模型层是View和Model沟通的桥梁。 <3>一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中 <4>另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。
- 我们直接来看Vue的MVVM:
Vue的生命周期:
Vue入门:
- 创建Vue对象的时候,传入了一些options:{} ①{}中包含了el属性:该属性决定了这个Vue对象挂载到哪一个元素上,很明显,我们这里是挂载到了id为app的元素上 ②{}中包含了data属性:该属性中通常会存储一些数据
<div id="app">
<h2>hello {{name}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
name: 'Hello World',
}
})
</script>
- 目前掌握这些选项:
①el:
<1>类型:string | HTMLElement
<2>作用:决定之后Vue实例会管理哪一个DOM。
②data:
<1>类型:Object | Function (
组件当中data必须是一个函数) <2>作用:Vue实例对应的数据对象。 ③methods: <1>类型:{ [key: string]: Function } <2>作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用。
Mustache模板语法:
- 如何将data中的文本数据,插入到HTML中呢? ①我们已经学习过了,可以通过Mustache语法(也就是双大括号)。 ②Mustache: 胡子/胡须.
<div id="app">
<h2>hello {{name}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{counter * 2}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
name: 'Hello World',
firstName:'george',
lastName:'lin',
counter:100
}
})
</script>
v-once指令:
- 但是在某些情况下,我们可能不希望界面随意的跟随改变。这个时候我们就可以使用一个Vue的指令。
- v-once: ①该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的) ②该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
<div id="app">
<h2 v-once>{{message}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
message: 'Hello World'
}
})
</script>
v-html指令:
- 某些情况下,我们从服务器请求到的数据本身就是一个HTML代码,如果我们直接通过{{}}来输出,会将HTML代码也一起输出。但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。
- 如果我们希望解析出HTML展示,可以使用v-html指令。 ①该指令后面往往会跟上一个string类型 ②会将string的html解析出来并且进行渲染
<div id="app">
<h2 v-html="link"></h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
link: '<a href="http://www.baidu.com">百度一下<a/>'
}
})
</script>
v-text指令:
- v-text作用和Mustache比较相似:都是用于将数据显示在界面中
- v-text通常情况下,接受一个string类型。
<div id="app">
<h2 v-text="message"></h2>
<h2>{{message}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
message: 'hello world'
}
})
</script>
v-pre指令:
- v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
- 比如下面的代码: ①第一个h2元素中的内容会被编译解析出来对应的内容 ②第二个h2元素中会直接显示{{message}}
<div id="app">
<h2>{{message}}</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
message: 'hello world'
}
})
</script>
v-cloak指令:
- 在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签。
- 加了cloak斗篷后,再加上对于style,在未编译前浏览器即不会显示。
<div id="app">
<h2 v-cloak>hello {{message}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
message: 'world'
}
})
</script>
<style>
[v-cloak] {
display:none;
}
</style>
v-bind指令:
- 前面我们学习的指令主要作用是将值插入到我们模板的内容当中。 但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。比如动态绑定a元素的href属性 比如动态绑定img元素的src属性 这个时候,我们可以使用v-bind指令。
- v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍)
<div id="app">
<a v-bind:href="link">vuejs官网</a>
<img v-bind:src='logoURL' alt="">
</div>
<script>
let vm = new Vue({
el:"app",
data:{
logoURL: 'https://vuejs.org/images/logo.png',
link:'https://vuejs.org/'
}
})
</script>
- v-bind有一个对应的语法糖,也就是简写方式 在开发中,我们通常会使用语法糖的形式,因为这样更加简洁。
<div id="app">
<a :href="link">vuejs官网</a>
<img :src='logoURL' alt="">
</div>
- 注意:
没有参数时,可以绑定一个包含键值对的对象
<div v-bind="{class:'box',[key]:'my-box'}"></div>
【结果】
<div id="app">
<div id="my-box" class="box"></div>
</div>
const vm = new Vue({
el:"#app",
data:{
imageSrc:"./pic1.jpg",
fileName:"pic1.jpg",
key: "id",
value: "btn"
}
})
- v-bind绑定class: ①绑定class有两种方式: <1>对象语法 <2>数组语法
一、绑定方式:对象语法:
用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>
用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
二、绑定方式:数组语法:
用法一:直接通过[]绑定一个类
<h2 :class="['active']">Hello World</h2>
用法二:也可以传入多个值
<h2 :class=“[‘active’, 'line']">Hello World</h2>
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :class=“[‘active’, 'line']">Hello World</h2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
- v-bind绑定style: ①我们可以利用v-bind:style来绑定一些CSS内联样式。 ②在写CSS属性名的时候,比如font-size <1>我们可以使用驼峰式 (camelCase) fontSize <2>或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’ ③绑定class有两种方式: <1>对象语法 <2>数组语法
绑定方式一:对象语法:
:style="{color: currentColor, fontSize: fontSize + 'px'}"
style后面跟的是一个对象类型
对象的key是CSS属性名称
对象的value是具体赋的值,值可以来自于data中的属性
绑定方式二:数组语法:
<div v-bind:style="[baseStyles, overridingStyles]"></div>
style后面跟的是一个数组类型
多个值以,分割即可
- 可以用方括号括起来的JavaScript表达式作为一个指令的参数:
//当attributeName=href时,等同于上面的v-bind:href="url"
<a v-bind : [attributeName]="url"> ... < / a>
什么是计算属性?
- 我们知道,在模板中可以直接通过插值语法显示一些data中的数据。但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示。比如我们有firstName和lastName两个变量,我们需要显示完整的名称。但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}} {{lastName}}。
- 每个计算属性都包含一个getter和一个setter。在上面的例子中,我们只是使用getter来读取。在某些情况下,你也可以提供一个setter方法(不常用)。
<div id="app">
<h2>{{fullName}}</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
name: 'Hello World',
firstName:'george',
lastName:'lin',
counter:100
},
computed:{
fullName(){
return this.firstName + ' ' + this.lastName
},
fullName2:{
get(){
return this.firstName + ' ' + this.lastName
},
set(newValue){
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
}
})
</script>
- 我们可能会考虑这样的一个问题:
①methods和computed看起来都可以实现我们的功能,
②那么为什么还要多一个计算属性这个东西呢?
③原因:
计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。
侦听器watch():
- 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
- 方法名必须是属性名,方法的参数第一个是改变后的新值,第二个是改变前的旧值。
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script>
const watchExampleVM = Vue.createApp({
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// 每当 question 发生变化时,该函数将会执行
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.get('https://yesno.wtf/api')
.then(response => {
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
事件监听与v-on指令:
- 在前端开发中,我们需要经常和用于交互。这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等,在Vue中如何监听事件呢?使用v-on指令。
- 注:v-on也有对应的语法糖:v-on:click可以写成@click。
- 当通过methods中定义方法,以供@click调用时,需要注意参数问题:
①
如果该方法不需要额外参数,那么方法后的()可以不添加。但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去。②如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。 - v-on修饰符:在某些情况下,我们拿到event的目的可能是进行一些事件处理。Vue提供了修饰符来帮助我们方便的处理一些事件: ①.stop - 调用 event.stopPropagation() - 停止冒泡。 ②.prevent - 调用 event.preventDefault() - 阻止默认行为。 ③.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。 ④.native - 监听组件根元素的原生事件。 ⑤.once - 只触发一次回调。
<div id="app">
<button v-on:click="counter++">按钮点击1</buton>
<button @click="btnClick">按钮点击2</buton>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
counter: 0,
},
methods:{
btnClick():{
this.counter++
}
}
})
</script>
<div id="app">
<h2>点击次数:{{counter}}</h2>
<button @click="handleAdd">+1</buton>
<button @click="handleAdd(10,$event)">+10</buton>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
counter: 0,
},
methods:{
handleAdd(event):{
console.log(event);
this.counter++;
},
handleAddTen(count,event):{
console.log(event);
this.counter += 10;
}
}
})
</script>
v-if、v-else-if、v-else指令:
- v-if、v-else-if、v-else 这三个指令与JavaScript的条件语句if、else、else if类似。Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件。
- v-if的原理:v-if后面的条件为false时,对应的元素以及其子元素不会渲染。也就是根本没有不会有对应的标签出现在DOM中。
- 注意: ①这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。 ②解决方案:如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key,并且我们需要保证key的不同。
<div id="app">
<div v-if="score>=90">优秀</div>
<div v-else-if="score>=80">良好</div>
<div v-else="score>=60">及格</div>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
score: 98,
}
})
</script>
v-show指令:
- v-show的用法和v-if非常相似,也用于决定一个元素是否渲染: ①v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢? ②v-if当条件为false时,压根不会有对应的元素在DOM中。 ③v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
- 开发中如何选择呢? ①当需要在显示与隐藏之间切片很频繁时,使用v-show ②当只有一次切换时,通过使用v-if
<div id="app">
<h2 v-show="isShow">我才不要显示</h2>
</div>
<script>
let vm = new Vue({
el:"app",
data:{
isShow: 'true',
}
})
</script>
v-for指令:
- 当我们有一组数据需要进行渲染时,我们就可以使用v-for来完成。 ①v-for的语法类似于JavaScript中的for循环。 ②格式如下:item in items的形式。
- 如果在遍历的过程中,我们需要拿到元素在数组中的索引值呢? ①语法格式:v-for=(item, key,index) in items ②其中的index就代表了取出的item在原数组的索引值。
- 组件的key属性:
①
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。②为什么需要这个key属性呢(了解)? <1>这个其实和Vue的虚拟DOM的Diff算法有关系。这里我们借用React’s diff algorithm中的一张图来简单说明一下:当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点,我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率? <2>所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。 ③所以一句话,key的作用主要是为了高效的更新虚拟DOM。 - 检测数组更新: ①因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。 ② <1>push():向数组的末尾添加一个或多个元素,并返回新的长度。 <2>pop():最后一位元素删除并返回数组的最后一个元素。 <3>shift():把数组的第一个元素从其中删除,并返回第一个元素的值。 <4>unshift():可向数组的开头添加一个或更多元素,并返回新的长度。 <5>splice():删除元素/插入元素/替换元素。传入一个参数index,将index以及后面的数据都删除掉 1、删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素) 2、替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素 3、插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素 <6>sort():排序 <7>reverse():逆转顺序
v-model指令:
- 表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。Vue中使用v-model指令来实现表单元素和数据的双向绑定。
- 当然我们也可以将v-model用于textarea元素。
- v-model其实是一个语法糖,它的背后本质上是包含两个操作: ①v-bind绑定一个value属性 ②v-on指令给当前元素绑定input事件
<input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
- v-model修饰符: ①lazy修饰符默认情况下,v-model默认是在input事件中同步输入框的数据的。也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。lazy修饰符可以让数据在失去焦点或者回车时才会更新。 ②number修饰符:默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。number修饰符可以让在输入框中输入的内容自动转成数字类型。 ③trim修饰符:如果输入的内容首尾有很多空格,通常我们希望将其去除,trim修饰符可以过滤内容左右两边的空格。
<div id='app'>
//按钮
<label><input type='radio' v-model='gender' id='female' value='female'>女</label>
//单复选框
<label><input type='checkbox' v-model='checked' id='check'>同意协议</label>
//多复选框
<label><input type='checkbox' v-model='hobbies' value='篮球'>篮球</label>
<label><input type='checkbox' v-model='hobbies' value='足球'>足球</label>
<label><input type='checkbox' v-model='hobbies' value='台球'>台球</label>
//下拉框
<select v-model= 'mySelects' multiple>
<option value='apple'>苹果</option>
<option value='orange'>橘子</option>
<option value='banana'>香蕉</option>
</select>
</div>
<script>
let app = new Vue({
el:'#app',
data"{
checked:false,
hobbies:[]
}
<script>
高阶函数:
- 编程范式: 命令式编程/声明式编程
- 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数) ①filter:过滤 ②map:映射 ③reduce:reduce作用对数组中所有的内容进行汇总
过滤器:
- 过滤器类似于高阶函数中的map映射操作。
- 过滤器可以用在两个地方:双花括号插值和 v-bind 表达式。
①多个过滤器可以进行串联
②过滤器中可以传入多个参数:
定义的过滤器默认第一个参数是处理的需要处理的信息,后续才是传入的参数。
<div id="app">
<h2>总价格: {{totalPrice | showPrice}}</h2>
<div v-bind: price=" totalPrice | dealPrice | showPrice"></div>
<h2>处理价格: {{totalPrice | showPrice(1,2)}}</h2>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
totalPrice : 100
},
filters: {
dealPrice(price) {
return '价格:' + price
},
showPrice(price) {
return '¥' + price.toFixed(2)
},
handlerPrice(price,agr1,arg2) {
return '¥' + price + agr1 + agr2
}
}
})
</script>
- 全局与局部:
//全局
Vue.filter('filter_ addPricePrefix', function (value) {
return "$"+ value;
})
//局部
filters: {
showPrice(price) {
return '¥' + price.toFixed(2)
}
}
Vue组件化
什么是组件化?
- 组件化也是类似的思想: ①如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。 ②但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
- 组件化是Vue.js中的重要思想: ①它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。 任何的应用都会被抽象成一颗组件树
- 组件化思想的应用: ①有了组件化的思想,我们在之后的开发中就要充分的利用它。 ②尽可能的将页面拆分成一个个小的、可复用的组件。 ③这样让我们的代码更加方便组织和管理,并且扩展性也更强。
注册组件的基本步骤:
- 组件的使用分成三个步骤: ①创建组件构造器 ②注册组件 ③使用组件。
<div id="app">
<!--3.使用组件-->
<my-cpn></my-cpn>
<div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const myComponent = vue.extend({
template:`
<div>
<h2>组件标题</h2>
<p>我是组件中的一个段落内容</p>
</div>`
});
//2.注册组件,并且定义组件标签的名称
vue.component('my-cpn', myComponent)
let app = new vue({
el: '#app'
})
</script>
- 步骤分析:
①Vue.extend():
<1>调用Vue.extend()创建的是一个组件构造器。
<2>通常在创建组件构造器时,传入template代表我们自定义组件的模板。
<3>该模板就是在使用到组件的地方,要显示的HTML代码。
<4>
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。②Vue.component(): <1>调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。 <2>所以需要传递两个参数:1、注册组件的标签名 2、组件构造器 ③组件必须挂载在某个Vue实例下,否则它不会生效。
全局组件和局部组件:
- 当我们通过调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
- 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件。
//全局注册:
vue.component('my-cpn', myComponent)
//局部注册:
const myComponent = vue.extend({
template:`
<div>
<h2>组件标题</h2>
<p>组件正文的内容,今天真开心啊!!!</p>
</div>`
})
let app1 = new Vue({
el: "#app",
components:{
'my-cpn' : myComponent
}
})
父组件和子组件:
- 在前面我们看到了组件树: ①组件和组件之间存在层级关系 ②而其中一种非常重要的关系就是父子组件的关系
- 父子组件错误用法:以子标签的形式在Vue实例中使用
①因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
②该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
③
<child-cpn></child-cpn>是只能在父组件中被识别的。④类似这种用法,<child-cpn></child-cpn>是会被浏览器忽略的
注册组件语法糖:
- 在上面注册组件的方式,可能会有些繁琐:
①Vue为了简化这个过程,提供了注册的语法糖。
②
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
//全局注册:
vue.component('my-cpn', {
template:`
<div>
<h2>组件标题</h2>
<p>组件正文的内容,今天真开心啊!!!</p>
</div>`
})
//局部注册:
let app1 = new Vue({
el: "#app",
components:{
'my-cpn' : {
template:`
<div>
<h2>组件标题</h2>
<p>组件正文的内容,今天真开心啊!!!</p>
</div>`
}
}
})
模板的分离写法:
- 刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
- 如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
- Vue提供了两种方案来定义HTML模块内容:
①
使用<script>标签②使用<template>标签
<div id="app">
<my-cpn></my-cpn>
<div>
<script src="../js/vue.js"></script>
//方案一:
<template id="myCpn">
<div>
<h2>组件标题</h2>
<p>我是组件中的一个段落内容</p>
</div>
</template>
//方案二:
<script type="text/x-template" id="myCpn">
<div>
<h2>组件标题</h2>
<p>我是组件中的一个段落内容</p>
</div>
<script>
<script>
//创建并注册组件,并且定义组件标签的名称
vue.component('my-cpn', myComponent)
let app = new vue({
el: '#app',
component:{
'my-cpn':{
template: '#myCpn'
}
}
})
</script>
组件数据的存放:
- 组件是一个单独功能模块的封装: 这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
- 组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)只是这个data属性必须是一个函数。而且这个函数返回一个对象,对象内部保存着数据。
为什么data在组件中必须是一个函数呢?①首先,如果不是一个函数,Vue直接就会报错。 ②其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
<script>
//创建并注册组件,并且定义组件标签的名称
vue.component('my-cpn', myComponent)
let app = new vue({
el: '#app',
component:{
'my-cpn':{
template: '#myCpn',
data():{
return {
counter: 0
}
}
}
}
})
</script>
父子组件的通信:
- 我们提到了子组件是不能引用父组件或者Vue实例的数据的。 但是,在开发中,往往一些数据确实需要从上层传递到下层: ①比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
- 如何进行父子组件间的通信呢?
①
通过props向子组件传递数据②通过事件向父组件发送消息 - props基本用法:
①在组件中,使用选项props来声明需要从父级接收到的数据。
②props的值有两种方式:
<1>字符串数组:数组中的字符串就是传递时的名称。
<2>对象:
对象可以设置传递时的类型,也可以设置默认值等。③验证都支持哪些数据类型呢? <1>String<2>Number<3>Boolean<4>Array<5>Object<6>Date<7>Function<8>Symbol
<div id=" app">
<child-cpn :message="message" :numberMessage="100"</child-cpn>
</div>
<template id="childCpn">
<div>显示的信息:{{message}]</div>
<div>显示的数字:{{numberMessage}]</div>
</template>
<script src="../ js/vue.js"></script>
<script>
let app = new vue({
el: '#app',
data: {
message:'Hello world'
},
components: {
'child-cpn: {
template: '#childCpn',
//一、数组写法:
props:['message']
//二、对象写法:
props:{
numberMessage:Numer
}
}
}
})
</script>
- 自定义事件: ①当子组件需要向父组件传递数据时,就要用到自定义事件了。 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。 ②自定义事件的流程: <1>在子组件中,通过$emit()来触发事件。 <2>在父组件中,通过v-on来监听子组件事件。
<div id="app">
<child-cpn @increment="changeTotal" @decrement="changeTotal"></child-cpn>
<h2>点击次数: {{total}</h2>
</div>
<template id="childCpn">
<div>
<button @click="increment">+1</ button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
let app = new vue({
el: '#app',
data: {
total: 0
}
methods: {
changeTotal(counter) {
this.total =counter
}
},
components: {
'child-cpn' : {
template: '#childCpn',
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++;
this.$emit('increment',this.counter)
},
decrement() {
this.counter--;
this.$emit('decrement',this.counter)
}
}
}
}
})
</script>
- 父子组件的访问方式:有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。父组件访问子组件:使用
$children或$refs。子组件访问父组件:使用$parent。 ①$children:是一个数组类型,它包含所有子组件对象。 ②$children的缺陷:<1>通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。 <2>但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。 <3>有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs。 ③$refs:<1>$refs和ref指令通常是一起使用的。 <2>首先,我们通过ref给某一个子组件绑定一个特定的ID。 <3>其次,通过this.$refs.ID就可以访问到该组件了。 ④$parent:<1>尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。 <2>子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。 <3>如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。 <4>另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
非父子组件通信:
- 刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢? ①非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。
- 在Vue1.x的时候,可以通过$dispatch和$broadcast完成 ①$dispatch用于向上级派发事件 ②$broadcast用于向下级广播事件 ③但是在Vue2.x都被取消了
- 在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。 ①但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。 ②并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。
插槽slot:
- 编译作用域: ①父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
- 为什么使用slot: ①组件的插槽也是为了让我们封装的组件更加具有扩展性。 ②让使用者可以决定组件内部的一些内容到底展示什么。
- 如何去封装这类的组件呢? ①它们也很多区别,但是也有很多共性。 ②如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。 ③但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
- 如何封装合适呢?抽取共性,保留不同。 ①最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。 ②一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。 ③是搜索框,还是文字,还是菜单。由调用者自己来决定。
- slot基本使用: ①在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。 ②该插槽插入什么内容取决于父组件如何使用。 ③<slot>中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容。
- 当子组件的功能复杂时,子组件的插槽可能并非是一个。 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢? ①这个时候,我们就需要给插槽起一个名字只要给slot元素一个name属性即可。 ②<slot name='myslot'>
//定义:
<template id="myCpn">
<div>
<slot name='left'>我是一个插槽中的默认内容</slot>
<slot name='right'>我是一个插槽中的默认内容</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
Vue.component( 'my-cpn', {
template: '#myCpn '
})
let app = new vue({
el: '#app'
})
</script>
//使用:
<div id="app" >
<my-cpn></my-cpn>
<my-cpn>
<h2 slot='left'>我是替换插槽的内容</h2>
<p slot='right'>我也是替换插槽的内容</p>
</my-cpn>
<div>
- 作用域插槽:
①
父组件替换插槽的标签,但是内容由子组件来提供。②子组件中使用data属性定义,父组件中使用template标签以及slot-scope属性获取。
//使用:
<div id="app>
<!--1.列表形式展示-->
<my-cpn>
<template slot-scope="slotProps">
<ul>
<li v-for="info in slotProps.data">{{info}}</li>
</ul>
</template>
</my-cpn>
<!--2.水平展示-->
<my-cpn>
<template slot-scope="slotProps">
<span v-for="info in slotProps.data">{{info}}</span>
</template>
</my-cpn>
</div>
//设置:
<template id="myCpn">
<div>
<slot :data="pLanguages"></slot>
</div>
</template>
<script src=" ../js/ue.js"></script>
<script>
Vue.component( 'my-4pn ', {
template: '#myCpr'
data() {
return {
pLanguages: [
'JavaScript', 'Python ' , 'Swift', 'Go', 'C++'
]
}
}
})
</script>
Vue模块化
JavaScript原始功能:
- 在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。 ①那个时候的代码是怎么写的呢?直接将代码写在<script>标签中即可
- 随着ajax异步请求的出现,慢慢形成了前后端的分离 ①客户端需要完成的事情越来越多,代码量也是与日俱增。 ②为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。 ③但是这种维护方式,依然不能避免一些灾难性的问题。
- 比如全局变量同名问题:看右边的例子
- 另外,这种代码的编写方式对js文件的依赖顺序几乎是强制性的 ①但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。 ②而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。
匿名函数的解决方案:
- 我们可以使用匿名函数来解决方面的重名问题
使用模块作为出口:
- 我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?
- 我们做了什么事情呢?
①
非常简单,在匿名函数内部,定义一个对象。②给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。③最后将这个对象返回,并且在外面使用了一个MoudleA接受。④使用时我们只需要使用属于自己模块的属性和方法即可。 - 这就是模块最基础的封装,事实上模块的封装还有很多高级的话题: ①但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。 ②幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。
- 常见的模块化规范:CommonJS、AMD、CMD,也有ES6的Modules
//定义:
var ModuleA = (function() {
1.定义一个对象
var obj = {}
2.在对象内部添加变量和方法
obj.f1ag = true
obj.myFunc = function (info) {
console.log(info);
}
3.将对象返回
return obj
})()
//使用:
if (ModuleA.flag) {
console.log('小明是个天才');
}
ModuleA.myFunc('小明长得真帅')
console.log(ModuleA);
CommonJS:
- 模块化有两个核心:导出和导入
- CommonJS的导出:
module.exports = {
flag: true,
test(a,b) {
return a + b
},
demo(a, b) {
return a * b
}
}
- CommonJS的导入:
/common]s模块
let { test,demo,flag } = require('moduleA');
//等同于
let _mA = require('moduleA');
let test = _mA.test;
let demo = _mA.demo;
let flag = _mA.f1ag;
ES6的Modules:
- export指令用于导出变量。export基本使用:
//第一种写法
export let name = 'why';
export let age = 18;
export let height = 1.88;
//第二种写法
let name = 'why';
let age = 18;
let height = 1.88;
export {name,age,height};
- export导出函数或类:
//第一种写法
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) i
this.name = name;this.age = age;
}
run() {
console.log(this.name +'在奔跑');
}
}
export {test,Person}
- 某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名。这个时候就可以使用export default。
①需要注意:export default在同一个模块中,不允许同时存在多个。
②一句话总结:
<1>
默认导出(就是在defau{}里面的)不要加{},因为import模块时的命名可以是随意的。<2>其他需要加{},因为指定了命名,需要按指定命名import。<3>一个模块中只能有一个默认导出export default,但是却可以有任意命名导出(0个,1个,多个)。③Import路径或者依赖(模块) <1>依赖(模块):模块一般为一个字符串。从当前 package的 node_modules 里面找,找不到就到当前 package 目录上层 node_modules 里面取... 一直找到全局node_modules 目录。 <2>路径:路径一般以./、/、../开头。根据对应路径寻找对应文件。
//info.js
export default function(){
console.log('default function');
}
import myFunc from './info.js'
myFcun()
//一个default,多个任意
// A.js
export default 42
export const myA =43
export const Something=44
// B.js
import A,{myA,Something}from'./A'
- 我们就可以通过import命令来加载对应的这个模块了: ①如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦。 ②可以通过*可以导入模块中所有的export变量 ③但是通常情况下我们需要给*起一个别名,方便后续的使用
import {name,age,hegigt} from './info.js'
import * as info from './info.js'
Webpack
什么是Webpack?
- 从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
- 我们从两个点来解释上面这句话:
模块和打包①在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。这就是webpack中模块化的概念。 ②理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
和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了。
- 所以,grunt/gulp和webpack有什么不同呢? ①grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。 ②webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
webpack安装:
- 安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
- 查看自己的node版本:node -v
- 全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本): ①npm install webpack@3.6.0 -g
- 局部安装webpack(后续才需要),--save-dev是开发时依赖,项目打包后不需要继续使用的。 ①cd 对应目录 ②npm install webpack@3.6.0 --save-dev
- 为什么全局安装后,还需要局部安装呢? ①在终端直接执行webpack命令,使用的全局安装的webpack ②当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
webpack局部安装与自定义启动:
- 目前,我们使用的webpack是全局的webpack,如果我们想使用局部来打包呢? ①因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。 ②所以通常一个项目,都有自己局部的webpack。
- 步骤: ①项目中需要安装自己局部的webpack:npm install webpack@3.6.0 --save-dev ②通过node_modules/.bin/webpack启动webpack打包
- package.json中定义启动:
①次执行都敲这么一长串有没有觉得不方便,我们可以在package.json的scripts中定义自己的执行脚本。
②package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
<1>首先,会寻找本地的node_modules/.bin路径中对应的命令。
<2>如果没有找到,会去全局的环境变量中寻找。
③如何执行我们的build指令呢?
<1>
npm run build
{
"name" : "meetwebpack" ,
"version" : "1.0.0"",
"description":"",
"main" : "index.js",
"scripts": {
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.6.0"
}
文件和文件夹解析:
- dist文件夹:用于存放之后打包的文件
- src文件夹:用于存放我们写的源文件
- main.js:项目的入口文件。具体内容查看下面详情。
- mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。
- index.html:浏览器打开展示的首页html
- package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
js文件打包与配置:
- 如何打包呢?使用webpack的指令即可: ①webpack src/main.js dist/bundle.js
- 打包后会在dist文件下,生成一个bundle.js文件。bundle.js文件是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可。
- 如果每次使用webpack的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时直接读取呢?当然可以,就是创建一个webpack.config.js文件。
const path = require( 'path')
module.exports = {
//入口:可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可
entry: './ src/main.js',
//出口:通常是一个对象,里面至少包含两个重要属性,path 和 filename
output: {
path: path.resolve(___dirname,'dist'),//注意:path通常是一个绝对路径
filename: " bundle.js'
}
}
什么是loader?
- 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关键字下进行配置
- 大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法。
css文件打包与配置:
- 在入口文件main.js中引用css文件
- 命令: ①npm install --save-dev style-loader ②npm install --save-dev css-loader
- css-loader只负责加载css文件,但是并不负责将css具体样式嵌入到文档中。这个时候,我们还需要一个style-loader帮助我们处理。
- 注意: ①style-loader需要放在css-loader的前面。 ②这次因为webpack在读取使用的loader的过程中,是按照从右向左的顺序读取的。
const path = require( 'path')
module.exports = {
//入口:可以是字符串/数组/对象,这里我们入口只有一个,所以写个字符串即可
entry: './src/main.js',
//出口:通常是一个对象,里面至少包含两个重要属性,path 和fi Lename
output: {
path: path. resolve(__ dirname, 'dist'), //注意: path通常是 个绝对路径
filename: 'bundle.js '
},
module: {
rules: [
{
test: /.CSS$/ ,
use: ['style- loader', 'css-loader']
}
]
}
}
less文件打包与配置:
- 在入口文件main.js中引用less文件
- 命令:
- 命令: ①npm install --save-dev less-loader less
- 修改对应的配置文件:添加一个rules选项,用于处理.less文件
{
test : /\. less$/ ,
use: [{
loader: "style-loader"
},{
loader: "css-loader"
}, {
loader: "less-loader"
}]
}
图片文件打包与配置:
- 图片处理,我们使用url-loader来处理,依然先安装url-loader: ①npm install --save-dev url-loader
- 修改webpack.config.js配置文件:
{
test: /\.(png ljpg |gifljpeg)$/,
use :[
{
loader: 'url-loader' ,
options: {
limit: 8192
}
}
]
}
- 再次打包,运行index.html,就会发现我们的背景图片选出了出来。 而仔细观察,你会发现背景图是通过base64显示出来的。OK,这也是limit属性的作用,当图片小于8kb时,对图片进行base64编码。
- 那么问题来了,如果大于8kb呢?
我们需要安装file-loader。①npm install --save-dev file- loader ②再次打包会发现dist文件夹下多了一个图片文件。 - 我们发现webpack自动帮助我们生成一个非常长的名字 ①这是一个32位hash值,目的是防止名字重复 ②但是,真实开发中,我们可能对打包的图片名字有一定的要求 ③比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复
- 所以,我们可以在options中添加上如下选项: ①img:文件要打包到的文件夹 ②name:获取图片原来的名字,放在该位置 ③hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位 ④ext:使用图片原来的扩展名
options: {
limit: 8192
name : 'img/[name].[hash:8].[ext]'
}
- 但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确 ①默认情况下,webpack会将生成的路径直接返回给使用者 ②但是,我们整个程序是打包在dist文件夹下的,所以这里我们需要在路径下再添加一个dist/
output: {
path: path. resolve(_ dirname, 'dist'),
filename: ' bundle.js',
publicPath: 'dist/'
}
babel打包与配置:
- 如果你仔细阅读webpack打包的js文件,发现写的ES6语法并没有转成ES5,那么就意味着可能一些对ES6还不支持的浏览器没有办法很好的运行我们的代码。在前面我们说过,如果希望将ES6的语法转成ES5,那么就需要使用babel。
- 而在webpack中,我们直接使用babel对应的loader就可以了。 ①npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
{
test: /\.m?js$/ ,
exclude: / (node_ modules Ibower_ components)/ ,
use: {
loader: ' babel-loader' ,
options:
{
presets: [ ' es2015 ']
}
}
}
webpack配置vue:
- 我们希望在项目中使用Vuejs,那么必然需要对其有依赖,所以需要先进行安装。注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖。 ①npm install vue --save
- 修改完成后,重新打包,运行程序:
①打包过程没有任何错误(因为只是多打包了一个vue的js文件而已)
②但是运行程序,没有出现想要的效果,而且浏览器中有报错
③
这个错误说的是我们使用的是runtime-only版本的Vue。④这里我只说解决方案:Vue不同版本构建,后续我具体讲解runtime-only和runtime-compiler的区别。 ⑤所以我们修改webpack的配置,添加如下内容即可。
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
el和template区别:
- 如果我们希望将data中的数据显示在界面中,就必须是修改index.html。如果我们后面自定义了组件,也必须修改index.html来使用组件。但是html模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
- 在前面的Vue实例中我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容。这里我们可以将div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素。但是如果我依然希望在其中显示{{message}}的内容,我们可以再定义一个template属性,代码如下:
new Vue({
el: ' #app '
template: '<div id=" app">{{message}}</div>',
data: {
message: ' coderwhy '
}
})
- el和template模板的关系是什么呢? ①我们知道el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等。 ②而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。 ③这样做之后我们就不需要在以后的开发中再次操作index.html,只需要在template中写入对应的标签即可。 ④书写template模块非常麻烦怎么办呢?没有关系,稍后我们会将template模板中的内容进行抽离。会分成三部分书写:template、script、style,结构变得非常清晰。
.vue文件封装处理:
- 但是一个组件以一个js对象的形式进行组织和使用的时候是非常不方便的。一方面编写template模块非常的麻烦。另外一方面如果有样式的话,我们写在哪里比较合适呢?
- 现在我们以一种全新的方式来组织一个vue的组件。
- 但是这个时候这个文件可以被正确的加载吗?必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。 ①谁来处理呢?vue-loader以及vue-template-compiler。
- 安装vue-loader和vue-template-compiler。 ①npm install vue-loader vue-template-compiler --save-dev
- 修改webpack.config.js的配置文件:
{
test: /\.vue$/,
use: ['vue-loader']
}
webpack插件:
- plugin是插件的意思,通常是用于对某个现有的架构进行扩展。webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
- loader和plugin区别: ①loader主要用于转换某些类型的模块,它是一个转换器。 ②plugin是插件,它是对webpack本身的扩展,是一个扩展器。
- plugin的使用过程: ①通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装) ②在webpack.config.js中的plugins中配置插件。
- 添加版权的Plugin: ①我们先来使用一个最简单的插件,为打包的文件添加版权声明。该插件名字叫BannerPlugin,属于webpack自带的插件。按照下面的方式来修改webpack.config.js的文件:
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归coderwhy所有')
}
- 打包html的plugin: ①我们的index.html文件是存放在项目的根目录下的。我们知道在真实发布项目时发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件。HtmlWebpackPlugin插件可以为我们做这些事情: <1>自动生成一个index.html文件(可以指定模板来生成) <2>将打包的js文件,自动通过script标签插入到body中 ②安装HtmlWebpackPlugin插件:npm install html-webpack-plugin --save-dev ③使用插件修改webpack.config.js文件中plugins部分的内容如下: <1>这里的template表示根据什么模板来生成index.html <2>另外,我们需要删除之前在output中添加的publicPath属性,否则插入的script标签中的src可能会有问题。
plugins: [
new htmlWebpackPlugin({
template: 'index.htmL'
})
]
- js压缩的Plugin: ①在项目发布之前,我们必然需要对js等文件进行压缩处理。这里我们就对打包的js文件进行压缩 我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致。 ②npm install uglifyjs-webpack-plugin@1.1.1 --save-dev ③修改webpack.config.js文件,使用插件:
module.exports = {
plugins: [
new uglifyJsPlugin()
}
vue本地服务器:
- webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
- 不过它是一个单独的模块,在webpack中使用之前需要先安装它:npm install --save-dev webpack-dev-server@2.9.1
- devserver也是作为webpack中的一个选项,选项本身可以设置如下属性: ①contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist ②port:端口号 ③inline:页面实时刷新 ④historyApiFallback:在SPA页面中,依赖HTML5的history模式
- webpack.config.js文件配置修改如下:
devServer: {
contentBase './dist',
inline: true
}
- 我们可以再在package.json配置另外一个scripts:--open参数表示直接打开浏览器
"dev": "webpack-dev-server --open"
import导入模块时路径的分析:
- 这个和 ES6 没有关系,是模块系统的约定以及实现。在 node 文档里面详细描述了处理过程。
- 在 Node.js 模块系统中,如果 require 的模块不是核心模块,而且没有 './' 之类的开头,那就需要从当前 package的 node_modules 里面找,找不到就到当前 package 目录上层 node_modules 里面取... 一直找到全局node_modules 目录。
- 这样找到的往往是文件夹,所以接下来就是处理一个文件目录作为 Node 模块的情况。如果文件目录下有 package.json,就根据它的main 字段找到 js 文件。如果没有 package.json,那就默认取文件夹下的 index.js。
- 由于 webpack browsersify 等模块打包工具是兼容 node的模块系统的,自然也会进行同样的处理流程。不同的是,它们支持更灵活的配置。比如在 webpack 里面,可以通过 alias 和 external 字段配置,实现对默认 import 逻辑的自定义。