Vue中用props给data赋初始值遇到的问题

37次阅读

共计 3519 个字符,预计需要花费 9 分钟才能阅读完成。

前言
前段时间做一个运营活动的项目,上线后产品反馈页面埋点不对,在排查过程中发现,问题由于 Vue 中的 data 初始值导致,而 data 的初始值来自于 props。为方便描述,现将问题抽象如下:
一、现象
代码:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title> 用 props 初始化 data 中变量 </title>
<script src=”https://cdn.bootcss.com/vue/2.5.16/vue.min.js”></script>
</head>
<body>
<div id=”app”>
<user-info :user-data=”user”></user-info>
</div>
<script>
// 全局组件
let userInfo = Vue.component(‘userInfo’ ,{
name: ‘user-info’,
props: {
userData: Object
},
data() {
return {
userName: this.userData.name
}
},
template: `
<div>
<div> 姓名:{{userName}}</div>
<div> 性别:{{userData.gender}}</div>
<div> 生日:{{userData.birthday}}</div>
</div>
`
});

//Vue 实例
new Vue({
el: ‘#app’,
data: {
user: {
name: ”,
gender: ”,
birthday: ”
}
},
created(){
this.getUserData();
},
methods:{
getUserData(){
setTimeout(()=>{
this.user = {
name: ‘ 于永雨 ’,
gender: ‘ 男 ’,
birthday: ‘1991-7’
}
}, 500)
}
},
components: {
userInfo
}
});
</script>
</body>
</html>
代码解读:

根组件 data 中有一个对象:user,包含三个属性:name、gender、birthday,初始值都为空字符串
模拟 api 异步请求,500 毫秒后对 user 的重新赋值,三个属性都不再为空
声明一个子组件 userInfo,props 中有一个对象 userData,用于接收父组件的 user;data 中有一个变量 userName,初始值来自于 userData.name

结果:

页面初始化后,姓名、性别、生日都显示为空,500 毫秒后性别和生日显示正常结果,仅姓名没有变化。
为什么会这样呢?
我最初的想法:user.name 是 String,属于基本数据类型,用它给子组件 data 中 userName 赋值,属于基本数据类型赋值,所以当父组件中 user.name 变化时,子组件中 userName 并不会随之变化。
是这样的吗?于是我决定将 user.name 改为对象,通过引用数据类型赋值,然后观察是否符合预期。代码如下:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title> 用 props 初始化 data 中变量 - 对象形式 </title>
<script src=”https://cdn.bootcss.com/vue/2.5.16/vue.min.js”></script>
</head>
<body>
<div id=”app”>
<user-info :user-data=”user”></user-info>
</div>
<script>
// 全局组件
let userInfo = Vue.component(‘userInfo’ ,{
name: ‘user-info’,
props: {
userData: Object
},
data() {
return {
userName: this.userData.name
}
},
template: `
<div>
<div> 姓名:{{userName.text}}</div>
<div> 性别:{{userData.gender}}</div>
<div> 生日:{{userData.birthday}}</div>
</div>
`
});

//Vue 实例
new Vue({
el: ‘#app’,
data: {
user: {
name: {text: ”},
gender: ”,
birthday: ”
}
},
created(){
this.getUserData();
},
methods:{
getUserData(){
setTimeout(()=>{
this.user = {
name: {text: ‘ 于永雨 ’},
gender: ‘ 男 ’,
birthday: ‘1991-7’
}
}, 500)
}
},
components: {
userInfo
}
});
</script>
</body>
</html>
运行结果:姓名仍然没有值,和第一次结果一样!!!
二、原因
那么,原因到底是什么呢?百思不得解,后来和小伙伴们讨论时,有人提出:会不会因为 data 在初始化时深拷贝?
我觉得这种解释比较靠谱,于是去收集证据,首先去 Vue 官网翻了一下关于 data 的文档,其中:

当看到 ” 递归地 ” 那个词,基本上就能断定上面的推论是正确的,因为深拷贝的核心原理就是递归。
原来,Vue 初始化时会递归地遍历 data 所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,用于实现双向绑定。官方文档在 Reactivity in Depth 一章明确有说:

还顺便解释了一下为什么 Vue 不支持 IE8 的原因:IE8 不支持 Object.defineProperty。
三、解决办法
既然因为 data 深拷贝的原因,data 无法随着 props 的变化而更新,我们很自然的就想到 Vue 中有监听作用的两个功能:watch、computed。修改代码如下,观察结果:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title> 解决方案:watch、computed</title>
<script src=”https://cdn.bootcss.com/vue/2.5.16/vue.min.js”></script>
</head>
<body>
<div id=”app”>
<user-info :user-data=”user”></user-info>
</div>
<script>
// 全局组件
let userInfo = Vue.component(‘userInfo’ ,{
name: ‘user-info’,
props: {
userData: Object
},
data() {
return {
userName: this.userData.name
}
},
computed: {
computedUserName(){
return this.userData.name
}
},
watch: {
‘userData.name’: function (val) {// 监听 props 中的属性
this.userName = val;
}
},
template: `
<div>
<div> 姓名 (watch):{{userName}}</div>
<div> 姓名 (computed):{{computedUserName}}</div>
<div> 性别:{{userData.gender}}</div>
<div> 生日:{{userData.birthday}}</div>
</div>
`
});

//Vue 实例
new Vue({
el: ‘#app’,
data: {
user: {
name: ”,
gender: ”,
birthday: ”
}
},
created(){
this.getUserData();
},
methods:{
getUserData(){
setTimeout(()=>{
this.user = {
name: ‘ 于永雨 ’,
gender: ‘ 男 ’,
birthday: ‘1991-7’
}
}, 500)
}
},
components: {
userInfo
}
});
</script>
</body>
</html>
运行结果

完美!!!
四、总结:关于 Vue 中 props 的要点
事后又仔细翻了一下关于 props 的文档:

大概梳理一下:
1.props 是单向数据流:父组件的数据变化,通过 props 实时反应在子组件中,反之不然
2. 不允许在子组件中直接操作 props
3. 可以变相操作 props
(1)在 data 中声明局部变量,并用 props 初始化,弊端:局部变量不随着 props 更新而更新

(2)在 computed 中对 props 值转换后输出

正文完
 0