了解Vue组件的全部内容。
定义一个名为 my-com 的新组件:
<div id="app">
<my-com></my-com>
</div>
Vue.component('my-com', {
template: '<div>{{msg}}</div>',
data: function() {
return {
msg: '这里是我的组件内容'
}
}
});
var vm = new Vue({
el: '#app'
});
组件是可复用的 Vue 实例,且带有一个名字,这个名字是以注册的形式完成的,以上的my-com
就是注册的组件自定义元素标记名称,推荐使用小写加减号分割的形式命名。
要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-com></my-com>
的形式来使用组件了。
在注册组件的Vue.component(arg1,arg2)
方法中,第一个参数arg1
为定义组件名称的字符串类型,第二个参数arg2
为一个包含{ }
结构的对象,其结构中含有props
、template
、data
等选项属性。
在组件对象的选项属性template
中,DOM结构必须被一个标记元素包含,如<div></div>
作为根元素,否则,不带根元素
是无法渲染的。在渲染时 , <my-com></my-com>
会被替换为template
的内容。
组件对象的选项属性data
必须是一个函数。当定义这个 <my-com>
组件时,发现它的 data
并不是像这样直接提供一个对象:
data: {
msg: '这里是我的组件内容'
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
msg: '这里是我的组件内容'
}
}
可以将组件进行任意次数的复用:
<div id="app">
<my-com></my-com><br />
<my-com></my-com><br />
<my-com></my-com>
</div>
Vue.component('my-com', {
template: '<button @click="counter++">您点击了我{{ counter }}次。</button>',
data: function() {
return {
counter: 0
};
}
});
var vm = new Vue({
el: '#app'
});
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是<my-com>
。可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用。在组件定义的template
选项属性中,<button></button>
作为其渲染的根元素。
注意当点击按钮时,每个组件都会各自独立维护它的 counter
。因为每用一次组件,就会有一个它的新实例被创建。
通常一个应用会以一棵嵌套的组件树的形式来组织:
图 1. 组件树结构
一个页面会有页头、侧边栏、内容区等组件,每个组件又包含了其它的“导航链接、博文内容”等之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册 和 局部注册。
全局注册:
<div id="app">
<my-com></my-com>
</div>
<script>
Vue.component('my-com', {
template: '<div>{{msg}}</div>',
data: function() {
return {
msg: '这里是我的组件内容,全局注册。'
}
}
});
var vm = new Vue({
el: '#app'
});
</script>
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
局部注册:
<div id="app">
<my-com></my-com>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
'my-com': {
template: '<div>这里是我的组件内容,局部注册</div>'
}
}
});
</script>
局部注册组件,注册后的组件只有在该实例作用域下有效。
在自定义注册组件时,复用组件的自定义标签元素可以设置一些自定义属性(attribute),可以在注册组件时,使用props
选项将其属性包含在该组件 prop
列表中。props
的值可以有两种, 一种是字符串数组,一种是对象 , 先介绍数组的用法。
<div id="app">
<myblog v-for="post in posts" :key="post.id" :title="post.title"></myblog>
</div>
<script>
Vue.component('myblog', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
var vm = new Vue({
el: '#app',
data: {
posts: [{
id: 1,
title: 'JavaScript面向对象设计'
},
{
id: 2,
title: 'TensorFlow初学者教程'
},
{
id: 3,
title: '利用NumPy进行高效计算'
}
]
}
});
</script>
创建一个博文组件,在复用组件时,向这个组件传递某一篇博文的标题。父组件要正向地向子组件传递数据或参数,子组件收到后,根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程就是通过使用选项 props
来声明需要从父级接收的数据,将自定义标记元素<myblog title=""></myblog>
中的title
属性构造为一个数组单元元素,接收一个来自父级的数据值,并把它在组件模板<h3>{{ title }}</h3>
中渲染。
一个组件默认可以拥有任意数量的 prop
, 这里prop
为选项 props
对应数组中的元素,即为自定义标记元素的属性。任何值都可以传递给任何 prop
。在上述模板中,发现能够在组件实例中访问这个值,就像访问Vue根实例中data
选项中的值一样。
想要为每篇博文渲染一个组件,可以使用 v-bind
来动态传递 prop
,这在一开始不清楚要渲染的具体内容,从一个后端系统获取博文列表的时候,是非常有用的。
当构建一个 <myblog>
的博文组件时,组件的模板最终会包含的展示信息远不止一个标题:
<div id="app">
<myblog v-for="post in posts" :key="post.id" :post="post"></myblog>
</div>
<script>
Vue.component('myblog', {
props: ['post'],
template: `\
<div class="my-blog">\
<h3>{{ post.title }}</h3>\
<div v-html="post.content"></div>\
</div>`
})
var vm = new Vue({
el: '#app',
data: {
posts: [{
id: 1,
title: 'JavaScript面向对象设计',
content:'<p>JavaScript将带您进入新天地。</p>'
},
{
id: 2,
title: 'TensorFlow初学者教程',
content:'<p>深度学习是机器学习的一个子领域,它是一组受大脑结构和功能启发的算法。</p>'
},
{
id: 3,
title: '利用NumPy进行高效计算',
content:'<p>NumPy 是 Python 语言的一个扩展程序库,是一个高效计算工具</p>'
}
]
}
});
</script>
每个组件必须只有一个根元素,可以将模板的内容包裹在一个父元素内:
<div class="my-blog">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
在开发 <myblog>
组件时,它的一些功能可能要求与父级组件进行沟通。例如引入一个辅助功能:放大博文的字号,同时让页面的其它部分保持默认的字号。
<div id="app">
<div :style="{ fontSize: postFontSize + 'em' }">
<myblog v-for="post in posts" :key="post.id" :post="post"
@enlarge-text="postFontSize += 0.1"></myblog>
</div>
</div>
<script>
Vue.component('myblog', {
props: ['post'],
template: `\
<div class="my-blog">\
<h3>{{ post.title }}</h3>\
<button @click="$emit('enlarge-text')">放大文字</button>\
<div v-html="post.content"></div>\
</div>`
})
var vm = new Vue({
el: '#app',
data: {
posts: [{
id: 1,
title: 'JavaScript面向对象设计',
content: '<p>JavaScript将带您进入新天地。</p>'
},
{
id: 2,
title: 'TensorFlow初学者教程',
content: '<p>深度学习是机器学习的一个子领域,它是一组受大脑结构和功能启发的算法。</p>'
},
{
id: 3,
title: '利用NumPy进行高效计算',
content: '<p>NumPy 是 Python 语言的一个扩展程序库,是一个高效计算工具</p>'
}
],
postFontSize: 1
}
});
</script>
子组件添加一个按钮来放大字号,同时可以通过调用内建的 $emit
方法并传入一个自定义事件的事件名称enlarge-text
来触发一个事件:
<button @click="$emit('enlarge-text')"> // @click为v-on:click的语法糖
放大文字
</button>
父级组件可以像处理本地 DOM
事件一样通过 v-on
监听子组件实例的任意事件:
<myblog
...
@enlarge-text="postFontSize += 0.1" //@enlarge-text为v-on:enlarge-text的语法糖
></myblog>
有了这个 v-on:enlarge-text="postFontSize += 0.1"
监听器,父级组件就会接收该事件并更新 postFontSize
的值。
使用事件抛出一个值:
用一个事件来抛出一个特定的值,可以使用 $emit
的第二个参数来提供这个值:
<button @click="$emit('enlarge-text', 0.1)">
放大文字
</button>
然后当在父级组件监听该事件的时候,可以通过 $event
访问到被抛出的这个值:
<myblog
...
@enlarge-text="postFontSize += $event"
></myblog>
或者,通过事件处理函数方法,这个值将会作为第一个参数传入该方法:
<myblog
...
@enlarge-text="onEnlargeText"
></myblog>
methods: {
onEnlargeText: function (enlargeValue) {
this.postFontSize += enlargeValue
}
}
在组件上使用 v-model
:
自定义事件也可以用于创建支持 v-model
的自定义输入组件:
<div id="app">
<my-input v-model="searchText"></my-input>
<p>{{searchText}}</p>
</div>
<script>
Vue.component('my-input', {
props: ['value'],
template: `\
<input\
:value="value"\
@input="$emit('input', $event.target.value)"\
>`
})
var vm = new Vue({
el: '#app',
data: {
searchText:''
}
});
</script>
等价关系:
<my-input v-model="searchText"></my-input>
等价于:
<my-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></my-input>
为了让它正常工作,这个组件内的 <input>
必须:
value
属性绑定到一个名叫 value
的 prop 上;input
事件被触发时,将新的值通过自定义的 input
事件抛出。通过 props 传递数据是单向, 也就是 父组件数据变化时会传递给子组件,但是反过来不行。之所以这样设计,是尽可能将父子组件阶耦,以避免子组件无意中修改了父组件的状态。
有两种需要改变 prop
的情况,一种情况是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。可以在子组件的 data
选项内再声明一个数据 ,引用父组件的 prop
:
<div id="app">
<input type="number" v-model.number="parent_count">
<my-com :init_count="parent_count"></my-com>
<p>{{ parent_count}}</p>
</div>
<script>
Vue.component('my-com', {
props: ['init_count'],
template: '<div>{{count}}</div>',
data: function() {
return {
count: this.init_count
}
}
});
var vm = new Vue({
el: '#app',
data: {
parent_count: 0
}
});
</script>
子组件中声明了数据 count
, 它在组件初始化时获取了父组件的parent_count
值传递给子组件属性 initCount
, 之后就与之无关 了,只用维护 count
, 这样就可以避免直接操作 initCount
。
另一种情况就是 prop
作为需要被转换的依赖值传入。此种情况使用计算属性:
<div id="app">
<input type="number" v-model.number="parent_count">
<my-com :init_count="parent_count"></my-com>
<p>{{ parent_count}}</p>
</div>
<script>
Vue.component('my-com', {
props: ['init_count'],
template: '<div>{{count.c}}</div>',
computed: {
count: function() {
return {
c: this.init_count
}
}
}
});
var vm = new Vue({
el: '#app',
data: {
parent_count: 0
}
});
</script>
在定义组件时,props
选项除了数组外,还可以是对象,当 prop
需要验证时,按对象写法。为了定制 prop
的验证方式,可以为 props
中的值提供一个带有验证需求的对象,而不是一个字符串数组。
可以使用类型(type
)来声明组件属性可以接受的数据类型,当组件属性可以是多种类型中的一个时,使用数组来表示:
<div id="app">
<my-com :my_mes="parentMes"></my-com>
</div>
<script>
var myCom = {
props: {
my_mes: [Number, String]
},
template: '<div> {{ my_mes }} </div>'
};
var vm = new Vue({
el: '#app',
data: {
parentMes: '567'
},
components: {
'my-com': myCom
}
});
</script>
当传入的属性值的类型不正确时,Vue会发出警告提示,在控制台可以观测。
type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
required:
可以使用required
选项来声明这个属性的数值是否必须传入。
Vue.component('my-com', {
props: {
my_mes: {
type: Number,
required: true
}
},
template: '<div> {{ my_mes }} </div>'
});
default:
使用default
选项来指定当父组件未传入参数值时props
的属性变量的默认值:
<my-com></my-com>
Vue.component('my-com', {
props: {
my_mes: {
type: Number,
default: 123
}
},
template: '<div> {{ my_mes }} </div>'
});
当type
的类型为Array
或者Object
的时候,default
必须是一个函数:
<div id="app">
<my-com></my-com>
</div>
<script>
Vue.component('my-com', {
props: {
my_mes: {
type: Array,
default: function() {
return ['张三', '李四'];
}
}
},
template: '<div> {{ my_mes }} </div>'
});
var vm = new Vue({
el: '#app',
data: {
parentMes: ['特朗普', '蓬佩奥', '佩洛西']
}
});
</script>
required && default:
required
和default
同时出现在一个props属性变量中:
Vue.component('my-com', {
props: {
my_mes: {
type: Number,
required: true,
default: 122
}
},
template: '<div> {{ my_mes }} </div>'
});
validator:
当校验规则很复杂,默认提供的校验规则无法满足的时候可以使用自定义函数来校验。
Vue.component('my-com', {
props: {
my_mes: {
validator: function(value) {
return value >= 0 && value <= 123;
}
}
},
template: '<div> {{ my_mes }} </div>'
});
组件之间通信如图2所示:
图 2. 组件通信
当子组件需要向父组件传递数据时,就要用到自定义事件,v-on
指令除了监昕 DOM 事件外,还可以用于组件之间的自定义事件。
子组件用 $emit()
来触发事件,父组件用$on()
来监昕子组件的事件 。父组件可以直接在子组件的自定义标签元素上使用 v-on
指令来监昕子组件触发的自定义事件。
<div id="app">
<p>总数: {{ total }}</p>
<my-com @add="getTotal" @minus="getTotal"></my-com>
</div>
<script>
Vue.component('my-com', {
template: `\
<div>\
<button @click="handleAdd">加 1</button>\
<button @click="handleMinus">减 1</button>\
</div>`,
data: function() {
return {
counter: 0
}
},
methods: {
handleAdd: function() {
this.counter++
this.$emit('add', this.counter)
},
handleMinus: function() {
this.counter--
this.$emit('minus', this.counter)
}
}
});
var vm = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
getTotal: function(value) {
this.total = value;
}
}
});
</script>
以上的子组件有两个按钮,分别为“加 1”和“减 1” ,每个按钮通过$emit()
分别触发add
、minus
两个自定义事件,$emit()
方法的第一个参数是自定义事件的名称,第二个参数是要传递的数据,该数值为子组件data
选项的返回值counter
。父组件用 v-on:add和 v-on:minus在组件上监听自定义事件,并将事件值$event
传递给事件处理方法getTotal
作为第一个参数传入。
使用一个空的Vue实例作为中央事件总线(bus
),即创建了一个名为 bus
的空 Vue 实例,里面没有任何内容。然后全局定义组件my-com
。接着在再创建Vue的根实例vm
,在其生命周期的钩子函数mounted
中,监听来自 bus
的事件 on-mes
。在my-com
组件中,通过按钮事件处理程序调用bus
的$emit
函数将事件on-mes
发送出去。vm
接收到来自bus
的事件,并在回调中实现业务逻辑。
<div id="app">
<p>数据: {{ num }}</p>
<my-com></my-com>
</div>
<script>
var bus = new Vue();
Vue.component('my-com', {
template: `\
<div>\
<button @click="handleEvent">发送事件</button>\
</div>`,
data: function() {
return {
counter: 520
}
},
methods: {
handleEvent: function() {
bus.$emit('on-mes', this.counter)
}
}
});
var vm = new Vue({
el: '#app',
data: {
num: 0
},
mounted: function() {
var _this = this;
bus.$on('on-mes', function(value) {
_this.num = value;
});
}
});
</script>
以上方法通过“事件总线”实现了任何组件间的通信,包括父子、兄弟、跨级组件。
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web 组件规范草案,将 <slot>
元素作为承载分发内容的出口。
<div id="app">
<my-com url="http://www.ischoolcode.cn/post/144">
<span>查阅:</span>
{{message}}
</my-com>
</div>
<script>
Vue.component('my-com', {
props: ['url'],
template: `\
<a\
:href="url"\
class="nav-link">\
<slot></slot>\
</a>`
});
var vm = new Vue({
el: '#app',
data: {
message: '您的个人资料'
}
});
</script>
当组件渲染的时候,<slot></slot>
将会被替换,可以包含包括 HTML的任何模板代码。替换如下:
<span>查阅:</span> {{message}}
如果 <my-com>
的 template
中没有包含一个 <slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
这里的message
是父组件的数据。 slot
分发的内容,作用域是在父组件上的。
父级模板里的所有内容都是在父级作用域中编译的;子组件级模板里的所有内容都是在子作用域中编译的。
单个 slot:
在子组件内使用特殊的<slot>
元素就可以为这个子组件开启一个 slot
(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>
标签及它的内容。
<div id="app">
<my-child>
<span>内容:</span>
<p>{{message}}</p>
</my-child>
</div>
<script>
Vue.component('my-child', {
template: `\
<div>\
<slot>\
<p> 若父组件没有内容, 这个将默认显现.</p>\
</slot>\
</div>`
});
var vm = new Vue({
el: '#app',
data: {
message: '父级资料'
}
});
</script>
子组件 my-child
的模板内定义了一个<slot>
元素,并且用一个<p>
作为默认的内容, 在父组件没有写入slot
时,会渲染这段默认的文本;如果写入了slot
, 那就会替换整个<slot>
。有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在父组件没有提供内容的时候被渲染。
注意: 子组件的·<slot>
内的默认内容,其作用域在子组件内。
具名 slot:
给<slot>
元素指定一个 name
属性后可以分发多个内容,具名slot可以与单个slot共存。
<div id="app">
<my-child>
<h2 slot="header">标题</h2>
<span>内容:</span>
<p>{{message}}</p>
<div slot="footer">底部信息</div>
</my-child>
</div>
<script>
Vue.component('my-child', {
template: `\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot>\
<p> 若父组件没有内容, 这个将默认显现.</p>\
</slot>\
</div>\
<div class="footer ">\
<slot name="footer"></slot>\
</div>\
</div>`
});
var vm = new Vue({
el: '#app',
data: {
message: '父级资料'
}
});
</script>
子组件内声明了 3 个<slot>
元素,其中在<div class="main">
内的<slot>
没有使用 name
属性,它将作为默认 slot
' 出现。父组件没有使用 slot
属性的元素与内容都将出现在这里。
一个不带 name
的 <slot>
出口会带有隐含的名字“default”。如果没有指定默认的匿名 slot
,父组件内多余的内容片段都将被抛弃。
在向具名插槽提供内容的时候,可以在一个<template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
<div id="app">
<my-child>
<!--可以缩写为:<template #header>-->
<template v-slot:header>
<h1>这里可以是页面标题</h1>
</template>
<!--可以缩写为:<template #default>-->
<template v-slot:default>
<p>一段主内容.</p>
<p>另一段内容.</p>
</template>
<!--可以缩写为:<template #footer>-->
<template v-slot:footer>
<p>这里可以是页面脚部,是一些联系信息</p>
</template>
</my-child>
</div>
<script>
Vue.component('my-child', {
template: `\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot>\
<p> 若父组件没有内容, 这个将默认显现.</p>\
</slot>\
</div>\
<div class="footer ">\
<slot name="footer"></slot>\
</div>\
</div>`
});
var vm = new Vue({
el: '#app'
});
</script>
现在 <template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容。如果希望更明确一些,使用v-slot:default
,仍然可以在一个 <template>
中包裹默认插槽的内容。
注意: v-slot
只能添加在 <template>
上。
有时让插槽内容能够访问子组件中才有的数据是很有用的。
<div id="app">
<my-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</my-user>
</div>
<script>
Vue.component('my-user', {
template: `\
<span>\
<slot :user="user">\
{{ user.lastName }}\
</slot>\
</span>`,
data: function() {
return {
user: {
firstName: '张',
lastName: '三'
}
}
}
});
var vm = new Vue({
el: '#app'
});
</script>
只有 <my-user>
组件可以访问到 user
, 而渲染提供的内容是在父级的。为了让 user
在父级的插槽内容中可用,可以将 user
作为子组件模板中 <slot>
元素的一个 user
属性 v-bind
绑定上去。
绑定在 <slot>
元素上的 属性被称为插槽 prop
。现在在父级作用域中,可以使用带值的 v-slot
来定义提供的插槽 prop
的名字。在这个例子中,选择将包含所有插槽 prop
的对象命名为 slotProps
,但也可以使用任意喜欢的名字。
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表的每一项:
<div id="app">
<my-list :books="books">
<template v-slot:book="slotProps">
<li>{{ slotProps.book_name }}</li>
</template>
</my-list>
</div>
<script>
Vue.component('my-list', {
props: {
books: {
type: Array,
default: function() {
return [];
}
}
},
template: `\
<ul>\
<slot name="book"\
v-for="book in books"\
:book_name="book.name">\
</slot>\
</ul>`
});
var vm = new Vue({
el: '#app',
data: {
books: [{
name: 'JavaScript面向对象程序设计'
},
{
name: '前端框架技术'
},
{
name: '后端框架技术'
}
]
}
});
</script>
子组件 my-list
接收一个来自父级的 prop
数组 books
, 并且将它在 name
为 book
的具名 slot
上使用 v-for
指令循环,同时绑定暴露一个变量 book_name
。
Vue
提供了$slots
,它是用来访问被 slot
分发的内容。
<div id="app">
<my-child>
<h2 slot="header">标题</h2>
<p>有关概念 </p>
<p>Vue 也完全能够为复杂的单页应用提供驱动</p>
<div slot="footer">底部信息</div>
</my-child>
</div>
<script>
Vue.component('my-child', {
template: `\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer ">\
<slot name="footer"></slot>\
</div>\
</div>`,
mounted: function() {
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer;
console.log(footer);
console.log(footer[0].elm.innerHTML);
}
});
var vm = new Vue({
el: '#app'
});
</script>
通过$slots
可以访问某个具名的slot
,而this.$slots.default
代表包括了所有没有被包含在具名 slot
中的元素节点。可在控制台console
中观察到日志输出。
在很多 Vue 项目中,使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素。这种方式在很多中小规模的项目中运作的很好,但当在更复杂的项目中,前端完全由 JavaScript 驱动的时候,将有下面突出的缺陷:
component
中的命名不得重复;HTML
中有多行的时候,需要用到丑陋的 \
换行符号;HTML
和 JavaScript
组件化时,CSS
明显被遗漏;HTML
和 ES5 JavaScript
,而不能使用ES6新的语法预处理器,如Pug (formerly Jade)
和 Babel
。采用文件扩展名为 .vue
的单文件组件 (single-file components) 为以上所有问题提供了解决方法。并且可以使用 webpack
或 Browserify
等构建工具。这是vue.js
自定义的一种文件格式,一个.vue
文件就是一个单独的组件,在文件内封装了组件相关的代码:HTML
、JavaScript
、CSS
。 .vue
文件是由三部分组成:<template>
、<script>
、<style>
。
这是一个文件名为 Hello.vue 的简单实例:
<template>
<p>{{ greeting }} World!</p>
</template>
<script>
export default {
data: function() {
return {
greeting: "Hello"
};
}
};
</script>
<!-- 添加“scoped”属性以将 CSS 仅限于此组件 -->
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
正如前述,可以使用预处理器来构建简洁和功能更丰富的组件,比如 Pug
,Babel
和 Stylus
。可以只是简单地使用 Babel
、TypeScript
、SCSS
、PostCSS
或者其他任何能够帮助提高生产力的预处理器。如果搭配 vue-loader
使用 webpack
,它也能为 CSS
模块提供一流的支持。
在一个组件里,其模板、逻辑和样式是内部耦合的,并且把他们搭配在一起实际上使得组件更加内聚且更可维护。即便在单文件组件内,仍然可以把 JavaScript、CSS 分离成独立的文件然后做到热重载和预编译。
<!-- my-com.vue -->
<template>
<div>这个将被预编译</div>
</template>
<script src="./my-com.js"></script>
<style src="./my-com.css"></style>
浏览器本身并不能识别.vue
文件,需要Vue-loader
才能对.vue文件进行解析。想从零搭建自己的构建工具,这时需要通过 Vue Loader
手动配置 webpack
。
创建项目文件夹及安装
首先创建一个目录sincom-demo
,并初始化 npm
,并安装vue
模块:
mkdir sincom-demo
cd sincom-demo
npm init -y // -y的含义,在初始化时省略所有问答,全部采用默认答案,生成的默认的package.json
npm install -S vue // -S为--save的缩写,表示生产环境上线使用到的模块
应该将 vue-loader
和 vue-template-compiler
一起安装。需要注意的是vue-loader
是基于webpack
的, webpack
是一个前端资源模块化加载器和打包工具,它能够把各种资源都作为模块化来使用和处理。不同的loader用来帮助webpack打包处理一系列资源,都可以通过模块加载,最后打包输出。
npm install -D webpack webpack-cli webpack-dev-server // -D是--seve-dev的简写,只在项目开发时需要的模块
npm install -D vue-loader vue-template-compiler
vue-template-compiler
需要独立安装的原因是可以单独指定其版本。
类似的loader还有许多,如:html-loader
、css-loader
、style-loader
、babel-loader
。
npm install -D vue-html-loader
npm install -D css-loader
npm install -D vue-style-loader
npm install -D file-loader // 处理一系列的图片文件,比如:.png 、 .jpg 、.jepg等格式的图片;
npm install -D babel-loader // 用于es6转化
npm install -D @babel/core
npm install -D babel-preset-env // 自动根据配置的运营环境自动启用需要加载的babel插件
npm install -D html-webpack-plugin // 这是一个webpack插件,它简化了HTML文件的创建以服务于webpack包
安装之后,修改文件 package.json
的scripts
属性项,并观察dependencies
与devDependencies
属性项如下:
"scripts": {
"start": "webpack serve --open",
"build": "webpack"
},
"dependencies": {
"vue": "^2.6.14"
},
"devDependencies": {
"@babel/core": "^7.14.3",
"babel-loader": "^8.2.2",
"babel-preset-env": "^1.7.0",
"css-loader": "^5.2.6",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1",
"style-loader": "^2.0.0",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.9.7",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2"
},
webpack 配置
Vue Loader
的配置和其它的 loader
不太一样。除了通过一条规则将 vue-loader
应用到所有扩展名为 .vue
的文件上之外,请确保在webpack
配置中添加 Vue Loader
的插件:
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
// 配置入口文件
entry: {
main: './src/main.js',
},
// 配置出口文件
output:{
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'), // 项目的根路径
clean: true,
},
devtool: 'inline-source-map', // 追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置
devServer: { // 提供了一个基本的web server,从什么位置查找文件
contentBase: './dist',
},
// 配置模块加载器
module: {
rules: [
// ... 其它规则
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.js$/,
loader: 'babel-loader',
exclude:/node_modules/ //除了node_modules文件夹以外
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(
{
title:'单文件组件',
template: 'public/index.html',
inject: 'body',
scriptLoading: 'blocking'
}
),
]
}
这个VueLoaderPlugin
插件是必须的 !它的职责是将你定义过的其它规则复制并应用到.vue
文件里相应语言的块。例如,若有一条匹配 /\.js$/
的规则,那么它会应用到 .vue
文件里的 <script>
块。HtmlWebpackPlugin
插件依据模板将生成一个 HTML5 文件, 在 body 中使用 script 标签引入所有 webpack 生成的 bundle。
编写main.js
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false; // 阻止显示生产模式的消息。
new Vue({
render: h => h(App) // 使用 render函数渲染组件
}).$mount('#app');
编写App.vue
<template>
<div id="app">
<p>{{ greeting }} World!</p>
</div>
</template>
<script>
export default {
data: function() {
return {
greeting: "Hello"
};
}
};
</script>
<style scoped>
#app {
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
p {
font-size: 2em;
text-align: center;
}
</style>
在命令行下执行:
npm run serve
webpack-dev-server
是webpack
提供的一个小型Express
服务器。使用它可以为webpack
打包生成的资源文件提供web服务。以上命令启动web服务器,打包生成资源文件,并自动打开浏览器观察执行情景。
博文最后更新时间: