在很多 Vue 项目中,全局组件使用 app.component() 方法定义,然后使用 app.mount(‘#app’) 在页面内绑定一个容器元素。
这种方式在很多小规模的项目中运作的很好,在这些项目里 JavaScript 只是被用来加强特定的视图。然而,在更复杂的项目中,或者前端完全由JavaScript驱动时,以下缺点将变得非常明显:
在 Vue.js 中,可以使用单文件组件解决上述所有问题。在一个文件扩展名为 .vue 的文件中编写组件,可以将组件模板代码以 HTML 的方式书写,同时 JavaScript 与 CSS 代码也在同一个文件中编写。
例如:
<template>
<div>
<ul class="item">
<li class="username">用户名:{{ post.user.username }},留言时间:{{ gstTime }}li>
<li class="title">主题:{{ post.title }},li>
<li>内容:{{ post.content }}li>
ul>
div>
template>
<style scoped>
.item {
border-top: solid 1px grey;
padding: 15px;
font-size: 14px;
color: grey;
line-height: 21px;
}
.username{
font-size: 16px;
font-weight: bold;
line-height: 24px;
color: #009a61;
}
.title {
font-size: 16px;
font-weight: bold;
line-height: 24px;
color: #009a61;
}
ul li {
list-style: none;
}
style>
<script>
export default{
name:'postItem',
data() {
return {}
},
props:['post'],
computed:{
gstTime:function(){
let d = new Date(this.post.gstTime);
d.setHours(d.getHours - 8);
return d.toLocaleString();
}
}
}
script>
在单文件组件中编写 CSS 样式规则时,可以添加一个 scoped 属性。该属性的作用是限定 CSS 样式只作用于当前组件元素,相当于是组件作用域的 CSS。
介绍组件开发中一些不常用但特殊需求下会用到的功能。
总结一下前面介绍的组件通信的 3 种方式:
而此处将介绍组件通信的其它实现方式。
在每一个根组件实例的子组件中,都可以通过 root 属性访问根实例。如下:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<div id="app">
<parent>parent>
div>
<script src="https://unpkg.com/vue@next">script>
<script>
const app = Vue.createApp({
data() {
return {
price: 188
}
},
computed: {
totalPrice() {
return this.price * 10;
}
},
methods: {
hello() {
return "葵花宝典";
}
}
})
app.component('parent', {
template: ' '
})
app.component('child', {
methods: {
accessRoot() {
console.log("单价:" + this.$root.price);
console.log("总价:" + this.$root.totalPrice);
console.log(this.$root.hello());
}
},
template: ''
})
app.mount('#app');
script>
body>
html>
在浏览器中点击“访问根实例”按钮,在 Console 窗口中的输出如下:
单价:188
总价:1880
葵花宝典
不管组件是根实例的子组件,还是更深层次的后代组件,root 属性总是代表了根实例。
与 root 类似, parent 属性用于在一个子组件中访问父组件的实例,这可以代替父组件通过 prop 想子组件传递数据的方式。
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<div id="app">
<parent>parent>
div>
<script src="https://unpkg.com/vue@next">script>
<script>
const app = Vue.createApp({});
app.component('parent', {
data() {
return {
price: 188
}
},
computed: {
totalPrice() {
return this.price * 10;
}
},
methods: {
hello() {
return "重生之门";
}
},
template: ' '
})
app.component('child', {
methods: {
accessParent() {
console.log("单价:" + this.$parent.price);
console.log("总价:" + this.$parent.totalPrice);
console.log(this.$parent.hello());
}
},
template: ''
})
app.mount('#app')
script>
body>
html>
parent 属性只能用于访问父组件实例,如果父组件之上还有父组件,那么该组件是访问不到的。
父组件要访问子组件实例或子元素,可以给子组件或子元素添加一个特殊的属性 ref,为子组件或子元素分配一个引用 ID ,然后父组件就可以通过 refs 属性访问子组件实例或子元素。
如下:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<div id="app">
<parent>parent>
div>
<script src="https://unpkg.com/vue@next">script>
<script>
const app = Vue.createApp({});
app.component('parent', {
mounted() {
// 访问子元素,让其具有焦点
this.$refs.inputElement.focus();
// 访问子组件的message数据属性
console.log(this.$refs.childComponent.message)
},
template: `
`
})
app.component('child', {
data() {
return {
message: 'Java无难事'
}
},
template: '{{message}}
'
})
app.mount('#app');
script>
body>
html>
需要注意的是,refs属性只在组件渲染完成之后生效,并且它们不是响应式的。要避免在模板和计算属性中访问 refs。
root 属性用于访问根实例,parent 属性用于访问父组件实例,但如果组件嵌套的层级不确定,某个组件的数据或方法需要被后代组件所访问,又该如何实现?
此时需要用到两个新的实例选项:provide 和 inject 。
provide 选项允许指定要提供给后代组件的数据或方法,在后代组件中使用 inject 选项接收要添加到该实例中的特定属性。
如下:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title>
head>
<body>
<div id="app">
<parent>parent>
div>
<script src="https://unpkg.com/vue@next">script>
<script>
const app = Vue.createApp({});
app.component('parent', {
data() {
return {
msg: 'Java无难事'
}
},
methods: {
sayHello(name) {
console.log("Hello, " + name);
}
},
provide() {
return {
// 数据message和sayHello方法可供后代组件访问
message: this.msg,
hello: this.sayHello
}
},
template: ' ',
})
app.component('child', {
// 接收message数据属性和hello方法
inject: ['message', 'hello'],
mounted() {
// 当自身的方法来访问
this.hello('zhangsan');
},
// 当自身的数据属性来访问
template: '{{message}}
'
})
const vm = app.mount('#app')
script>
body>
html>
使用 provide 和 inject ,父组件不需要知道哪些后代组件要使用他提供的属性,后代组件不需要知道被注入的属性来自哪里。
不过上述代码也存在一些问题。首先,注入的 message 属性并不是响应式的,当修改父组件的 msg 数据属性时,message 属性并不会跟着改变。
这是因为默认情况下,provide/inject 绑定的并不是响应式的,可以通过传递 ref属性或 reactive 对象更改这个行为。
其次,provide 和 inject 将应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
如果数据需要在多个组件中访问,并且能够响应更新,可以考虑真正的状态管理解决方案-Vuex。