Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 先锋影音亚洲,性感美女一级片,久99久女女精品免费观看69堂

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          人間最后的告白-自書遺囑寫作規(guī)范及法律風(fēng)險(xiǎn)提示

          人間最后的告白-自書遺囑寫作規(guī)范及法律風(fēng)險(xiǎn)提示

          據(jù)國家統(tǒng)計(jì)局2021年發(fā)布的第七次全國人口普查的數(shù)據(jù)顯示,我國60歲及以上人口的比重達(dá)到18.7%,有2.6億人,老年人口規(guī)模龐大,我國已邁入嚴(yán)重的人口老齡化階段[1]。隨著老齡化程度加深,我國已面臨代際財(cái)富如何傳承的嚴(yán)峻問題。

          [1]羅知之、呂騫:國家統(tǒng)計(jì)局:60歲及以上人口比重達(dá)18.7% 老齡化進(jìn)程明顯加快,http://finance.people.com.cn/n1/2021/0511/c1004-32100026.html,最后訪問時(shí)間:2022年6月5日。

          自書遺囑的法律沿革

          遺囑是指人生前在法律允許的范圍內(nèi),按照法律規(guī)定的方式對其財(cái)產(chǎn)所作的個(gè)人處理,并于創(chuàng)立遺囑人死亡時(shí)發(fā)生效力的法律行為。

          自書遺囑,又稱親筆遺囑,是《中華人民共和國民法典》(以下簡稱“《民法典》”)規(guī)定的六種法定遺囑形式之一,是指遺囑人生前在法律允許的范圍內(nèi),按照法律規(guī)定的方式對其財(cái)產(chǎn)所作的個(gè)人處分,并于遺囑人死亡時(shí)發(fā)生效力的法律行為。

          1985年頒布的《中華人民共和國繼承法》(以下簡稱“《繼承法》”)(已失效)第十七條就規(guī)定了自書遺囑。

          《民法典》第一千一百三十四條也沿用了《繼承法》的規(guī)定,即自書遺囑由遺囑人親筆書寫,簽名,注明年、月、日。

          自書遺囑的寫作規(guī)范

          (一)形式要件

          《民法典》第一千一百三十四條規(guī)定,自書遺囑的形式要件包括須由遺囑人親筆書寫,簽名,注明年、月、日。這三個(gè)形式要件必須同時(shí)滿足,一般而言,遺囑如果非親筆書寫或者沒有簽名或者沒有日期是無效的。

          1.自書遺囑全部內(nèi)容均由遺囑人親筆書寫。

          2021年1月1日起《民法典》正式施行后,合法的遺囑形式包含:自書遺囑、代書遺囑、打印遺囑、錄音錄像遺囑、口頭遺囑、公證遺囑等6種法定遺囑形式。

          與《繼承法》相比,《民法典》新增了打印遺囑和錄像遺囑兩種形式,且這兩種形式均有兩個(gè)以上見證人在場見證等要求,如自書遺囑采用打印或錄像的形式,則很有可能會被歸為打印遺囑和錄像遺囑兩種形式,從而需要滿足兩個(gè)以上見證人在場見證等要求。因此,自書遺囑須全文由遺囑人親筆書寫,既不能由他人代筆,也不能用打印機(jī)打印。

          對于自書遺囑的內(nèi)容,應(yīng)當(dāng)清晰準(zhǔn)確,寫明遺囑人的身份、作出遺囑的時(shí)間地點(diǎn)、遺產(chǎn)具體信息、分配方案等。且遺囑內(nèi)容盡量不做修改,否則可能因此產(chǎn)生效力上的爭議。

          2.自書遺囑應(yīng)由遺囑人親筆簽名。

          遺囑人書寫完成遺囑內(nèi)容后,在自書遺囑末尾應(yīng)當(dāng)寫明自己的全名。第一,遺囑人應(yīng)親筆簽名,不能由他人代簽等;第二,所簽姓名為遺囑人身份證、戶口本上現(xiàn)在的姓名,非曾用名、藝名等;第三,如果遺囑為多頁,建議在每一頁簽字并捺手印

          3.自書遺囑應(yīng)由遺囑人注明年、月、日。

          自書遺囑應(yīng)當(dāng)注明年、月、日。立遺囑的時(shí)間是確定立遺囑人是否具有遺囑能力的依據(jù),關(guān)系到遺囑的成立時(shí)間。

          4.可以輔助全程錄像來佐證自書遺囑的真實(shí)性以及不存在其他導(dǎo)致遺囑無效的情形。錄像要保存好原始載體,不能隨意進(jìn)行剪輯。

          (二)實(shí)質(zhì)要件

          1.遺囑人設(shè)立遺囑時(shí)應(yīng)當(dāng)具有遺囑能力。

          設(shè)立遺囑是民事法律行為,因此遺囑人設(shè)立遺囑時(shí)必須有相應(yīng)的民事行為能力。根據(jù)《民法典》第一千一百四十三條的規(guī)定,只有完全民事行為能力人才具有遺囑能力,無民事行為能力人或者限制民事行為能力人所立的遺囑無效。

          2.遺囑必須是遺囑人的真實(shí)意思表示。

          自書遺囑是遺囑人生前的單方意思表示,無需他人的同意即可設(shè)立,故自書遺囑應(yīng)當(dāng)是遺囑人自己的真實(shí)意思表示,如果遺囑人受到脅迫、欺詐而設(shè)立遺囑的,則自書遺囑不具有法律效力。自書遺囑被偽造和篡改的,偽造的遺囑無效,被篡改的部分無效。

          根據(jù)《民法典》第一千一百四十三條的規(guī)定遺囑必須表示遺囑人的真實(shí)意思,受欺詐、脅迫所立的遺囑無效。偽造的遺囑無效。遺囑被篡改的,篡改的內(nèi)容無效

          3.自書遺囑的內(nèi)容不得違反法律、行政法規(guī)的強(qiáng)制性規(guī)定。

          遺囑人作出自書遺囑屬于民事法律行為。根據(jù)《民法典》第一百五十三條違反法律、行政法規(guī)的強(qiáng)制性規(guī)定的民事法律行為無效。但是,該強(qiáng)制性規(guī)定不導(dǎo)致該民事法律行為無效的除外。違背公序良俗的民事法律行為無效。因此,自書遺囑也應(yīng)當(dāng)符合法律規(guī)定,不得違反法律、行政法規(guī)的強(qiáng)制性規(guī)定。

          類案解讀

          (一)形式要件

          1.自書遺囑應(yīng)由遺囑人親筆書寫,簽名,注明年、月、日。

          案例1:(2021)京民申4006號

          裁判觀點(diǎn):自書遺囑應(yīng)由遺囑人親筆書寫,簽名,注明年、月、日。2012年7月1日《議定書》為蘇某4親筆書寫并有簽名、日期,故該《議定書》為蘇某4的自書遺囑。

          案例2:(2021)京民申5622號

          裁判觀點(diǎn):蔡某2提供的蔡中河所立遺囑,經(jīng)筆跡鑒定系蔡中河本人書寫并簽名注明日期,形式上符合自書遺囑的法定要件,能夠反映立遺囑人的真實(shí)意思表示,故該份遺囑中涉及蔡中河的財(cái)產(chǎn)部分應(yīng)屬合法有效,涉及郜靜霞的財(cái)產(chǎn)部分無效。

          2.自書遺囑應(yīng)當(dāng)注明年、月、日,若有筆誤或日期不準(zhǔn)確等情況,可以結(jié)合其他證據(jù)進(jìn)行補(bǔ)正,如:訂立遺囑時(shí)簽訂的筆錄、錄像、證人證言等。

          案例3:(2021)京民申5445號

          裁判觀點(diǎn):本案爭議的焦點(diǎn)在于王炳宗所立遺囑的效力問題,王某4所提交的王炳宗親筆書寫并簽名的遺囑的落款處寫明了“立遺囑日期:203424”的字樣,因“203424”之前有“立遺囑日期”,并結(jié)合本案其他證據(jù)可以看出,該字樣是立遺囑人在書寫年份時(shí)存在筆誤。關(guān)于立遺囑的具體年份日期,結(jié)合律師見證談話筆錄王炳宗寫的日期以及訂立遺囑現(xiàn)場照片顯示的時(shí)間等證據(jù),確定王炳宗訂立遺囑的日期是2013年4月24日,“203424”是日期“2013424”在書寫時(shí)的筆誤。綜上,王炳宗于2013年4月24日訂立的遺囑合法有效。

          案例4:(2021)京民申4434號

          裁判觀點(diǎn):本院經(jīng)審查認(rèn)為,本案在再審審查階段的焦點(diǎn)系原某6遺囑效力的認(rèn)定問題。本案中,原某2提交的原某6自書遺囑為其本人書寫、簽名,但僅注明了年、月,未注明具體日期,在遺囑形式上存在瑕疵。但原某2提交了唐某手寫的《證明書》、唐某、劉某、高某等保管人的證人證言,形成了完整的證據(jù)鏈條,對形式上的瑕疵進(jìn)行了補(bǔ)正。二審法院根據(jù)遺囑要式的立法目的、本案的綜合情況,認(rèn)定原某6的遺囑有效,并無不當(dāng)。

          案例5:(2021)京民申1788號

          裁判觀點(diǎn):對于陳某2所持曹恩秀遺囑,該遺囑形式上屬于自書遺囑,雖未注明日期,但在錄像中,曹恩秀手持本案自書遺囑,錄像資料中對當(dāng)日時(shí)間亦有記載。

          3.自書遺囑的形式要件僅有三個(gè):遺囑應(yīng)由遺囑人親筆書寫,簽名,注明年、月、日。是否有見證人、是否全程錄像等均不影響自書遺囑的效力。

          案例6:(2021)京民申5445號

          裁判觀點(diǎn):王炳宗所訂立的遺囑是自書遺囑,全程錄像和見證人見證并非必要條件,且王某4對沒有全程錄像問題作了說明,是因?yàn)橥醣跁鴮戄^慢,書寫的過程沒有錄像,錄像中亦反映了見證人書寫全過程,王某4女朋友的父親在場并不影響自書遺囑的效力。

          (二)實(shí)質(zhì)要件

          1.結(jié)合自書遺囑以及其他證據(jù),形成證據(jù)鏈,共同證明遺囑系遺囑人的真實(shí)意思表示。

          案例7:(2021)京民申5080號

          裁判觀點(diǎn):本案中,韓某2提交了韓鴻林2018年7月11 日的自書遺囑,韓鴻林在該遺囑中將涉案房產(chǎn)中屬于韓鴻林的產(chǎn)權(quán)和部分繼承權(quán)由韓某2繼承,韓某2同時(shí)提交了兩份贈與書及現(xiàn)場照片、錄像等證據(jù)。一、二審法院根據(jù)查明的事實(shí)和在案證據(jù),結(jié)合雙方的訴辯主張及舉證情況,認(rèn)定審理中雖未對上述遺囑的真實(shí)性進(jìn)行鑒定,但韓某2提交的證據(jù)佐證了遺囑的真實(shí)性,可與其他證據(jù)形成證據(jù)鏈,共同證明該遺囑系韓鴻林的真實(shí)意思表示,進(jìn)而對韓鴻林的遺產(chǎn)按遺囑繼承處理并無不當(dāng),所作判決認(rèn)定事實(shí)清楚,適用法律正確。

          2.患有精神方面的疾病并不必然導(dǎo)致喪失行為能力,不能證明遺囑人不具有民事行為能力。

          案例8:(2019)京民申5115號

          裁判觀點(diǎn):萬蔚在去世前留有自書遺囑,對于李某、段某3提出萬蔚在去世前患有抑郁癥多年,寫出大段遺囑不合常理的抗辯意見。經(jīng)查,萬蔚雖然患有混合型焦慮和抑郁障礙,但患有精神方面的疾病并不必然導(dǎo)致喪失行為能力,且在萬蔚生前的醫(yī)院記錄中多次明確載明神清,精神可或神志清楚,查體合作等字樣,亦表明萬蔚在生前的精神狀態(tài)良好,具有完全的民事行為能力,因此李某、段某3的此項(xiàng)主張亦不能成立。

          (三)其他

          1.遺囑人以遺囑處分了屬于他人所有的財(cái)產(chǎn),遺囑的這部分,應(yīng)認(rèn)定無效。

          案例9:(2021)京民申6994號

          2.被繼承人生前與他人訂有遺贈扶養(yǎng)協(xié)議,同時(shí)又立有遺囑的,繼承開始后,如果遺贈扶養(yǎng)協(xié)議與遺囑沒有抵觸,遺產(chǎn)分別按協(xié)議和遺囑處理;如果有抵觸,按協(xié)議處理,與協(xié)議抵觸的遺囑全部或部分無效。

          案例10:(2021)京民申993號

          遺產(chǎn)管理人

          (一)相關(guān)概念及規(guī)定【首次引入《民法典》】

          在《民法典》“繼承編”第四章中,新增了遺產(chǎn)管理制度。遺產(chǎn)管理制度,是指在繼承開始后遺產(chǎn)交付前,有關(guān)主體依據(jù)法律規(guī)定或有關(guān)機(jī)關(guān)的指定,以維護(hù)遺產(chǎn)價(jià)值和遺產(chǎn)權(quán)利人合法利益為宗旨,對被繼承人的遺產(chǎn)實(shí)施管理、清算的制度。遺產(chǎn)管理人,則是對死者的財(cái)產(chǎn)進(jìn)行妥善保存和管理分配的人。[2]其功能在于確保遺產(chǎn)得到妥善管理、順利分割,更好地維護(hù)繼承人、債權(quán)人利益。[3]

          [2]最高人民法院民法典貫徹實(shí)施工作領(lǐng)導(dǎo)小組主編:《民法典婚姻家庭編繼承編理解與適用》,人民法院出版社2020年7月第1版。

          [3]全國人民代表大會常務(wù)委員會副委員長王晨在2020年5月22日第十三屆全國人民代表大會第三次會議上所做的報(bào)告:《關(guān)于〈中華人民共和國民法典(草案)〉的說明》。

          (二)產(chǎn)生、資質(zhì)及職責(zé)

          1.產(chǎn)生

          《民法典》第一千一百四十五條規(guī)定,繼承開始后,遺囑執(zhí)行人為遺產(chǎn)管理人;沒有遺囑執(zhí)行人的,繼承人應(yīng)當(dāng)及時(shí)推選遺產(chǎn)管理人;繼承人未推選的,由繼承人共同擔(dān)任遺產(chǎn)管理人;沒有繼承人或者繼承人均放棄繼承的,由被繼承人生前住所地的民政部門或者村民委員會擔(dān)任遺產(chǎn)管理人。

          第一千一百四十六條規(guī)定,對遺產(chǎn)管理人的確定有爭議的,利害關(guān)系人可以向人民法院申請指定遺產(chǎn)管理人。

          因此,遺產(chǎn)管理人的產(chǎn)生應(yīng)按照以下順序:

          ①由被繼承人指定遺囑執(zhí)行人;

          ②繼承人推選;

          ③繼承人共同擔(dān)任;

          ④被繼承人生前住所地的民政部門或者村民委員會擔(dān)任。

          2.資質(zhì)

          遺產(chǎn)的管理行為是一種民事法律行為,并且遺囑的執(zhí)行涉及相關(guān)利害關(guān)系人的利益,因此遺產(chǎn)管理人須具備相應(yīng)的民事行為能力。雖然現(xiàn)行法律沒有明確規(guī)定,但遺囑的執(zhí)行屬于重大、復(fù)雜民事行為,故遺產(chǎn)管理人應(yīng)具有完全民事行為能力。

          此外,實(shí)務(wù)中通常會選擇律師作為遺產(chǎn)管理人,其作為專業(yè)法律人士,具有天然的優(yōu)勢。第一,具有法律專業(yè)知識與豐富經(jīng)驗(yàn),可以有效厘清遺產(chǎn)管理過程中涉及的婚姻、物權(quán)、知識產(chǎn)權(quán)等多項(xiàng)法律關(guān)系,并妥善處理相關(guān)爭議;第二,律師不同于繼承人或其他利害關(guān)系人,其地位是中立的,有助于公平、公正、有序地管理、分割遺產(chǎn)。

          3.職責(zé)

          《民法典》第一千一百四十七條簡單規(guī)定了遺產(chǎn)管理人的六項(xiàng)職責(zé):

          (一)清理遺產(chǎn)并制作遺產(chǎn)清單;

          (二)向繼承人報(bào)告遺產(chǎn)情況;

          (三)采取必要措施防止遺產(chǎn)毀損、滅失;

          (四)處理被繼承人的債權(quán)債務(wù);

          (五)按照遺囑或者依照法律規(guī)定分割遺產(chǎn);

          (六)實(shí)施與管理遺產(chǎn)有關(guān)的其他必要行為。

          此外,如果遺產(chǎn)管理人因故意或者重大過失造成繼承人、受遺贈人、債權(quán)人損害的,應(yīng)當(dāng)承擔(dān)民事責(zé)任。

          (三)以案說法

          由于遺產(chǎn)管理人在遺產(chǎn)分配中起主導(dǎo)作用,其權(quán)利行使的恰當(dāng)與否,將極大程度地影響繼承人、債權(quán)人及受遺贈人三方的利益,以及遺產(chǎn)繼承過程的公正性與有效性,因此實(shí)務(wù)中一般被繼承人會選擇律師作為其遺產(chǎn)管理人,并簽訂委托合同以明確雙方權(quán)利義務(wù)。委托律師流程如下:

          1.立遺囑人向律師說明要求、家庭情況等;

          2.立遺囑人與律師簽訂委托協(xié)議,指定律師為遺囑執(zhí)行人;

          3.與律師詳細(xì)溝通遺囑內(nèi)容,選擇合適的遺囑設(shè)立形式并設(shè)立遺囑;

          經(jīng)典案例:

          李某和王某生育四個(gè)子女,兩人先后去世,未留遺囑,兩老人的子女、侄甥、好友等均向法院主張參與遺產(chǎn)分配,且各方遲遲無法達(dá)成一致意見。法院在案件審理過程中,充分考慮案情實(shí)際情況,經(jīng)各方當(dāng)事人推選,指定原告委托的律師成為該案遺產(chǎn)管理人,由遺產(chǎn)管理人對遺產(chǎn)進(jìn)行統(tǒng)一清理、查明,法院在此基礎(chǔ)上高效且合理地將包括房產(chǎn)、存款、醫(yī)療保險(xiǎn)、貴重物品在內(nèi)的所有遺產(chǎn)及債權(quán)債務(wù)進(jìn)行一一處理,成功化解各方矛盾。

          寫作模板

          附:遺囑執(zhí)行人權(quán)利

          除遺囑中另有特別規(guī)定外,遺囑執(zhí)行人可執(zhí)行下列事務(wù):

          (一)查明遺囑是否合法真實(shí);

          (二)清理遺產(chǎn);

          (三)管理遺產(chǎn);

          (四)訴訟代理;

          (五)召集全體遺囑繼承人和受遺贈人,公開遺囑內(nèi)容;

          (六)按照遺囑內(nèi)容將遺產(chǎn)最終轉(zhuǎn)移給遺囑繼承人和受遺贈人;

          (七)排除各種執(zhí)行遺囑的妨礙;

          (八)請求繼承人賠償因執(zhí)行遺囑受到的意外損害。

          相關(guān)法條

          《中華人民共和國民法典》

          第一百五十三條違反法律、行政法規(guī)的強(qiáng)制性規(guī)定的民事法律行為無效。但是,該強(qiáng)制性規(guī)定不導(dǎo)致該民事法律行為無效的除外。違背公序良俗的民事法律行為無效。

          第一千一百三十四條自書遺囑由遺囑人親筆書寫,簽名,注明年、月、日。

          第一千一百四十三條無民事行為能力人或者限制民事行為能力人所立的遺囑無效。遺囑必須表示遺囑人的真實(shí)意思,受欺詐、脅迫所立的遺囑無效。偽造的遺囑無效。遺囑被篡改的,篡改的內(nèi)容無效。

          第一千一百四十五條繼承開始后,遺囑執(zhí)行人為遺產(chǎn)管理人;沒有遺囑執(zhí)行人的,繼承人應(yīng)當(dāng)及時(shí)推選遺產(chǎn)管理人;繼承人未推選的,由繼承人共同擔(dān)任遺產(chǎn)管理人;沒有繼承人或者繼承人均放棄繼承的,由被繼承人生前住所地的民政部門或者村民委員會擔(dān)任遺產(chǎn)管理人。

          第一千一百四十六條對遺產(chǎn)管理人的確定有爭議的,利害關(guān)系人可以向人民法院申請指定遺產(chǎn)管理人。

          第一千一百四十七條遺產(chǎn)管理人應(yīng)當(dāng)履行下列職責(zé):

          (一)清理遺產(chǎn)并制作遺產(chǎn)清單;

          (二)向繼承人報(bào)告遺產(chǎn)情況;

          (三)采取必要措施防止遺產(chǎn)毀損、滅失;

          (四)處理被繼承人的債權(quán)債務(wù);

          (五)按照遺囑或者依照法律規(guī)定分割遺產(chǎn);

          (六)實(shí)施與管理遺產(chǎn)有關(guān)的其他必要行為。

          第一千一百四十八條遺產(chǎn)管理人應(yīng)當(dāng)依法履行職責(zé),因故意或者重大過失造成繼承人、受遺贈人、債權(quán)人損害的,應(yīng)當(dāng)承擔(dān)民事責(zé)任。

          第一千一百四十九條遺產(chǎn)管理人可以依照法律規(guī)定或者按照約定獲得報(bào)酬。

          最高人民法院關(guān)于適用《中華人民共和國民法典》繼承編的解釋

          (一)第二十七條自然人在遺書中涉及死后個(gè)人財(cái)產(chǎn)處分的內(nèi)容,確為死者的真實(shí)意思表示,有本人簽名并注明了年、月、日,又無相反證據(jù)的,可以按自書遺囑對待。

          北京市高級人民法院關(guān)于審理繼承糾紛案件若干疑難問題的解答(2018)

          17. 遺囑的形式要件認(rèn)定規(guī)則?

          未嚴(yán)格按照法律規(guī)定的形式要件作出的遺囑,人民法院應(yīng)認(rèn)定無效。

          簽署日期不全的自書遺囑應(yīng)為無效。以遺書形式處分遺產(chǎn)的,如該遺書具備法律規(guī)定的自書遺囑形式要件的,應(yīng)認(rèn)定有效。

          18. 打印遺囑的性質(zhì)與效力?

          繼承案件中當(dāng)事人以打印遺囑系被繼承人自己制作為由請求確認(rèn)打印遺囑為有效自書遺囑的,人民法院不予支持。但確有達(dá)到排除合理懷疑程度的證據(jù)表明打印遺囑由被繼承人全程制作完成,并具備自書遺囑形式要件的,可認(rèn)定為有效自書遺囑。

          打印遺囑由被繼承人以外的人制作的,應(yīng)符合法律規(guī)定的代書遺囑形式要件。

          參考文獻(xiàn)

          1. 胡政.論自書遺囑形式要件的緩和[D].蘇州大學(xué),

          2020.DOI:10.27351/d.cnki.gszhu.2020.001362.

          2. 張仕訓(xùn). 我國自書遺囑的效力研究[D].上海師范大學(xué),2019.

          3. 羅晨.民法典遺產(chǎn)管理人制度評析[J].現(xiàn)代交際,2021(23):251-253.

          4. 陳振安.遺產(chǎn)管理人的法定訴訟擔(dān)當(dāng)資格研究——以無人繼承情形為視角[J].浙江萬里學(xué)院學(xué)報(bào),

          2021,34(06):41-46.DOI:10.13777/j.cnki.issn1671-2250.2021.06.007.

          5. 李敏. 民法典視角下遺產(chǎn)管理人制度研究[D].安徽大學(xué),2021.

          6. 楊璐嘉,廖惠敏,葉鑫欣.遺產(chǎn)管理人制度建構(gòu)的“非訟法理”——以《民法典》繼承編為視角[J].法治論壇,2020(02):318-327.

          發(fā)編程

          • asyncio - (Python標(biāo)準(zhǔn)庫)異步I/O,事件循環(huán),協(xié)程和任務(wù)。
          • multiprocessing——(Python標(biāo)準(zhǔn)庫)基于進(jìn)程的并行性。

          身份驗(yàn)證

          OAuth

          • authlib - JavaScript對象簽名和加密草案實(shí)現(xiàn)。
          • Django-allauth - Django的認(rèn)證應(yīng)用程序,“只是工作”。

          JWT

          • pyjwt - Python中的JSON Web令牌實(shí)現(xiàn)。
          • Python-JOSE -一個(gè)Python中的JOSE實(shí)現(xiàn)。

          內(nèi)置類增強(qiáng)

          • dataclass——(Python標(biāo)準(zhǔn)庫)數(shù)據(jù)類。

          緩存

          • Django-cache-machine - Django模型的自動緩存和失效時(shí)間。
          • django-cacheops——一個(gè)靈巧的ORM緩存,帶有自動粒度事件驅(qū)動的失效時(shí)間。

          配置文件

          • configparser - (Python標(biāo)準(zhǔn)庫)INI文件解析器。

          數(shù)據(jù)分析

          • pandas - Python數(shù)據(jù)分析庫。
          • NumPy - Python科學(xué)計(jì)算庫。
          • matplotlib - Python數(shù)據(jù)可視化庫。

          Database 數(shù)據(jù)庫

          • pickleDB -一個(gè)簡單輕量級的Python鍵值存儲。

          數(shù)據(jù)庫驅(qū)動程序

          mysql

          • mysqlclient - Python MySQL驅(qū)動程序。
          • PyMySQL - Python MySQL驅(qū)動程序。
          • sqlAlchemy - Python ORM數(shù)據(jù)庫驅(qū)動。
          • peewee - Python輕量級ORM。

          postgresql

          • psycopg2 - Python PostgreSQL驅(qū)動程序。

          sqlite

          • sqlite3 - Python SQLite3驅(qū)動程序。

          NOSQL

          • pymongo - Python MongoDB驅(qū)動程序。
          • redis - Python Redis驅(qū)動程序。
          • Redis -py - Redis的Python客戶端。
          • pymemcache - Python Memcached驅(qū)動程序。
          • elasticsearch - Python Elasticsearch驅(qū)動程序。

          時(shí)間和日期

          • arrow - Python中的時(shí)間與日期。
          • datetime - (Python標(biāo)準(zhǔn)庫)日期和時(shí)間。
          • pytz - Python時(shí)區(qū)支持。
          • dateutil - Python日期和時(shí)間工具。
          • time - (Python標(biāo)準(zhǔn)庫)時(shí)間。
          • pendulum - Python中的時(shí)間與日期。

          DevOps的工具

          • ansible - Python的配置管理工具。
          • saltstack - Python的配置管理工具。
          • fabric——用于遠(yuǎn)程執(zhí)行和部署的簡單python工具。
          • psutil - Python進(jìn)程和系統(tǒng)工具。
          • Paramiko庫:SSH遠(yuǎn)程連接與文件傳輸.

          環(huán)境管理

          • virtualenv - Python虛擬環(huán)境管理器。
          • virtualenvwrapper - Python虛擬環(huán)境管理器。
          • pipenv - Python虛擬環(huán)境管理器。
          • poetry - Python虛擬環(huán)境管理器。
          • conda - Python虛擬環(huán)境管理器。
          • pip - Python包管理器。

          文件處理

          • pathlib - (Python標(biāo)準(zhǔn)庫)路徑對象。
          • shutil - (Python標(biāo)準(zhǔn)庫)文件操作。
          • watchdog——用于監(jiān)視文件系統(tǒng)事件的API和shell實(shí)用程序。

          GUI開發(fā)

          • PyQt5 - Python的GUI庫。
          • Tkinter——Tkinter是原生標(biāo)準(zhǔn)GUI包。

          HTML解析

          • BeautifulSoup - Python的HTML和XML解析器。
          • lxml - Python的HTML和XML解析器。

          HTTP client

          • requests - Python的HTTP庫。
          • httpx - Python的HTTP庫。
          • aiohttp - Python的異步HTTP庫。
          • urllib3 - Python的HTTP庫。

          作業(yè)調(diào)度器

          • schedule -用于人類的Python作業(yè)調(diào)度。
          • celery -用于Python的分布式異步任務(wù)隊(duì)列。
          • APScheduler -用于Python的作業(yè)調(diào)度器。
          • Django-schedule -一個(gè)Django日歷應(yīng)用程序。

          日志記錄

          • logging - (Python標(biāo)準(zhǔn)庫)日志記錄。
          • loguru - Python日志記錄庫。
          • logbook - Python日志記錄庫。

          RESTful API

          • flask - Python的輕量級Web框架。
          • django-rest-framework——一個(gè)強(qiáng)大而靈活的構(gòu)建web api的工具包。
          • fastapi - Python的現(xiàn)代、快速、高性能的web框架。
          • sanic - Python的異步web框架。

          模板引擎

          • jinja2 - Python的模板引擎。

          爬蟲

          • scrapy - Python的爬蟲框架。
          • requests - Python的HTTP庫。
          • beautifulsoup4 - Python的HTML和XML解析器。
          • selenium - Python的Web瀏覽器自動化工具。

          WSGI 服務(wù)器

          • gunicorn - Python的WSGI服務(wù)器。
          • uvicorn - Python的異步WSGI服務(wù)器。
          • uwsgi——這個(gè)項(xiàng)目旨在開發(fā)一個(gè)完整的棧來構(gòu)建托管服務(wù),用C語言編寫。

          者:HcySunYang https://www.zhihu.com/people/huo-chun-yang-77/posts

          Vue3 的 Compiler 與 runtime 緊密合作,充分利用編譯時(shí)信息,使得性能得到了極大的提升。本文的目的告訴你 Vue3 的 Compiler 到底做了哪些優(yōu)化,以及一些你可能希望知道的優(yōu)化細(xì)節(jié),在這個(gè)基礎(chǔ)上我們試著總結(jié)出一套手寫優(yōu)化模式的高性能渲染函數(shù)的方法,這些知識也可以用于實(shí)現(xiàn)一個(gè) Vue3 的 jsx babel 插件中,讓 jsx 也能享受優(yōu)化模式的運(yùn)行時(shí)收益,這里需要澄清的是,即使在非優(yōu)化模式下,理論上 Vue3 的 Diff 性能也是要優(yōu)于 Vue2 的。另外本文不包括 SSR 相關(guān)優(yōu)化,希望在下篇文章總結(jié)。

          篇幅較大,花費(fèi)了很大的精力整理,對于對 Vue3 還沒有太多了解的同學(xué)閱讀起來也許會吃力,不妨先收藏,以后也許會用得到。

          按照慣例 TOC:

          • Block Tree 和 PatchFlags
          • 傳統(tǒng) Diff 算法的問題
          • Block 配合 PatchFlags 做到靶向更新
          • 節(jié)點(diǎn)不穩(wěn)定 - Block Tree
          • v-if 的元素作為 Block
          • v-for 的元素作為 Block
          • 不穩(wěn)定的 Fragment
          • 穩(wěn)定的 Fragment
          • v-for 的表達(dá)式是常量
          • 多個(gè)根元素
          • 插槽出口
          • <template v-for>
          • 靜態(tài)提升
          • 提升靜態(tài)節(jié)點(diǎn)樹
          • 元素不會被提升的情況
          • 元素帶有動態(tài)的 key 綁定
          • 使用 ref 的元素
          • 使用自定義指令的元素
          • 提升靜態(tài) PROPS
          • 預(yù)字符串化
          • Cache Event handler
          • v-once
          • 手寫高性能渲染函數(shù)
          • 幾個(gè)需要記住的小點(diǎn)
          • Block Tree 是靈活的
          • 正確地使用 PatchFlags
          • NEED_PATCH
          • 該使用 Block 的地方必須用
          • 分支判斷使用 Block
          • 列表使用 Block
          • 使用動態(tài) key 的元素應(yīng)該是 Block
          • 使用 Slot hint
          • 為組件正確地使用 DYNAMIC_SLOTS
          • 使用 $stable hint

          Block Tree 和 PatchFlags

          Block Tree 和 PatchFlags 是 Vue3 充分利用編譯信息并在 Diff 階段所做的優(yōu)化。尤大已經(jīng)不止一次在公開場合聊過思路,我們深入細(xì)節(jié)的目的是為了更好的理解,并試圖手寫出高性能的 VNode。

          傳統(tǒng) Diff 算法的問題

          “傳統(tǒng) vdom”的 Diff 算法總歸要按照 vdom 樹的層級結(jié)構(gòu)一層一層的遍歷(如果你對各種傳統(tǒng) diff 算法不了解,可以看我之前寫《渲染器》這套文章,里面總結(jié)了三種傳統(tǒng) Diff方式),舉個(gè)例子如下模板所示:

          <div>
              <p class="foo">bar</p>
          </div>

          對于傳統(tǒng) diff 算法來說,它在 diff 這段 vnode(模板編譯后的 vnode)時(shí)會經(jīng)歷:

          • Div 標(biāo)簽的屬性 + children
          • <p> 標(biāo)簽的屬性(class) + children
          • 文本節(jié)點(diǎn):bar

          但是很明顯,這明明就是一段靜態(tài) vdom,它在組件更新階段是不可能發(fā)生變化的。如果能在 diff 階段跳過靜態(tài)內(nèi)容,那就會避免無用的 vdom 樹的遍歷和比對,這應(yīng)該就是最早的優(yōu)化思路來源——跳過靜態(tài)內(nèi)容,只對比動態(tài)內(nèi)容

          Block 配合 PatchFlags 做到靶向更新

          咱們先說 Block 再聊 Block Tree。現(xiàn)在思路有了,我們只希望對比非靜態(tài)的內(nèi)容,例如:

          <div>
              <p>foo</p>
              <p>{{ bar }}</p>
          </div>

          在這段模板中,只有 <p>{{ bar }}</p> 中的文本節(jié)點(diǎn)是動態(tài)的,因此只需要靶向更新該文本節(jié)點(diǎn)即可,這在包含大量靜態(tài)內(nèi)容而只有少量動態(tài)內(nèi)容的場景下,性能優(yōu)勢尤其明顯。可問題是怎么做呢?我們需要拿到整顆 vdom 樹中動態(tài)節(jié)點(diǎn)的能力,其實(shí)可能沒有大家想像的復(fù)雜,來看下這段模板對應(yīng)的傳統(tǒng) vdom 樹大概長什么樣:

          const vnode={
              tag: 'div',
              children: [
                  { tag: 'p', children: 'foo' },
                  { tag: 'p', children: ctx.bar },  // 這是動態(tài)節(jié)點(diǎn)
              ]
          }

          在傳統(tǒng)的 vdom 樹中,我們在運(yùn)行時(shí)得不到任何有用信息,但是 Vue3 的 compiler 能夠分析模板并提取有用信息,最終體現(xiàn)在 vdom 樹上。例如它能夠清楚的知道:哪些節(jié)點(diǎn)是動態(tài)節(jié)點(diǎn),以及為什么它是動態(tài)的(是綁定了動態(tài)的 class?還是綁定了動態(tài)的 style?亦或是其它動態(tài)的屬性?),總之編譯器能夠提取我們想要的信息,有了這些信息我們就可以在創(chuàng)建 vnode的過程中為動態(tài)的節(jié)點(diǎn)打上標(biāo)記:也就是傳說中的 PatchFlags。

          我們可以把 PatchFlags 簡單的理解為一個(gè)數(shù)字標(biāo)記,把這些數(shù)字賦予不同含義,例如:

          • 數(shù)字 1:代表節(jié)點(diǎn)有動態(tài)的 textContent(例如上面模板中的 p 標(biāo)簽)
          • 數(shù)字 2:代表元素有動態(tài)的 class 綁定
          • 數(shù)字 3:代表xxxxx

          總之我們可以預(yù)設(shè)這些含義,最后體現(xiàn)在 vnode 上:

          const vnode={
              tag: 'div',
              children: [
                  { tag: 'p', children: 'foo' },
                  { tag: 'p', children: ctx.bar, patchFlag: 1 /* 動態(tài)的 textContent */ },
              ]
          }

          有了這個(gè)信息,我們就可以在 vnode 的創(chuàng)建階段把動態(tài)節(jié)點(diǎn)提取出來,什么樣的節(jié)點(diǎn)是動態(tài)節(jié)點(diǎn)呢?帶有 patchFlag 的節(jié)點(diǎn)就是動態(tài)節(jié)點(diǎn),我們將它提取出來放到一個(gè)數(shù)組中存著,例如:

          const vnode={
              tag: 'div',
              children: [
                  { tag: 'p', children: 'foo' },
                  { tag: 'p', children: ctx.bar, patchFlag: 1 /* 動態(tài)的 textContent */ },
              ],
              dynamicChildren: [
                  { tag: 'p', children: ctx.bar, patchFlag: 1 /* 動態(tài)的 textContent */ },
              ]
          }

          dynamicChildren 就是我們用來存儲一個(gè)節(jié)點(diǎn)下所有子代動態(tài)節(jié)點(diǎn)的數(shù)組,注意這里的用詞哦:“子代”,例如:

          const vnode={
              tag: 'div',
              children: [
                  { tag: 'section', children: [
                      { tag: 'p', children: ctx.bar, patchFlag: 1 /* 動態(tài)的 textContent */ },
                  ]},
              ],
              dynamicChildren: [
                  { tag: 'p', children: ctx.bar, patchFlag: 1 /* 動態(tài)的 textContent */ },
              ]
          }

          如上 vnode 所示,div 節(jié)點(diǎn)不僅能收集直接動態(tài)子節(jié)點(diǎn),它還能收集所有子代節(jié)點(diǎn)中的動態(tài)節(jié)點(diǎn)。為什么 div 節(jié)點(diǎn)這么厲害呢?因?yàn)樗鼡碛幸粋€(gè)特殊的角色:Block,沒錯(cuò)這個(gè) div 節(jié)點(diǎn)就是傳說中的 Block。一個(gè) Block 其實(shí)就是一個(gè) VNode,只不過它有特殊的屬性(其中之一就是 dynamicChildren)。

          現(xiàn)在我們已經(jīng)拿到了所有的動態(tài)節(jié)點(diǎn),它們存儲在 dynamicChildren 中,因此在 diff 過程中就可以避免按照 vdom 樹一層一層的遍歷,而是直接找到 dynamicChildren 進(jìn)行更新。除了跳過無用的層級遍歷之外,由于我們早早的就為 vnode 打上了 patchFlag,因此在更新 dynamicChildren 中的節(jié)點(diǎn)時(shí),可以準(zhǔn)確的知道需要為該節(jié)點(diǎn)應(yīng)用哪些更新動作,這基本上就實(shí)現(xiàn)了靶向更新。

          節(jié)點(diǎn)不穩(wěn)定 - Block Tree

          一個(gè) Block 怎么也構(gòu)不成 Block Tree,這就意味著在一顆 vdom 樹中,會有多個(gè) vnode 節(jié)點(diǎn)充當(dāng) Block 的角色,進(jìn)而構(gòu)成一顆 Block Tree。那么什么情況下一個(gè) vnode 節(jié)點(diǎn)會充當(dāng) block 的角色呢?

          來看下面這段模板:

          <div>
            <section v-if="foo">
              <p>{{ a }}</p>
            </section>
            <div v-else>
              <p>{{ a }}</p>
            </div>
          </div>

          假設(shè)只要最外層的 div 標(biāo)簽是 Block 角色,那么當(dāng) foo 為真時(shí),block 收集到的動態(tài)節(jié)點(diǎn)為:

          cosnt block={
              tag: 'div',
              dynamicChildren: [
                  { tag: 'p', children: ctx.a, patchFlag: 1 }
              ]
          }

          當(dāng) foo 為假時(shí),block 的內(nèi)容如下:

          cosnt block={
              tag: 'div',
              dynamicChildren: [
                  { tag: 'p', children: ctx.a, patchFlag: 1 }
              ]
          }

          可以發(fā)現(xiàn)無論 foo 為真還是假,block 的內(nèi)容是不變的,這就意味什么在 diff 階段不會做任何更新,但是我們也看到了:v-if 的是一個(gè) <section> 標(biāo)簽,v-else 的是一個(gè) <div> 標(biāo)簽,所以這里就出問題了。實(shí)際上問題的本質(zhì)在于 dynamicChildren 的 diff是忽略 vdom 樹層級的,如下模板也有同樣的問題:

          <div>
            <section v-if="foo">
              <p>{{ a }}</p>
            </section>
            <section v-else> <!-- 即使這里是 section -->
                 <div> <!-- 這個(gè) div 標(biāo)簽在 diff 過程中被忽略 -->
                      <p>{{ a }}</p>
                  </div>
            </section >
          </div>

          即使 v-else 的也是一個(gè) <section> 標(biāo)簽,但由于前后 DOM 樹的不穩(wěn)定,也會導(dǎo)致問題。這時(shí)我們就思考,如何讓 DOM 樹的結(jié)構(gòu)變穩(wěn)定呢?

          v-if 的元素作為 Block

          如果讓使用了 v-if/v-else-if/v-else 等指令的元素也作為 Block 會怎么樣呢?我們拿如下模板為例:

          <div>
            <section v-if="foo">
              <p>{{ a }}</p>
            </section>
            <section v-else> <!-- 即使這里是 section -->
                 <div> <!-- 這個(gè) div 標(biāo)簽在 diff 過程中被忽略 -->
                      <p>{{ a }}</p>
                  </div>
            </section >
          </div>

          如果我們讓這兩個(gè) section 標(biāo)簽都作為 block,那么將構(gòu)成一顆 block tree:

          Block(Div)
              - Block(Section v-if)
              - Block(Section v-else)

          父級 Block 除了會收集子代動態(tài)節(jié)點(diǎn)之外,也會收集子 Block,因此兩個(gè) Block(section) 將作為 Block(div) 的 dynamicChildren:

          cosnt block={
              tag: 'div',
              dynamicChildren: [
                  { tag: 'section', { key: 0 }, dynamicChildren: [...]}, /* Block(Section v-if) */
                  { tag: 'section', { key: 1 }, dynamicChildren: [...]}  /* Block(Section v-else) */
              ]
          }

          這樣當(dāng) v-if 條件為真時(shí),dynamicChildren 中包含的是 Block(section v-if),當(dāng)條件為假時(shí) dynamicChildren 中包含的是 Block(section v-else),在 Diff 過程中,渲染器知道這是兩個(gè)不同的 Block,因此會做完全的替換,這樣就解決了 DOM 結(jié)構(gòu)不穩(wěn)定引起的問題。而這就是 Block Tree。

          v-for 的元素作為 Block

          不僅 v-if 會讓 DOM 結(jié)構(gòu)不穩(wěn)定,v-for 也會,但是 v-for 的情況稍微復(fù)雜一些。思考如下模板:

          <div>
              <p v-for="item in list">{{ item }}</p>
              <i>{{ foo }}</i>
              <i>{{ bar }}</i>
          </div>

          假設(shè) list 值由 ?[1 ,2]? 變?yōu)??[1]?,按照之前的思路,最外層的 <div> 標(biāo)簽作為一個(gè) Block,那么它更新前后對應(yīng)的 Block Tree 應(yīng)該是:

          // 前
          const prevBlock={
              tag: 'div',
              dynamicChildren: [
                  { tag: 'p', children: 1, 1 /* TEXT */ },
                  { tag: 'p', children: 2, 1 /* TEXT */ },
                  { tag: 'i', children: ctx.foo, 1 /* TEXT */ },
                  { tag: 'i', children: ctx.bar, 1 /* TEXT */ },
              ]
          }
          
          // 后
          const nextBlock={
              tag: 'div',
              dynamicChildren: [
                  { tag: 'p', children: item, 1 /* TEXT */ },
                  { tag: 'i', children: ctx.foo, 1 /* TEXT */ },
                  { tag: 'i', children: ctx.bar, 1 /* TEXT */ },
              ]
          }

          prevBlcok 中有四個(gè)動態(tài)節(jié)點(diǎn),nextBlock 中有三個(gè)動態(tài)節(jié)點(diǎn)。這時(shí)候要如何進(jìn)行 Diff?有的同學(xué)可能會說拿 dynamicChildren 進(jìn)行傳統(tǒng) Diff,這是不對的,因?yàn)閭鹘y(tǒng) Diff 的一個(gè)前置條件是同層級節(jié)點(diǎn)間的 Diff,但是 dynamicChildren 內(nèi)的節(jié)點(diǎn)未必是同層級的,這一點(diǎn)我們之前就提到過。

          實(shí)際上我們只需要讓 v-for 的元素也作為一個(gè) Block 就可以了。這樣無論 v-for 怎么變化,它始終都是一個(gè) Block,這保證了結(jié)構(gòu)穩(wěn)定,無論 v-for 怎么變化,這顆 Block Tree 看上去都是:

          const block={
              tag: 'div',
              dynamicChildren: [
                  // 這是一個(gè) Block 哦,它有 dynamicChildren
                  { tag: Fragment, dynamicChildren: [/*.. v-for 的節(jié)點(diǎn) ..*/] }
                  { tag: 'i', children: ctx.foo, 1 /* TEXT */ },
                  { tag: 'i', children: ctx.bar, 1 /* TEXT */ },
              ]
          }

          不穩(wěn)定的 Fragment

          剛剛我們使用一個(gè) Fragment 并讓它充當(dāng) Block 的角色解決了 v-for 元素所在層級的結(jié)構(gòu)穩(wěn)定,但我們來看一下這個(gè) Fragment 本身:

          { tag: Fragment, dynamicChildren: [/*.. v-for 的節(jié)點(diǎn) ..*/] }

          對于如下這樣的模板:

          <p v-for="item in list">{{ item }}</p>

          在 list 由 ?[1, 2]? 變成 ?[1]? 的前后,F(xiàn)ragment 這個(gè) Block 看上去應(yīng)該是:

          // 前
          const prevBlock={
              tag: Fragment,
              dynamicChildren: [
                  { tag: 'p', children: item, 1 /* TEXT */ },
                  { tag: 'p', children: item, 2 /* TEXT */ }
              ]
          }
          
          // 后
          const prevBlock={
              tag: Fragment,
              dynamicChildren: [
                  { tag: 'p', children: item, 1 /* TEXT */ }
              ]
          }

          我們發(fā)現(xiàn),F(xiàn)ragment 這個(gè) Block 仍然面臨結(jié)構(gòu)不穩(wěn)定的情況,所謂結(jié)構(gòu)不穩(wěn)定從結(jié)果上看指的是更新前后一個(gè) block 的 dynamicChildren 中收集的動態(tài)節(jié)點(diǎn)數(shù)量或順序的不一致。這種不一致會導(dǎo)致我們沒有辦法直接進(jìn)行靶向 Diff,怎么辦呢?其實(shí)對于這種情況是沒有辦法的,我們只能拋棄 dynamicChildren 的 Diff,并回退到傳統(tǒng) Diff:即 DiffFragment 的 children 而非 dynamicChildren。

          但需要注意的是 Fragment 的子節(jié)點(diǎn)(children)仍然可以是 Block:

          const block={
              tag: Fragment,
              children: [
                  { tag: 'p', children: item, dynamicChildren: [/*...*/], 1 /* TEXT */ },
                  { tag: 'p', children: item, dynamicChildren: [/*...*/], 1 /* TEXT */ }
              ]
          }

          這樣,對于 <p> 標(biāo)簽及其子代節(jié)點(diǎn)的 Diff 將恢復(fù) Block Tree 的 Diff 模式。

          穩(wěn)定的 Fragment

          既然有不穩(wěn)定的 Fragment,那就有穩(wěn)定的 Fragment,什么樣的 Fragment 是穩(wěn)定的呢?

          • v-for 的表達(dá)式是常量<p v-for="n in 10"></p><!-- 或者 --><p v-for="s in 'abc'"></p>

          由于 ?10? 和 ?'abc'? 是常量,所有這兩個(gè) Fragment 是不會變化的,因此它是穩(wěn)定的,對于穩(wěn)定的 Fragment 是不需要回退到傳統(tǒng) Diff 的,這在性能上會有一定的優(yōu)勢。

          • 多個(gè)根元素

          Vue3 不再限制組件的模板必須有一個(gè)根節(jié)點(diǎn),對于多個(gè)根節(jié)點(diǎn)的模板,例如:

          <template>
              <div></div>
              <p></p>
              <i></i>
          </template>

          如上,這也是一個(gè)穩(wěn)定的 Fragment,有的同學(xué)或許會想如下模板也是穩(wěn)定的 Fragment 嗎:

          <template>
              <div v-if="condition"></div>
              <p></p>
              <i></i>
          </template>

          這其實(shí)也是穩(wěn)定的,因?yàn)閹в?v-if 指令的元素本身作為 Block 存在,所以這段模板的 Block Tree 結(jié)構(gòu)總是:

          Block(Fragment)
              - Block(div v-if)
              - VNode(p)
              - VNode(i)

          對應(yīng)到 VNode 應(yīng)該類似于:

          const block={
              tag: Fragment,
              dynamicChildren: [
                  { tag: 'div', dynamicChildren: [...] },
                  { tag: 'p' },
                  { tag: 'i' },
              ],
              PatchFlags.STABLE_FRAGMENT
          }

          無論如何,它的結(jié)構(gòu)都是穩(wěn)定的。需要注意的是這里的 ?PatchFlags.STABLE_FRAGMENT?,該標(biāo)志必須存在,否則會回退傳統(tǒng) ?Diff? 模式。

          • 插槽出口

          如下模板所示:

          <Comp>
              <p v-if="ok"></p>
              <i v-else></i>
          </Comp>

          組件 <Comp> 內(nèi)的 children 將作為插槽內(nèi)容,在經(jīng)過編譯后,應(yīng)該作為 Block 角色的內(nèi)容自然會是 Block,已經(jīng)能夠保證結(jié)構(gòu)的穩(wěn)定了,例如如上代碼相當(dāng)于:

          render(ctx) {
              return createVNode(Comp, null, {
                  default: ()=> ([
                      ctx.ok
                          // 這里已經(jīng)是 Block 了
                          ? (openBlock(), createBlock('p', { key: 0 }))
                          : (openBlock(), createBlock('i', { key: 1 }))
                  ]),
                  _: 1 // 注意這里哦
              })
          }

          既然結(jié)構(gòu)已經(jīng)穩(wěn)定了,那么在渲染出口處 Comp.vue:

          <template>
              <slot/>
          </template>

          相當(dāng)于:

          render() {
              return (openBlock(), createBlock(Fragment, null,
                  this.$slots.default() || []
              ), PatchFlags.STABLE_FRAGMENT)
          }

          這自然就是 STABLE_FRAGMENT,大家注意前面代碼中 ?_: 1? 這是一個(gè)編譯的 slot hint,當(dāng)我們手寫優(yōu)化模式的渲染函數(shù)時(shí)必須要使用這個(gè)標(biāo)志才能讓 runtime 知道 slot是穩(wěn)定的,否則會退出非優(yōu)化模式。另外還有一個(gè) $stable hint,在文末會講解。

          • <template v-for>

          如下模板所示:

          <template>
              <template v-for="item in list">
                  <p>{{ item.name }}</P>
                  <p>{{ item.age }}</P>
              </template>
          </template> 

          對于帶有 v-for 的 template 元素本身來說,它是一個(gè)不穩(wěn)定的 Fragment,因?yàn)?list 不是常量。除此之外,由于 <template> 元素本身不渲染任何真實(shí) DOM,因此如果它含有多個(gè)元素節(jié)點(diǎn),那么這些元素節(jié)點(diǎn)也將作為 Fragment 存在,但這個(gè) Fragment 是穩(wěn)定的,因?yàn)樗粫S著 list 的變化而變化。

          以上內(nèi)容差不多就是 Block Tree 配合 PatchFlags 是如何做到靶向更新以及一些具體的思路細(xì)節(jié)了。

          靜態(tài)提升

          提升靜態(tài)節(jié)點(diǎn)樹

          Vue3 的 Compiler 如果開啟了 hoistStatic 選項(xiàng)則會提升靜態(tài)節(jié)點(diǎn),或靜態(tài)的屬性,這可以減少創(chuàng)建 VNode 的消耗,如下模板所示:

          <div>
              <p>text</p>
          </div>

          在沒有被提升的情況下其渲染函數(shù)相當(dāng)于:

          function render() {
              return (openBlock(), createBlock('div', null, [
                  createVNode('p', null, 'text')
              ]))
          }

          很明顯,p 標(biāo)簽是靜態(tài)的,它不會改變。但是如上渲染函數(shù)的問題也很明顯,如果組件內(nèi)存在動態(tài)的內(nèi)容,當(dāng)渲染函數(shù)重新執(zhí)行時(shí),即使 p 標(biāo)簽是靜態(tài)的,那么它對應(yīng)的 VNode 也會重新創(chuàng)建。當(dāng)開啟靜態(tài)提升后,其渲染函數(shù)如下:

          const hoist1=createVNode('p', null, 'text')
          
          function render() {
              return (openBlock(), createBlock('div', null, [
                  hoist1
              ]))
          }

          這就實(shí)現(xiàn)了減少 VNode 創(chuàng)建的性能消耗。需要了解的是,靜態(tài)提升是以樹為單位的,如下模板所示:

          <div>
            <section>
              <p>
                <span>abc</span>
              </p>
            </section >
          </div>

          除了根節(jié)點(diǎn)的 div 作為 block 不可被提升之外,整個(gè) <section> 元素及其子代節(jié)點(diǎn)都會被提升,因?yàn)樗麄兪钦脴涠际庆o態(tài)的。如果我們把上面代碼中的 abc 換成 {{ abc }},那么整棵樹都不會被提升。再看如下代碼:

          <div>
            <section>
              {{ dynamicText }}
              <p>
                <span>abc</span>
              </p>
            </section >
          </div>

          由于 section 標(biāo)簽內(nèi)包含動態(tài)插值,因此以 section 為根節(jié)點(diǎn)的子樹就不會被提升,但是 p 標(biāo)簽以及其子代節(jié)點(diǎn)都是靜態(tài)的,是可以被提升的。

          元素不會被提升的情況

          • 元素帶有動態(tài)的 key 綁定

          除了剛剛講到的元素的所有子代節(jié)點(diǎn)必須都是靜態(tài)的才會被提升之外還有哪些情況下會阻止提升呢?

          如果一個(gè)元素有動態(tài)的 key 綁定那么它是不會被提升的,例如:

          <div :key="foo"></div>

          實(shí)際上一個(gè)元素?fù)碛腥魏蝿討B(tài)綁定都不應(yīng)該被提升,那么為什么 key 會被單獨(dú)拿出來?實(shí)際上 key 和普通的 props 相比,它對于 VNode 的意義是不一樣的,普通的 props 如果它是動態(tài)的,那么只需要體現(xiàn)在 PatchFlags 上就可以了,例如:

          <div>
              <p :foo="bar"></p>
          </div>

          我們可以為 p 標(biāo)簽打上 PatchFlags:

          render(ctx) {
              return (openBlock(), createBlock('div', null, [
                  createVNode('p', { foo: ctx }, null, PatchFlags.PROPS, ['foo'])
              ]))
          }

          注意到在創(chuàng)建 VNode 時(shí),為其打上了 PatchFlags.PROPS,代表這個(gè)元素需要更新 PROPS,并且需要更新的 PROPS 的名字叫 foo。

          h但是 key 本身具有特殊意hi義,它是 VNode(或元素) 的唯一標(biāo)識,即使兩個(gè)元素除了 key 以外一切都相同,但這兩個(gè)元素仍然是不同的元素,對于不同的元素需要做完全的替換處理才行,而 PatchFlags 用于在同一個(gè)元素上的屬性補(bǔ)丁,因此 key 是不同于其它 props的。

          正因?yàn)?key 的值是動態(tài)的可變的,因此對于擁有動態(tài) key 的元素,它始終都應(yīng)該參與到 diff 中并且不能簡單的打 PatchFlags 補(bǔ)丁標(biāo)識,那應(yīng)該怎么做呢?很簡單,讓擁有動態(tài) key 的元素也作為 Block 即可,以如下模板為例:

          <div>
              <div :key="foo"></div>
          </div>

          它對應(yīng)的渲染函數(shù)應(yīng)該是:

          render(ctx) {
              return (openBlock(), createBlock('div', null, [
                  (openBlock(), createBlock('div', { key: ctx.foo }))
              ]))
          }

          Tips:手寫優(yōu)化模式的渲染函數(shù)時(shí),如果使用動態(tài)的 key,記得要使用 Block 哦,我們在后文還會總結(jié)。

          • 使用 ref 的元素

          如果一個(gè)元素使用了 ref,無論是否動態(tài)綁定的值,那么這個(gè)元素都不會被靜態(tài)提升,這是因?yàn)樵诿恳淮?patch 時(shí)都需要設(shè)置 ref 的值,如下模板所示:

          <div ref="domRef"></div>

          乍一看覺得這完全就是一個(gè)靜態(tài)元素,沒錯(cuò),元素本身不會發(fā)生變化,但由于 ref 的特性,導(dǎo)致我們必須在每次 Diff 的過程中重新設(shè)置 ref 的值,為什么呢?來看一個(gè)使用 ref 的場景:

          <template>
              <div>
                  <p ref="domRef"></p>
              </div>
          </template>
          <script>
          export default {
              setup() {
                  const refP1=ref(null)
                  const refP2=ref(null)
                  const useP1=ref(true)
          
                  return {
                      domRef: useP1 ? refP1 : refP2
                  }
              }
          }
          </script>

          如上代碼所示,p 標(biāo)簽使用了一個(gè)非動態(tài)的 ref 屬性,值為字符串 domRef,同時(shí)我們注意到 setupContext(我們把 setup 函數(shù)返回的對象叫做 setupContext) 中也包含了同名的 domRef 屬性,這不是偶然,他們之間會建立聯(lián)系,最終結(jié)果就是:

          • 當(dāng) useP1 為真時(shí),refP1.value 引用 p 元素
          • 當(dāng) useP1 為假時(shí),refP2.value 引用 p 元素

          因此,即使 ref 是靜態(tài)的,但很顯然在更新的過程中由于 useP1 的變化,我們不得不更新 domRef,所以只要一個(gè)元素使用了 ref,它就不會被靜態(tài)提升,并且這個(gè)元素對應(yīng)的 VNode 也會被收集到父 Block 的 dynamicChildren 中。

          但由于 p 標(biāo)簽除了需要更新 ref 之外,并不需要更新其他 props,所以在真實(shí)的渲染函數(shù)中,會為它打上一個(gè)特殊的 PatchFlag,叫做:PatchFlags.NEED_PATCH:

          render() {
              return (openBlock(), createBlock('div', null, [
                  createVNode('p', { ref: 'domRef' }, null, PatchFlags.NEED_PATCH)
              ]))
          }
          • 使用自定義指令的元素

          實(shí)際上一個(gè)元素如果使用除 v-pre/v-cloak 之外的所有 Vue 原生提供的指令,都不會被提升,使用自定義指令也不會被提升,例如:

          <p v-custom></p>

          和使用 key 一樣,會為這段模板對應(yīng)的 VNode 打上 NEED_PATCH 標(biāo)志。順便講一下手寫渲染函數(shù)時(shí)如何應(yīng)用自定義指令,自定義指令是一種運(yùn)行時(shí)指令,與組件的生命周期類似,一個(gè) VNode 對象也有它自己生命周期:

          • beforeMount
          • mounted
          • beforeUpdate
          • updated
          • beforeUnmount
          • unmounted

          編寫一個(gè)自定義指令:

          const myDir: Directive={
            beforeMount(el, binds) {
              console.log(el)
              console.log(binds.value)
              console.log(binds.oldValue)
              console.log(binds.arg)
              console.log(binds.modifiers)
              console.log(binds.instance)
            }
          }

          使用該指令:

          const App={
            setup() {
              return ()=> {
                return h('div', [
                  // 調(diào)用 withDirectives 函數(shù)
                  withDirectives(h('h1', 'hahah'), [
                    // 四個(gè)參數(shù)分別是:指令、值、參數(shù)、修飾符
                    [myDir, 10, 'arg', { foo: true }]
                  ])
                ])
              }
            }
          }

          一個(gè)元素可以綁定多個(gè)指令:

          const App={
            setup() {
              return ()=> {
                return h('div', [
                  // 調(diào)用 withDirectives 函數(shù)
                  withDirectives(h('h1', 'hahah'), [
                    // 四個(gè)參數(shù)分別是:指令、值、參數(shù)、修飾符
                    [myDir, 10, 'arg', { foo: true }],
                    [myDir2, 10, 'arg', { foo: true }],
                    [myDir3, 10, 'arg', { foo: true }]
                  ])
                ])
              }
            }
          }

          提升靜態(tài) PROPS

          前面說過,靜態(tài)節(jié)點(diǎn)的提升以樹為單位,如果一個(gè) VNode 存在非靜態(tài)的子代節(jié)點(diǎn),那么該 VNode 就不是靜態(tài)的,也就不會被提升。但這個(gè) VNode 的 props 卻可能是靜態(tài)的,這使我們可以將它的 props 進(jìn)行提升,這同樣可以節(jié)約 VNode 對象的創(chuàng)建開銷,內(nèi)存占用等,例如:

          <div>
              <p foo="bar" a=b>{{ text }}</p>
          </div>

          在這段模板中 p 標(biāo)簽有動態(tài)的文本內(nèi)容,因此不可以被提升,但 p 標(biāo)簽的所有屬性都是靜態(tài)的,因此可以提升它的屬性,經(jīng)過提升后其渲染函數(shù)如下:

          const hoistProp={ foo: 'bar', a: 'b' }
          
          render(ctx) {
              return (openBlock(), createBlock('div', null, [
                  createVNode('p', hoistProp, ctx.text)
              ]))
          }

          即使動態(tài)綁定的屬性值,但如果值是常量,那么也會被提升:

          <p :foo="10" :bar="'abc' + 'def'">{{ text }}</p>

          'abc' + 'def' 是常量,可以被提升。

          預(yù)字符串化

          靜態(tài)提升的 VNode 節(jié)點(diǎn)或節(jié)點(diǎn)樹本身是靜態(tài)的,那么能否將其預(yù)先字符串化呢?如下模板所示:

          <div>
              <p></p>
              <p></p>
              ...20 個(gè) p 標(biāo)簽
              <p></p>
          </div>

          假設(shè)如上模板中有大量連續(xù)的靜態(tài)的 p 標(biāo)簽,當(dāng)采用了 hoist 優(yōu)化時(shí),結(jié)果如下:

          cosnt hoist1=createVNode('p', null, null, PatchFlags.HOISTED)
          cosnt hoist2=createVNode('p', null, null, PatchFlags.HOISTED)
          ... 20 個(gè) hoistx 變量
          cosnt hoist20=createVNode('p', null, null, PatchFlags.HOISTED)
          
          render() {
              return (openBlock(), createBlock('div', null, [
                  hoist1, hoist2, ...20 個(gè)變量, hoist20
              ]))
          }

          預(yù)字符串化會將這些靜態(tài)節(jié)點(diǎn)序列化為字符串并生成一個(gè) Static 類型的 VNode:

          const hoistStatic=createStaticVNode('<p></p><p></p><p></p>...20個(gè)...<p></p>')
          
          render() {
              return (openBlock(), createBlock('div', null, [
                 hoistStatic
              ]))
          }

          這有幾個(gè)明顯的優(yōu)勢:

          • 生成代碼的體積減少
          • 減少創(chuàng)建 VNode 的開銷
          • 減少內(nèi)存占用

          靜態(tài)節(jié)點(diǎn)在運(yùn)行時(shí)會通過 innerHTML 來創(chuàng)建真實(shí)節(jié)點(diǎn),因此并非所有靜態(tài)節(jié)點(diǎn)都是可以預(yù)字符串化的,可以預(yù)字符串化的靜態(tài)節(jié)點(diǎn)需要滿足以下條件:

          • 非表格類標(biāo)簽:caption 、thead、tr、th、tbody、td、tfoot、colgroup、col
          • 標(biāo)簽的屬性必須是:
          • 標(biāo)準(zhǔn) HTML attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
          • 或 data-/aria- 類屬性

          當(dāng)一個(gè)節(jié)點(diǎn)滿足這些條件時(shí)代表這個(gè)節(jié)點(diǎn)是可以預(yù)字符串化的,但是如果只有一個(gè)節(jié)點(diǎn),那么并不會將其字符串化,可字符串化的節(jié)點(diǎn)必須連續(xù)且達(dá)到一定數(shù)量才行:

          • 如果節(jié)點(diǎn)沒有屬性,那么必須有連續(xù) 20 個(gè)及以上的靜態(tài)節(jié)點(diǎn)存在才行,例如:<div><p></p><p></p>… 20 個(gè) p 標(biāo)簽<p></p></div>

          或者在這些連續(xù)的節(jié)點(diǎn)中有 5 個(gè)及以上的節(jié)點(diǎn)是有屬性綁定的節(jié)點(diǎn):

          <div>
              <p id="a"></p>
              <p id="b"></p>
              <p id="c"></p>
              <p id="d"></p>
              <p id="e"></p>
          </div>

          這段節(jié)點(diǎn)的數(shù)量雖然沒有達(dá)到 20 個(gè),但是滿足 5 個(gè)節(jié)點(diǎn)有屬性綁定。

          這些節(jié)點(diǎn)不一定是兄弟關(guān)系,父子關(guān)系也是可以的,只要閾值滿足條件即可,例如:

          <div>
              <p id="a">
                  <p id="b">
                      <p id="c">
                          <p id="d">
                              <p id="e"></p>
                          </p>
                      </p>
                  </p>
              </p>
          </div>

          預(yù)字符串化會在編譯時(shí)計(jì)算屬性的值,例如:

          <div>
              <p :id="'id-' + 1">
                  <p :id="'id-' + 2">
                      <p :id="'id-' + 3">
                          <p :id="'id-' + 4">
                              <p :id="'id-' + 5"></p>
                          </p>
                      </p>
                  </p>
              </p>
          </div>

          在與字符串化之后:

          const hoistStatic=createStaticVNode('<p id="id-1"></p><p id="id-2"></p>.....<p id="id-5"></p>')

          可見 id 屬性值時(shí)計(jì)算后的。

          Cache Event handler

          如下組件的模板所示:

          <Comp @change="a + b" />

          這段模板如果手寫渲染函數(shù)的話相當(dāng)于:

          render(ctx) {
              return h(Comp, {
                  onChange: ()=> (ctx.a + ctx.b)
              })
          }

          很顯然,每次 render 函數(shù)執(zhí)行的時(shí)候,Comp 組件的 props 都是新的對象,onChange 也會是全新的函數(shù)。這會導(dǎo)致觸發(fā) Comp 組件的更新。

          當(dāng) Vue3 Compiler 開啟 prefixIdentifiers 以及 cacheHandlers 時(shí),這段模板會被編譯為:

          render(ctx, cache) {
              return h(Comp, {
                  onChange: cache[0] || (cache[0]=($event)=> (ctx.a + ctx.b))
              })
          }

          這樣即使多次調(diào)用渲染函數(shù)也不會觸發(fā) Comp 組件的更新,因?yàn)?Vue 在 patch 階段比對 props 時(shí)就會發(fā)現(xiàn) onChange 的引用沒變。

          如上代碼中 render 函數(shù)的 cache 對象是 Vue 內(nèi)部在調(diào)用渲染函數(shù)時(shí)注入的一個(gè)數(shù)組,像下面這種:

          render.call(ctx, ctx, [])

          實(shí)際上,我們即使不依賴編譯也能手寫出具備 cache 能力的代碼:

          const Comp={
              setup() {
                  // 在 setup 中定義 handler
                  const handleChange=()=> {/* ... */}
                  return ()=> {
                      return h(AnthorComp, {
                          onChange: handleChange  // 引用不變
                      })
                  }
              }
          }

          因此我們最好不要寫出如下這樣的代碼:

          const Comp={
              setup() {
                  return ()=> {
                      return h(AnthorComp, {
                          onChang(){/*...*/}  // 每次渲染函數(shù)執(zhí)行,都是全新的函數(shù)
                      })
                  }
              }
          }

          v-once

          這是 Vue2 就支持的功能,v-once 是一個(gè)“很指令”的指令,因?yàn)樗褪墙o編譯器看的,當(dāng)編譯器遇到 v-once 時(shí),會利用我們剛剛講過的 cache 來緩存全部或者一部分渲染函數(shù)的執(zhí)行結(jié)果,例如如下模板:

          <div>
              <div v-once>{{ foo }}</div>
          </div>

          會被編譯為:

          render(ctx, cache) {
              return (openBlock(), createBlock('div', null, [
                  cache[1] || (cache[1]=h("div", null, ctx.foo, 1 /* TEXT */))
              ]))
          }

          這樣就緩存了這段 vnode。既然 vnode 已經(jīng)被緩存了,后續(xù)的更新就都會讀取緩存的內(nèi)容,而不會重新創(chuàng)建 vnode 對象了,同時(shí)在 Diff 的過程中也就不需要這段 vnode 參與了,因此你通常會看到編譯后的代碼更接近如下內(nèi)容:

          render(ctx, cache) {
              return (openBlock(), createBlock('div', null, [
                  cache[1] || (
                      setBlockTracking(-1), // 阻止這段 VNode 被 Block 收集
                      cache[1]=h("div", null, ctx.foo, 1 /* TEXT */),
                      setBlockTracking(1), // 恢復(fù)
                      cache[1] // 整個(gè)表達(dá)式的值
                  )
              ]))
          }

          稍微解釋一下這段代碼,我們已經(jīng)講解過何為 “Block Tree”,而 openBlock() 和 createBlock() 函數(shù)用來創(chuàng)建一個(gè) Block。而 setBlockTracking(-1) 則用來暫停收集的動作,所以在 v-once 編譯生成的代碼中你會看到它,這樣使用 v-once 包裹的內(nèi)容就不會被收集到父 Block 中,也就不參與 Diff 了。

          所以,v-once 帶來的性能提升來自兩方面:

          • 1、VNode 的創(chuàng)建開銷
          • 2、無用的 Diff 開銷

          但其實(shí)我們不通過模板編譯,一樣可以通過緩存 VNode 來減少 VNode 的創(chuàng)建開銷:

          const Comp={
              setup() {
                  // 緩存 content
                  const content=h('div', 'xxxx')
                  return ()=> {
                      return h('section', content)
                  }
              }
          }

          但這樣避免不了無用的 Diff 開銷,因?yàn)槲覀儧]有使用 Block Tree 優(yōu)化模式。

          這里有必要提及的一點(diǎn)是:在 Vue2.5.18+ 以及 Vue3 中 VNode 是可重用的,例如我們可以在不同的地方多次使用同一個(gè) VNode 節(jié)點(diǎn):

          const Comp={
              setup() {
                  const content=h('div', 'xxxx')
                  return ()=> {
                      // 多次渲染 content
                      return h('section', [content, content, content])
                  }
              }
          }

          手寫高性能渲染函數(shù)

          接下來我們將進(jìn)入重頭戲環(huán)節(jié),我們嘗試手寫優(yōu)化模式的渲染函數(shù)。

          幾個(gè)需要記住的小點(diǎn):

          1. 一個(gè) Block 就是一個(gè)特殊的 VNode,可以理解為它只是比普通 VNode 多了一個(gè) dynamicChildren 屬性
          2. createBlock() 函數(shù)和 createVNode() 函數(shù)的調(diào)用簽名幾乎相同,實(shí)際上 createBlock() 函數(shù)內(nèi)部就是封裝了 createVNode(),這再次證明 Block 就是 VNode。
          3. 在調(diào)用 createBlock() 創(chuàng)建 Block 前要先調(diào)用 openBlock() 函數(shù),通常這兩個(gè)函數(shù)配合逗號運(yùn)算符一同出現(xiàn):render() {return (openBlock(), createBlock('div'))}

          Block Tree 是靈活的:

          在之前的介紹中根節(jié)點(diǎn)以 Block 的角色存在的,但是根節(jié)點(diǎn)并不必須是 Block,我們可以在任意節(jié)點(diǎn)開啟 Block:

          setup() {
              return ()=> {
                  return h('div', [
                      (openBlock(), createBlock('p', null, [/*...*/]))
                  ])
              }
          }

          這也是可以的,因?yàn)殇秩酒髟?Diff 的過程中如果 VNode 帶有 dynamicChildren 屬性,會自動進(jìn)入優(yōu)化模式。但是我們通常會讓根節(jié)點(diǎn)充當(dāng) Block 角色。

          正確地使用 PatchFlags:

          PatchFlags 用來標(biāo)記一個(gè)元素需要更新的內(nèi)容,例如當(dāng)元素有動態(tài)的 class 綁定時(shí),我們需要使用 PatchFlags.CLASS 標(biāo)記:

          const App={
            setup() {
              const refOk=ref(true)
          
              return ()=> {
                return (openBlock(), createBlock('div', null, [
                  createVNode('p', { class: { foo: refOk.value } }, 'hello', PatchFlags.CLASS) // 使用 CLASS 標(biāo)記
                ]))
              }
            }
          }

          如果使用了錯(cuò)誤的標(biāo)記則可能導(dǎo)致更新失敗,下面列出詳細(xì)的標(biāo)記使用方式:

          • PatchFlags.CLASS - 當(dāng)有動態(tài)的 class 綁定時(shí)使用
          • PatchFlags.STYLE - 當(dāng)有動態(tài)的 style 綁定時(shí)使用,例如:createVNode(‘p’, { style: { color: refColor.value } }, ‘hello’, PatchFlags.STYLE)
          • PatchFlags.TEXT - 當(dāng)有動態(tài)的文本節(jié)點(diǎn)是使用,例如:createVNode(‘p’, null, refText.value, PatchFlags.TEXT)
          • PatchFlags.PROPS - 當(dāng)有除了 class 和 style 之外的其他動態(tài)綁定屬性時(shí),例如:createVNode(‘p’, { foo: refVal.value }, ‘hello’, PatchFlags.PROPS, [‘foo’])

          這里需要注意的是,除了要使用 PatchFlags.PROPS 之外,還要提供第五個(gè)參數(shù),一個(gè)數(shù)組,包含了動態(tài)屬性的名字。

          • PatchFlags.FULL_PROPS - 當(dāng)有動態(tài) name 的 props 時(shí)使用,例如:createVNode(‘p’, { [refKey.value]: ‘val’ }, ‘hello’, PatchFlags.FULL_PROPS)

          實(shí)際上使用 FULL_PROPS 等價(jià)于對 props 的 Diff 與傳統(tǒng) Diff 一樣。其實(shí),如果覺得心智負(fù)擔(dān)大,我們大可以全部使用 FULL_PROPS,這么做的好處是:

          • 避免誤用 PatchFlags 導(dǎo)致的 bug
          • 減少心智負(fù)擔(dān)的同時(shí),雖然失去了 props diff 的性能優(yōu)勢,但是仍然可以享受 Block Tree 的優(yōu)勢。

          當(dāng)同時(shí)存在多種更新,需要將 PatchFlags 進(jìn)行按位或運(yùn)算,例如:?PatchFlags.CLASS | PatchFlags.STYLE? 。

          NEED_PATCH 標(biāo)識

          為什么單獨(dú)把這個(gè)標(biāo)志拿出來講呢,它比較特殊,需要我們額外注意。當(dāng)我們使用 ref 或 onVNodeXXX 等 hook 時(shí)(包括自定義指令),需要使用該標(biāo)志,以至于它可以被父級 Block 收集,詳細(xì)原因我們在靜態(tài)提升一節(jié)里面講解過了:

          const App={
            setup() {
              const refDom=ref(null)
              return ()=> {
                return (openBlock(), createBlock('div', null,[
                  createVNode('p',
                    {
                      ref: refDom,
                      onVnodeBeforeMount() {/* ... */}
                    },
                    null,
                    PatchFlags.NEED_PATCH
                  )
                ]))
              }
            }
          }

          該使用 Block 的地方必須用

          在最開始的時(shí)候,我們講解了有些指令會導(dǎo)致 DOM 結(jié)構(gòu)不穩(wěn)定,從而必須使用 Block 來解決問題。手寫渲染函數(shù)也是一樣:

          • 分支判斷使用 Block:const App={setup() {const refOk=ref(true) return ()=> { return (openBlock(), createBlock('div', null, [ refOk.value // 這里使用 Block ? (openBlock(), createBlock('div', { key: 0 }, [/* ... */])) : (openBlock(), createBlock('div', { key: 1 }, [/* ... */])) ])) }}}

          這里使用 Block 的原因我們在前文已經(jīng)講解過了,但這里需要強(qiáng)調(diào)的是,除了分支判斷要使用 Block 之外,還需要為 Block 指定不同的 key 才行。

          • 列表使用 Block:

          當(dāng)我們渲染列表時(shí),我們常常寫出如下代碼:

          const App={
            setup() {
              const obj=reactive({ list: [ { val: 1 }, { val: 2 } ] })
          
              return ()=> {
                return (openBlock(), createBlock('div', null,
                  // 渲染列表
                  obj.list.map(item=> {
                    return createVNode('p', null, item.val, PatchFlags.TEXT)
                  })
                ))
              }
            }
          }

          這么寫在非優(yōu)化模式下是沒問題的,但我們現(xiàn)在使用了 Block,前文已經(jīng)講過為什么 v-for需要使用 Block 的原因,試想當(dāng)我們執(zhí)行如下語句修改數(shù)據(jù):

          obj.list.splice(0, 1)

          這就會導(dǎo)致 Block 中收集的動態(tài)節(jié)點(diǎn)不一致,最終 Diff 出現(xiàn)問題。解決方案就是讓整個(gè)列表作為一個(gè) Block,這時(shí)我們需要使用 Fragment:

          const App={
            setup() {
              const obj=reactive({ list: [ { val: 1 }, { val: 2 } ] })
          
              return ()=> {
                return (openBlock(), createBlock('div', null, [
                  // 創(chuàng)建一個(gè) Fragment,并作為 Block 角色
                  (openBlock(true), createBlock(Fragment, null,
                    // 在這里渲染列表
                    obj.list.map(item=> {
                      return createVNode('p', null, item.val, PatchFlags.TEXT)
                    }),
                    // 記得要指定正確的 PatchFlags
                    PatchFlags.UNKEYED_FRAGMENT
                  ))
                ]))
              }
            }
          }

          總結(jié)一下:

          • 對于列表我們應(yīng)該始終使用 Fragment,并作為 Block 的角色
          • 如果 Fragment 的 children 沒有指定 key,那么應(yīng)該為 Fragment 打上 PatchFlags.UNKEYED_FRAGMENT。相應(yīng)的,如果指定了 key 就應(yīng)該打上 PatchFlags.KEYED_FRAGMENT
          • 注意到在調(diào)用 openBlock(true) 時(shí),傳遞了參數(shù) true,這代表這個(gè) Block 不會收集 dynamicChildren,因?yàn)闊o論是 KEYED 還是 UNKEYED 的 Fragment,在 Diff 它的 children 時(shí)都會回退傳統(tǒng) Diff 模式,因此不需要收集 dynamicChildren。

          這里還有一點(diǎn)需要注意,在 Diff Fragment 時(shí),由于回退了傳統(tǒng) Diff,我們希望盡快恢復(fù)優(yōu)化模式,同時(shí)保證后續(xù)收集的可控性,因此通常會讓 Fragment 的每一個(gè)子節(jié)點(diǎn)都作為 Block 的角色:

          const App={
            setup() {
              const obj=reactive({ list: [ { val: 1 }, { val: 2 } ] })
          
              return ()=> {
                return (openBlock(), createBlock('div', null, [
                  (openBlock(true), createBlock(Fragment, null,
                    obj.list.map(item=> {
                      // 修改了這里
                      return (openBlock(), createBlock('p', null, item.val, PatchFlags.TEXT))
                    }),
                    PatchFlags.UNKEYED_FRAGMENT
                  ))
                ]))
              }
            }
          }

          最后再說一下穩(wěn)定的 Fragment,如果你能確定列表永遠(yuǎn)不會變化,例如你能確定 obj.list是不會變化的,那么你應(yīng)該使用:PatchFlags.STABLE_FRAGMENT 標(biāo)志,并且調(diào)用 openBlcok() 去掉參數(shù),代表收集 dynamicChildren:

          const App={
            setup() {
              const obj=reactive({ list: [ { val: 1 }, { val: 2 } ] })
          
              return ()=> {
                return (openBlock(), createBlock('div', null, [
                  // 調(diào)用 openBlock() 不要傳參
                  (openBlock(), createBlock(Fragment, null,
                    obj.list.map(item=> {
                      // 列表中的任何節(jié)點(diǎn)都不需要是 Block 角色
                      return createVNode('p', null, item.val, PatchFlags.TEXT)
                    }),
                    // 穩(wěn)定的片段
                    PatchFlags.STABLE_FRAGMENT
                  ))
                ]))
              }
            }
          }

          如上注釋所述。

          • 使用動態(tài) key 的元素應(yīng)該是 Block

          正如在靜態(tài)提升一節(jié)中所講的,當(dāng)元素使用動態(tài) key 的時(shí)候,即使兩個(gè)元素的其他方面完全一樣,那也是兩個(gè)不同的元素,需要做替換處理,在 Block Tree 中應(yīng)該以 Block 的角色存在,因此如果一個(gè)元素使用了動態(tài) key,它應(yīng)該是一個(gè) Block:

          const App={
            setup() {
              const refKey=ref('foo')
          
              return ()=> {
                return (openBlock(), createBlock('div', null,[
                  // 這里應(yīng)該是 Block
                  (openBlock(), createBlock('p', { key: refKey.value }))
                ]))
              }
            }
          }

          這實(shí)際上是必須的,詳情查看 https://github.com/vuejs/vue-next/issues/938 。

          使用 Slot hint

          我們在“穩(wěn)定的 Fragment”一節(jié)中提到了 slot hint,當(dāng)我們?yōu)榻M件編寫插槽內(nèi)容時(shí),為了告訴 runtime:“我們已經(jīng)能夠保證插槽內(nèi)容的結(jié)構(gòu)穩(wěn)定”,則需要使用 slot hint:

          render() {
              return (openBlock(), createBlock(Comp, null, {
                  default: ()=> [
                      refVal.value
                         ? (openBlock(), createBlock('p', ...)) 
                         ? (openBlock(), createBlock('div', ...)) 
                  ],
                  // slot hint
                  _: 1
              }))
          }

          當(dāng)然如果你不能保證這一點(diǎn),或者覺得心智負(fù)擔(dān)大,那么就不要寫 hint 了。

          使用 $stable hint

          $stable hint 和之前講的優(yōu)化策略不同,前文中的策略都是假設(shè)渲染器在優(yōu)化模式下工作的,而 $stable 用于非優(yōu)化模式,也就是我們平時(shí)寫的渲染函數(shù)。那么它有什么用呢?如下代碼所示(使用 tsx 演示):

          export const App=defineComponent({
            name: 'App',
            setup() {
              const refVal=ref(true)
          
              return ()=> {
                refVal.value
          
                return (
                  <Hello>
                    {
                      { default: ()=> [<p>hello</p>] }
                    }
                  </Hello>
                )
              }
            }
          })

          如上代碼所示,渲染函數(shù)中讀取了 refVal.value 的值,建立了依賴收集關(guān)系,當(dāng)修改 refVal 的值時(shí),會觸發(fā) <Hello> 組件的更新,但是我們發(fā)現(xiàn) Hello 組件一來沒有 props 變化,二來它的插槽內(nèi)容是靜態(tài)的,因此不應(yīng)該更新才對,這時(shí)我們可以使用 $stable hint:

          export const App=defineComponent({
            name: 'App',
            setup() {
              const refVal=ref(true)
          
              return ()=> {
                refVal.value
          
                return (
                  <Hello>
                    {
                      { default: ()=> [<p>hello</p>], $stable: true } // 修改了這里
                    }
                  </Hello>
                )
              }
            }
          })

          為組件正確地使用 DYNAMIC_SLOTS

          當(dāng)我們動態(tài)構(gòu)建 slots 時(shí),需要為組件的 VNode 指定 PatchFlags.DYNAMIC_SLOTS,否則將導(dǎo)致更新失敗。什么是動態(tài)構(gòu)建 slots 呢?通常情況下是指:依賴當(dāng)前 scope 變量構(gòu)建的 slots,例如:

          render() {
              // 使用當(dāng)前組件作用域的變量
              const slots={}
              // 常見的場景
              // 情況一:條件判斷
              if (refVal.value) {
                  slots.header=()=> [h('p', 'hello')]
              }
              // 情況二:循環(huán)
              refList.value.forEach(item=> {
                  slots[item.name]=()=> [...]
              })
              // 情況三:動態(tài) slot 名稱,情況二包含情況三
              slots[refName.value]=()=> [...]
          
              return (openBlock(), createBlock('div', null, [
                  // 這里要使用 PatchFlags.DYNAMIC_SLOTS
                  createVNode(Comp, null, slots, PatchFlags.DYNAMIC_SLOTS)
              ]))
          }

          如上注釋所述。

          以上,不知道到達(dá)這里的同學(xué)有多少,Don’t stop learning…


          主站蜘蛛池模板: 日韩最新视频一区二区三| 国产乱人伦精品一区二区| 亚洲一区中文字幕在线观看| 中文字幕Av一区乱码| 亚洲av一综合av一区| 亚洲天堂一区在线| 尤物精品视频一区二区三区| 午夜无码视频一区二区三区| 久久精品国产一区二区| 日韩人妻一区二区三区蜜桃视频| 久久伊人精品一区二区三区| 精产国品一区二区三产区| 一区二区不卡久久精品| 久久国产午夜一区二区福利| 亚洲综合无码一区二区痴汉| 夜色阁亚洲一区二区三区| 亚洲电影一区二区三区| 天堂Av无码Av一区二区三区| 一区二区三区无码视频免费福利| 久久久久人妻一区精品性色av| 国产一区二区三区电影| 国模少妇一区二区三区| 天堂一区二区三区在线观看| 中文字幕一区二区免费| 亚洲一区二区三区国产精品无码| 亚洲国产av一区二区三区| 精品无码av一区二区三区| | 三级韩国一区久久二区综合| 国产亚洲日韩一区二区三区| 国产精品视频免费一区二区| 国产一区二区三区在线看| 性色av闺蜜一区二区三区| 国产在线精品一区二区在线观看| 在线|一区二区三区| 亚洲一区二区三区日本久久九| 视频一区二区三区免费观看 | 久久免费精品一区二区| 暖暖免费高清日本一区二区三区 | 日本精品少妇一区二区三区| 3d动漫精品啪啪一区二区中文 |