元件里的 data 必须是函式是为了确保元件里的资料不会被别的元件资料所污染。如果 data 是物件,因为 JavaScript 的物件是传址,一旦有元件的资料被修改,别的元件的资料也会被修改。因此,需要用函式,回传一个新物件的做法,确保自己元件的资料自己改,不会被污染。
另外,如果使用箭头函式建立 data,只要 data 物件里没有用到 this
,就没有问题。因为这里的 this
不会指向 Vue ,而是 Window 物件,因此如果你打算使用 this
来取得 Vue 里的资料的话就会出错。
以下会作出详细解说。
平常我们习惯在元件建立 data 时,使用 function return 的方式,假设有一个名为 <Example />
的元件,里面有以下资料:
Example.vue
data(){
return {
foo: 1
}
}
但以下的写法就会报错:
Example.vue
data: {
foo: 1
}
Vue 规定需要使用函式回传一个新物件是为了避免元件资料互相污染。因为 JavaScript 物件是传址(pass by reference),因此,当 Example
这元件被重复在多个地方使用时,一旦其中一个元件的资料被修改,其他元件的资料也会一并被修改掉。例如我有 4 个 Example
元件,只要其中一个 Example
元件的资料被修改,其他 Example
元件也会受影响:
<Example />
<Example />
<Example />
<Example />
这个示范模拟了共用同一个物件作为元件资料的情况。
官方文件这里也有相关的示范例子。
注意: 从 Vue 3 开始,不论是根元件或子元件,都必须使用 function return,否则会报错。而在 Vue 2 则容许在根元件直接使用物件,但子元件仍然必须使用 function return。
在建立 data 资料时,data 里面如果没有用到 this,就能放心使用箭头函式。原因是箭头函式的 this 会指向 Window 物件,不是 Vue 物件。
这个例子示范了以上提到的情况。
使用箭头函式:
const app = Vue.createApp({})
app.component('Message', {
template: `
<p> 目前 this 指向的物件:{{ thisObj }} </p>
<p> 结果:{{ str }} </p>
`,
props: {
msg: {
type: String
}
},
data: () => ({
str: this.msg,
thisObj: this // Window 物件
})
})
app.mount('#app')
结果:
结果没显示到 str
,但使用 Vue 检查工具时会发现,str 是 undefined:
因为 Window
不会有 str 属性,因此是 undefined
。
使用传统函式看看:
data() {
return {
str: this.msg,
thisObj: JSON.stringify(this) // Vue 的 data 物件
}
}
这里使用 JSON.stringify
把 Vue 的物件显示出来,否则会报错。
结果:
查看 Vue 检查工具:
因此,使用箭头函式是没问题。但如果 data 里有用到 this
,就会出错。因为 this
会指向 Window 物件,而非 Vue 的物件,因此无法正确取到在 Vue 所建立的资料。
在复习此题目时,想起能不能在 data 的资料里,使用 this
来取得 computed
里的资料。虽然这个做法没必要,因为 computed
里的资料本身是函式,它会回传一个值,所以平常我们只需要直接取 computed
的值来用即可。像是这样:
<p> {{ addSomeText }} </p>
computed: {
addSomeText() {
return 'Add some text'
}
}
结果画面就会显示 "Add some text"。
虽然没必要在 data 取得 computed 的资料,但还是想试试,如果在 data 里取得 computed
里的值会怎样:
<div id="app">
<Message job="Web developer" />
</div>
const app = Vue.createApp({})
app.component('Message', {
template:
`<p> {{ str }} </p>`
,
props: {
job: {
type: String
}
},
data(){
return{
str: this.addSomeText,
name: 'Alysa'
}
},
computed: {
addSomeText() {
return 'Add some text'
}
}
})
app.mount('#app')
结果 str
是 undefined
。即使是使用箭头函式还是这里用到的传统函式,str
的值都一样是 undefined
:
原因不在於 data 使用箭头函式与否,而是生命周期的问题。因为 Vue 会先建立 data 资料,之後才建立 computed
的资料,因此在 data 里无法取到 addSomeText
的值,因为 addSomeText
在 data 建立时是 undefined
。
试试以下例子,就会发现 str 能成功取到值:
HTML:
<div id="app">
<Message job="Web developer" />
</div>
JavaScript:
const app = Vue.createApp({})
app.component('Message', {
//在画面呼叫 str 函式
template:
`<p> {{ str() }} </p>`
,
props: {
job: {
type: String
}
},
data(){
return {
// 把 str 改为函式,回传 this.addSomeText
str() {
return this.addSomeText
},
name: 'Alysa'
}
},
computed: {
addSomeText() {
return 'Add some text'
}
}
})
app.mount('#app')
这是因为我们在 template
里呼叫 str
函式,意思就是当整个画面都被渲染好後,才会呼叫 str
,这时候就一定能取到 computed
里的资料。
题外话,在这情况下,我们用箭头函式建立 data 也会成功取到值,但 str 必须要是传统函式:
JavaScript:
data: () => ({
str() {
return this.addSomeText
},
// 以下会回传 undefined
// str: () => this.addSomeText,
name: 'Alysa'
}),
原因是我们在画面中,在 {{ }}
里呼叫 str
。这两个大括号是指向 Vue 实体物件里的状态,所以事实上我们是透过 Vue 物件来呼叫 str
,因此 str
里的 this
会指向 Vue。
关於 this
的运作,此文章的最後部分会再简单重温一遍。
https://codepen.io/alysachan/pen/jOwZRNa?editors=1011
写 Vue 时我们都习惯使用 this
就能取得在 Vue 的资料,包括 data
、computed
以及呼叫在 methods
建立的方法等等。
HTML 的部分:
<div id="app">
<User job="Web developer" />
</div>
Vue 的部分:
const app = Vue.createApp({})
app.component('User', {
props: {
job: {
type: String
}
},
template: `<p> {{ fullName }} </p>`,
data: () => ({
firstName: 'Alysa',
lastName: 'Chan'
}),
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
},
methods: {
greeting(){
console.log(`Hi I'm ${this.fullName}`)
}
},
mounted() {
console.log(this) // Proxy 物件
this.greeting() // Hi I'm Alysa Chan
}
})
app.mount('#app')
这时候查看 console 会发现一个由 Vue 包装好的 Proxy 物件:
由此可见,Vue 会把所有建立的资料和函式全都放在同一个物件上,并且成为 Proxy 代理的 target。在以上例子中,我在 methods
里使用 this.fullName
来引用在 computed
里的 fullName
。该 this
会指向这个 target 物件里的 fullName
。
因此,以上情况就等於以下的写法:
const obj = {
firstName: 'Chan',
lastName: 'Alysa',
fullName() {
return `${this.lastName} ${this.firstName}`
},
greeting(){
console.log(`Hi I'm ${this.fullName()}`)
}
}
obj.greeting() // Hi I'm Alysa Chan
补充一点,Vue 是使用 Proxy 来实现响应式更新(Reactivity)。我在前几天的文章有讨论过,有兴趣的话欢迎看看。
这里用以上例子,稍为重温 this
的概念,如果把 fullName
改为箭头函式,会出现什麽结果?
const obj = {
firstName: 'Chan',
lastName: 'Alysa',
fullName: () =>`${this.lastName} ${this.firstName}`,
greeting(){
console.log(`Hi I'm ${this.fullName()}`)
}
}
obj.greeting()
结果 console
会出现:Hi I'm undefined undefined
在回答这问题之前,要先知道几个核心概念:
this
所指向的值。在全域时,就会指向 Window
物件。最後两点可能比较难理解,但套用到题目里就更清晰了。
题目中,第一步是用 obj.greeting()
来呼叫 greeting
函式。
greeting
是使用传统函式。因此,在 greeting
里的 this.fullName
,这个 this
会指向 obj 这物件。上面提过,传统函式里 this 的值,会指向你呼叫此函式时,所引用的物件。在这里,我是用 obj
来呼叫 greeting
,所以 greeting
里的 this
会指向 obj
。
第二步,就是在 greeting
里呼叫 this.fullName()
,因为之前提到,这里的 this
是指向 obj,所以意思就是 obj.fullName()
,换言之,即是呼叫在 obj
里的 fullName
函式。
但是,fullName
是使用箭头函式。 虽然使用 obj.fullName()
来呼叫fullName
,但在 fullName
里的 this
并不会指向 obj
。箭头函式里的 this 会往上一层找,看看有没有函式,以及这个函式所指向的 this
是什麽。 但目前 fullName
再往上层找,只有 obj
这物件,并没有函式。直至找到全域,并指向 Window
物件。因为 Window
不会有 lastName
和 firstName
,所以结果就是 undefined
。
补充一点,以上提到箭头函式里的 this
需要往上层找函式,更准确的说法是,需要往上层找作用域,而函式会建立一个作用域,但物件不能。因此,在 greeting
里的 this
往上一层找是 obj
,但 obj
不会建立一个作用域,最後导致找到全域,并指向 Window
物件。
去年我的铁人赛系列,JavaScript 基本功修炼,有关於箭头函式和 this 的文章,有兴趣的话也欢迎再参考看看。
this
,就可以使用箭头函式来建立 data。否则,会因为this
指向 Window 物件,而非 Vue 物件而造成取值时出错。重新认识 Vue.js - 1-2 Vue.js 的核心: 实体
重新认识 Vue.js - 2-1 元件系统的特性
Vue JS: Difference of data() { return {} } vs data:() => ({ })
Use computed property in data in Vuejs
>>: EP06 - 从零开始,在 AWS 上建置 Jenkins 使用 Terraform
前言 最近因为上班进度缓慢,所以内容比较慢,但应该也只能这样了哈哈,我们今天也一样会用到 Oncli...
延续昨日 我们今天来完善功能测试 首先设一个runtest的function async runte...
制作表单 createRadio(); 沿用上一个文章的参考 加上以下设定 我们可以用radio去做...
今天是要来填之前未补之坑, 那就是建立 VPN 连线, 以小公司来说, 其实能够快速加快产品上市比较...
兔大夫: 「请问是兔豪的家属吗?」 兔豪爸: 「是,我就是。 请问我鹅子他...」 兔大夫: 「抱...