Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 492|回复: 0

[转]Vue中用props给data赋初始值遇到的问题解决

[复制链接]
  • TA的每日心情
    奋斗
    6 天前
  • 签到天数: 795 天

    [LV.10]以坛为家III

    2050

    主题

    2108

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    724084
    发表于 2021-4-23 01:18:00 | 显示全部楼层 |阅读模式

    原文地址:https://segmentfault.com/a/1190000017149162

    2018-11-28更:文章发布后因为存在理解错误,经@Kim09AI同学提醒后做了调整,在此深表感谢。其他不足之处,还望不吝赐教。

    前言

    前段时间做一个运营活动的项目,上线后产品反馈页面埋点不对,在排查过程中发现,问题竟然是由于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 = '于永雨'; this.user.gender = '男'; this.user.birthday = '1991-7'; }, 500) } }, components: { userInfo } }); </script> </body> </html>

    代码解读:

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

    结果:

    clipboard.png

    页面初始化后,姓名、性别、生日都显示为空,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 = '于永雨'; this.user.gender = '男'; this.user.birthday = '1991-7'; }, 500) } }, components: { userInfo } }); </script> </body> </html>

    运行结果:

    clipboard.png

    完美!!!

    如果我们不想把user.name改为Object类型,有没有其他的解决办法呢?

    既然基本数据类型赋值没法实现值同步,那我们可以考虑监听props中的值,然后手动变更局部变量。基于此,我们很自然的就想到Vue中有监听作用的两个功能:watch、computed

    为了缩减篇幅,我们此处只贴出userInfo组件,其他代码与第一个示例一致,具体如下:

    方法一:watch

    let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, watch: { 'userData.name': function (val) { //监听props中的属性 this.userName = val; } }, template: ` <div> <div>姓名:{{ userName }}</div> <div>性别:{{ userData.gender }}</div> <div>生日:{{ userData.birthday }}</div> </div> ` });

    方法二:computed

    let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, computed: { computedUserName(){ return this.userData.name } }, template: ` <div> <div>姓名:{{ computedUserName }}</div> <div>性别:{{ userData.gender }}</div> <div>生日:{{ userData.birthday }}</div> </div> ` });

    经验证,结果符合皆预期!

    三、走过的弯路

    第一条弯路

    详见评论区@Kim09AI同学的评论。

    第二条弯路

    其实,曾以为导致文章开头的问题,是由于data在初始化后深拷贝,props再次变化data并不会刷新导致的。

    直到文章发布之初,仍然持此观点,后来经@Kim09AI同学提醒才恍然大悟。

    当初之所以深信是data被深拷贝导致的,主要是自己在翻到Vue官方文档看到关于data的描述:

    clipboard.png

    看到"递归地”那个词,就想当然地认为data被深拷贝了,因为深拷贝的核心原理就是递归。

    其实现在再回过头来看那段描述,包括在Reactivity in Depth一章的描述:

    clipboard.png

    它们真正含义是:Vue会递归地遍历data所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter,让data中的属性更具“交互性”,以此作为实现双向绑定的基础。包括还顺便解释了一下为什么Vue不支持IE8的原因:IE8不支持Object.defineProperty。

    这也仅仅解释了为什么只有在组件初始化之初data中已经声明的属性才具有“交互性”,即data中属性的变化会引起视图变化,而其他在最初data中没有声明的属性则不会。正如在The Vue Instance所说:

    clipboard.png

    小结一下:

    • 文章开头的问题是一个关于基本数据类型和引用数据类型赋值的问题
    • data在初始化时被递归遍历转化是用于实现双向绑定

    这么看来,二者是没有任何关系的。

    四、关于Vue中props的要点

    事后又仔细翻了一下关于props的文档:

    clipboard.png

    大概梳理一下:
    1.props是单向数据流:父组件的数据变化,通过props实时反应在子组件中,反之不然

    2.不允许在子组件中直接操作props

    3.可以变相操作props
    (1)在data中声明局部变量,并用props初始化
    (2)在computed中对props值转换后输出

    五、一点反思

    分享是一种知识的传递,严谨和正确是最重要的,技术文章更是如此。想当然和不加深究实为大忌,引以为戒。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-28 13:36 , Processed in 0.061131 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表