記vue父子組件傳值報錯的一次事件

上週五(3.27)做公司項目時,遇到一個有意思的問題,雖然解決很好解決,但是今天上午(3.30)才把整個流程梳理通暢,所以記錄下來,應該是個有意思的知識點(PS:其實不是我自己解決的,全程是我請教我們組的另一位大佬幫我答惑解疑的,畢竟,我是個渣渣…我又沒看過源碼也看不懂~)。

問題來源於我在created生命週期中調用接口獲取數據然後賦值給data中某個空對象的某個屬性。這整個對象被傳給子組件使用。但是我發現值都不對勁。
大致是這樣的:
父組件: 父組件
父組件值定義:
父組件值定義
子組件:
子組件使用值
子組件使用值
子組件使用值

父組件的created生命週期:
父組件賦值

出來的結果是除了todoData.unread有數據之外,其他的全爲0。
於是我就想不通了,當然,本着能用就行/數據出來就好的原則,我請教了我們組的前端大佬!

大佬給出幾種解決方案:

	1. 在data中定義數據的時候,就把幾個屬性一起聲明瞭;
	2. 我賦值的時候是直接使用“**=**”賦值的,大佬建議可以使用**$set**賦值;

試了一下,果然有效!本來想就這樣不管了的,實在不知道該劃什麼水了+心裏存了那麼一丟丟好奇心,最重要的本人的求知慾是很強的(手動假笑臉),我開啓了“十萬個爲什麼”模式!

“爲什麼???(爲什麼總是我寫什麼都要錯,而大佬寫的感覺和我寫的差不多,爲什麼就大佬的對我不對呢?我!不!!服!!!)”

下面記錄和大佬的對話過程,後面會總結一下自己的理解

然後就跟大佬探討起來(全程舔着臉請教。其實是經過幾輪錯誤探討的,前幾輪都有我認爲有不對勁的地方,然後大佬去看源碼又找問題,我提問題 – 大佬看源碼解決問題…,一直到我覺得這個解釋天衣無縫了,可以了,我沒有疑問了,上午纔算把這個事情解決完,大佬應該很煩我了吧,怎麼找了這麼個喜歡找茬的)

	我:數據通過接口拿到放進一個對象中    在頁面上傳給TodoList子組件    子組件顯示數據
	我:我能想到的唯一可能就是請求是異步,需要時間。在此之前肯定是傳給了子組件,然後因爲只是更改了對象的值  導致數據沒有同步更新   但是。。。。。這也不對啊  對象的值更改了子組件的值是會更改的啊
	
	大佬:父組件沒有觸發視圖更新,函數式組件就不會更新
	
	我:如何才能達到觸發試圖更新的條件
	
	大佬:不用 functional 就行了
	
	我:不用也不行啊!我就是從不用functional轉成使用的(各位親,functional我都不怎麼了解,也是看到這位大佬用我纔跟着用的,但是想知道用functional的話可以用methods嗎?我怎麼都使用不了methods)
	
	大佬:父組件 todoData 在哪聲明的
	
	我:data,賦值在created
	
	大佬:裏面的三個屬性要寫上(意思是data中定義todoData的時候就要聲明三個屬性了)
	
	我:....(嘗試之後)....emmm........原理是什麼?
	
	大佬給發了一篇vue官網的文章來(深入響應式原理那裏,說實話,爲什麼每次我在官網上什麼都找不到,而大佬總是給我驚喜?)
	
	我:(看了之後)那  按照我的理解   我使用$nextTick不是應該也沒問題嗎?
	我:還是用nextTick只是數據更新了   視圖層並不會更新?
	(我這裏理解不正確,囫圇吞棗,大家別學我)
	
	大佬:nextTick 類似於 settimeout ,和更新沒有任何關係
	大佬:歸根結底在於 Object.defineProperty 的侷限性,
	大佬:只有賦值才能觸發,而不是 聲明,
	
	我:我之前在created裏面也有賦值啊    難道瀏覽器認爲我是在聲明屬性值?就因爲第一次出現?
	
	大佬:在 data() 裏面只聲明 a,沒聲明a.b,直接 a.b = ** , 對應 b 屬性來說就是聲明
	大佬:不是賦值
	
	我:對象值修改不是響應式的嗎  Object.defineProperty是監聽了data中的a爲空數組   但是我添加a.b的時候不相當於也是觸發了a的getter/setter?
	我:況且就算是你說的那樣那也不對勁啊    我其他的有問題   但是未讀消息一樣是首次聲明    未讀消息條數沒問題!
	(此處自己好像繞進去了,自個人明明知道對象修改是響應式的,但是我記得在vue中修改對象好像還真不會觸發視圖層更新,頁面不會重新渲染)
	
	大佬:![大佬發了倆圖給我](https://img-blog.csdnimg.cn/20200330172329313.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mjk3MTkzMw==,size_16,color_FFFFFF,t_70)
	大佬:![大佬發了倆圖給我](https://img-blog.csdnimg.cn/20200330172347792.png)
	
	我:(無話可說,想想又覺得好像是那麼個道理,又開始找不同)那我的unread是如何正確的?
	
	大佬:unread 在 created 中同步賦值,不像另外兩個是異步延遲了,vue 在 created 鉤子執行後,還會做一次檢測
	
	我:就是像普通的聲明某個屬性可以在data中也可以在created中一樣是吧
	
	大佬:(放大招了)詳細情況看源碼
	
	我:然後   用$set改變值也是OK的   所以就算沒有聲明某屬性   也會在$set中聲明嗎
	我:[假笑臉]我   我這個級別還看不懂源碼
	
	大佬:$set 就是給這種情況做的補充,內部再重寫 set 方法
	
	我:好的  雖然不懂是怎麼實現的   但是好歹也知道了個大概

至此,第一回合結束,雖然我有點蒙圈,畢竟我在created中也是賦值了的,怎麼就第一次聲明就不管用了!我在created中直接都可以定義可以直接使用的data中的某個屬性,雖然不懂,但是大佬說的就算不是真的,我也找不到其他人問了。

過了一個多小時,大佬又發來一段,這時候我在做其他的,沒有這個了,想週末回家重新梳理思緒。
第二階段的討論又展開:

	大佬:剛纔說錯了,unread 是在 created 中同步賦值,渲染真實成 dom 是在 mounted 時,那時可以拿到 unread 的值,相反,另兩個屬性,渲染真實成 dom 的時候還不存在,所以頁面上顯示不出來。但是,這三個屬性依舊是不具有可響應的,後續修改無法觸發更新
	
	我:即使使用$set也不能?
	
	大佬:set 就是讓其變成可響應的,
	大佬:但不建議使用,這只是補救措施
	大佬:害我翻了好久的源碼
	(此處嚴重懷疑大佬在裝biu~)
	
	我:爲什麼不建議使用
	我:我也是在消化你說的   開始還走偏了想偏了   我還在想怎麼組織成我能理解的樣子  看來要一會兒了
	
	大佬:使用 set 是命令式編程,
	大佬:可以使用聲明式就拒絕使用命令式

我又敗下陣來,被說服了,討論了一些其他問題,討論完了,過了幾分鐘,我覺得我又發現什麼不對勁了,又開始了battle,踢了個小問題:

	不對啊   響應式數據改變都不會觸發視圖更新的話    那我在data裏面聲明瞭屬性,他也還是個響應式數據啊   爲什麼再更改又行

可惜,大佬沒回答我,這期間我一直嘗試將整個過程自己寫出來便於理解,也相當於把過程走一遍,可惜沒走通。一晃就下班還放假了,再一晃就週一了,是的週末我沒找他battle,畢竟我週末牀都不可能起。

今天,週一,一大早就開始找他繼續話題了。第三輪爭論又開始了!這次我應該是通了吧:

	我:上週的爭論還沒結束勒
	我:你說“這三個屬性依舊是不具有可響應的,後續修改無法觸發更新。”這一句有疑問
	我:直接賦值不行是吧     我嘗試在點擊按鈕後直接修改它的值   是可以修改的又
	
	大佬:很大可能是其他值引起的刷新
	
	我:等一下  我給你上代碼
	
	....中間省略無數代碼以及文件直接發過去(大佬在老家,疫情交通管制無法到現成,否則我肯定會被忽的一愣一愣的,哪裏來的腦子想他的話哪裏有問題)
	
	大佬翻了翻源碼(是的,就是這麼任性,一言不合翻源碼)
	
	大佬:  子組件的props 自動轉成響應式對象,而且是深層的,裏面所有的屬性都會轉換(還附帶截圖,完全看不懂那種)
	
	我:就是不管子組件接收的是什麼值  是對象屬性值或者是普通字符等   都是響應式的
	
	大佬:對於子組件來說是這樣,
	
	我(來勁了,因爲問題饒了一圈又回到最初了):那問題就來了
	我:那這樣的話   最開始雖然已經渲染過了   但是我created請求發送之後無論如何更改了數據啊    值應該是相對應的啊
	我:難道是當時渲染   傳了對象過去子組件   子組件接收對象後沒有屬性   又在子組件給創建了新的與父組件同名的屬性    所以父組件更改就不行了?
	我:就是最初的問題了
	
	大佬表示不知所云,我都快急瘋了,表達能力這麼差嗎?
	
	我:我在created裏面修改todoData的屬性   同時創建屬性和賦值   
	我:爲什麼值又都是0
	我:因爲不是都是響應式的數據嗎    那就算created生命週期完結了修改了值也能修改啊
	
	直接上文件給大佬了
	
	我重申了我的疑問:todoData中的unread屬性是我直接用$store賦值的,這個的值就沒問題。其他的在初始聲明data時不聲明屬性的話就不行,so,why?
	
	大佬:子組件 breforeCre\created 晚於父組件,子組件生成的時候發現只有 unread 屬性,也就只有 unread  被轉換了,
	
	我:意思就是   我在子組件中使用的todoData.unlast和todoData.unpay屬性和父組件後面傳過來的是不一樣的?
	
	大佬:一樣,但是沒有被轉換了
	
	我:這個轉換怎麼理解
	
	大佬:就加上了 get/set 

至此,爭論結束,我的“十萬個爲什麼”也問完了。

以上,我只是將我和大佬的對話記錄下來,方便以後再看。大家可能看不懂。我自己會整個梳理一下流程,畢竟要將複製粘貼的東西變成自己的,覺得這個問題蠻有趣的。

接下來是我自己的梳理過程

接下來由囉嗦的自己把過程重新過一遍,重新梳理一遍(當然,全部是我的自認爲!)。

	首先,我在data中聲明空對象todoData:{},在created生命週期中賦值this.todoData.unread=this.$store.state.user.msg並調用接口在.then中賦值this.todoData.unpay=xxx;this.todoData.unlast=xxx;
	整個todoData被我傳給子組件,子組件中直接顯示(props.todoData.unread,...)

理解是這樣的:

	我在data中聲明瞭todoData但沒有聲明其中的屬性(準備在created中直接聲明兼賦值一起了),但是在created中賦值的時候它是個異步!是個異步!
	除了unread是同步賦值之外,其他的都暫時被跳過了。created同步執行到了mounted完成頁面渲染後給人家把數據已經傳過去了。相當於現在的情況是,傳過去的值:
	todoData:{ unread: xxx }
	只有個這個玩意兒!所以在父組件聲明其他屬性之前子組件已經提前把屬性給聲明好了只是沒有值而已。之前沒發現是由於我寫了 || 0把值給默認成0了。
	
	這樣聲明的屬性,雖然父組件和子組件的屬性是一致的,但是屬性沒有被轉換爲props屬性
	(父子組件傳的值--也就是通過props傳的值,都可以深度修改。我理解的是在頁面初次渲染-的時候子組件給每一個props裏面的屬性以及對象的屬性等都重寫了一個getter/setter方法,所以值可以深度修改,但在本次案例中,首次渲染,傳過去子組件的對象中只有一個屬性是被重寫了setter/getter的。)
	
	所以我再在父組件這邊修改todoData.unpay和todoData.unlast的值,人家都不理我了,誰讓我上車晚了,雖然座位是我的,但是我不在車上,也不能在車上到處蹦躂了。。。。
	
	我也測試過在created的異步請求完成之後聲明一個普通的屬性(非對象,因爲對象直接點操作一個不存在的屬性是不會報錯的),結果果然在頁面渲染時就報錯了,貌似錯誤信息是傳的那個屬性is not define。
	
	以上,證明大佬說的流程還是沒錯的。我也想不出來哪裏還有問題了
	
	至於響應式數據本來修改就不會觸發頁面更新的問題,大佬說了,在props裏面會,具體原理是啥我也不知道,咱也不敢問,問就是看源碼。

羅裏吧嗦了大半天,寫完這篇文章都已經不是文中的“今天”了。所以,我其實還是有那麼一丟丟不知所云。大佬教正確了,我消化了那就是我的了。雖然可能哪天回來看會覺得這是什麼垃圾看不懂,哈哈哈哈,希望你們別被我帶偏,也別看不懂了。
感覺挺簡單一過程,就是被我妖魔化,然後囉嗦了一堆。
話說,這好像也暴露了我沒理解到vue生命週期具體執行順序的問題。。。。。

至於最後,我還是用了在data中事先聲明對象的每一個屬性的方式解決。畢竟大佬讓我能儘量用聲明解決的問題就不要用$set解決。而且也省事啊。

===================================
以上的哪個結論如果哪裏有什麼問題,哪怕是無關此次討論主題的一個小問題,歡迎各位大佬指出,是錯誤就要改正,改正了之後纔會變成一個新的自己。
歡迎探討!感謝各位大佬!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章