⑴ vue3源碼分析-實現props,emit,事件處理等
>
本期來實現, setup裡面使用props,父子組件通信props和emit等 ,所有的源碼請查看
在render函數中, 可以通過this,來訪問setup返回的內容,還可以訪問this.$el等
由於是測試dom,jest需要提前注入下面的內容,讓document裡面有app節點,下面測試用例類似在html中定義一個app節點哦
本功能的測試用例正式開始
上面的測試用例
解決這兩個需求:
針對上面的分析,需要在setupStatefulComponent中來創建proxy並且綁定到instance當中,並且setup的執行結果如果是對象,也已經存在instance中了,可以通過instance.setupState來進行獲取
通過上面的操作,從render中this.xxx獲取setup返回對象的內容就ok了,接下來處理el
需要在mountElement中,創建節點的時候,在vnode中綁定下,el,並且在setupStatefulComponent 中的代理對象中判斷當前的key
看似沒有問題吧,但是實際上是有問題的,請仔細思考一下, mountElement是不是比setupStatefulComponent 後執行,setupStatefulComponent執行的時候,vnode.el不存在,後續mountelement的時候,vnode就會有值,那麼上面的測試用例肯定是報錯的,$el為null
解決這個問題的關鍵,mountElement的載入順序是 render -> patch -> mountElement,並且render函數返回的subtree是一個vnode,改vnode中上面是mount的時候,已經賦值好了el,所以在patch後執行下操作
在vue中,可以使用onEvent來寫事件,那麼這個功能是怎麼實現的呢,咋們一起來看看
在本功能的測試用例中,可以分析以下內容:
解決問題:
這個功能比較簡單,在處理prop中做個判斷, 屬性是否滿足 /^on[A-Z]/i這個格式,如果是這個格式,則進行事件注冊,但是vue3會做事件緩存,這個是怎麼做到?
緩存也好實現,在傳入當前的el中增加一個屬性 el._vei || (el._vei = {}) 存在這里,則直接使用,不能存在則創建並且存入緩存
事件處理就ok啦
父子組件通信,在vue中是非常常見的,這里主要實現props與emit
根據上面的測試用例,分析props的以下內容:
解決問題:
問題1: 想要在子組件的setup函數中第一個參數, 使用props,那麼在setup函數調用的時候,把當前組件的props傳入到setup函數中即可 問題2: render中this想要問題,則在上面的那個代理中,在 加入一個判斷,key是否在當前instance的props中 問題3: 修改報錯,那就是只能讀,可以使用以前實現的 api shallowReadonly來包裹一下 既可
做完之後,可以發現咋們的測試用例是運行沒有毛病的
上面實現了props,那麼emit也是少不了的,那麼接下來就來實現下emit
根據上面的測試用例,可以分析出:
解決辦法: 問題1: emit 是setup的第二個參數, 那麼可以在setup函數調用的時候,傳入第二個參數 問題2: 關於emit的第一個參數, 可以做條件判斷,把xxx-xxx的形式轉成xxxXxx的形式,然後加入on,最後在props中取找,存在則調用,不存在則不調用 問題3:emit的第二個參數, 則使用剩餘參數即可
到此就圓滿成功啦!
⑵ 【面試題解析】從 Vue 源碼分析 key 的作用
最近看了面試題中有一個這樣的題, v-for 為什麼要綁定 key?
Vue 中 key 很多人都弄不清楚有什麼作用,甚至還有些人認為不綁定 key 就會報錯。
其實沒綁定 key 的話,Vue 還是可以正常運行的,報警告是因為沒通過 Eslint 的檢查。
接下來將通過源碼一步步分析這個 key 的作用。
Virtual DOM 最主要保留了 DOM 元素的層級關系和一些基本屬性,本質上就是一個 JS 對象。相對於真實的 DOM,Virtual DOM 更簡單,操作起來速度更快。
如果需要改變 DOM,則會通過新舊 Virtual DOM 對比,找出需要修改的節點進行真實的 DOM 操作,從而減小性能消耗。
傳統的 Diff 演算法需要遍歷一個樹的每個節點,與另一棵樹的每個節點對比,時間復雜度為 O(n²)。
Vue 採用的 Diff 演算法則通過逐級對比,大大降低了復雜性,時間復雜度為 O(n)。
VNode 更新首先會經過 patch 函數, patch 函數源碼如下:
vnode 表示更新後的節點,oldVnode 表示更新前的節點,通過對比新舊節點進行操作。
1、vnode 未定義,oldVnode 存在則觸發 destroy 的鉤子函數
2、oldVnode 未定義,則根據 vnode 創建新的元素
3、oldVnode 不為真實元素並且 oldVnode 與 vnode 為同一節點,則會調用 patchVnode 觸發更新
4、oldVnode 為真實元素或者 oldVnode 與 vnode 不是同一節點,另做處理
接下來會進入 patchVnode 函數,源碼如下:
1、vnode 的 text 不存在,則會比對 oldVnode 與 vnode 的 children 節點進行更新操作
2、vnode 的 text 存在,則會修改 DOM 節點的 text
接下來在 updateChildren 函數內就可以看到 key 的用處。
key 的作用主要是給 VNode 添加唯一標識,通過這個 key,可以更快找到新舊 VNode 的變化,從而進一步操作。
key 的作用主要表現在以下這段源碼中。
updateChildren 過程為:
1、分別用兩個指針(startIndex, endIndex)表示 oldCh 和 newCh 的頭尾節點
2、對指針所對應的節點做一個兩兩比較,判斷是否屬於同一節點
3、如果4種比較都沒有匹配,那麼判斷是否有 key,有 key 就會用 key 去做一個比較;無 key 則會通過遍歷的形式進行比較
4、比較的過程中,指針往中間靠,當有一個 startIndex > endIndex,則表示有一個已經遍歷完了,比較結束
從 VNode 的渲染過程可以得知,Vue 的 Diff 演算法先進行的是同級比較,然後再比較子節點。
子節點比較會通過 startIndex、endIndex 兩個指針進行兩兩比較,再通過 key 比對子節點。如果沒設置 key,則會通過遍歷的方式匹配節點,增加性能消耗。
所以不綁定 key 並不會有問題,綁定 key 之後在性能上有一定的提升。
綜上,key 主要是應用在 Diff 演算法中,作用是為了更快速定位出相同的新舊節點,盡量減少 DOM 的創建和銷毀的操作。
希望以上內容能夠對各位小夥伴有所幫助,祝大家面試順利。
Vue 的文檔中對 key 的說明如下:
關於就地修改,關鍵在於 sameVnode 的實現,源碼如下:
可以看出,當 key 未綁定時,主要通過元素的標簽等進行判斷,在 updateChildren 內會將 oldStartVnode 與 newStartVnode 判斷為同一節點。
如果 VNode 中只包含了文本節點,在 patchVnode 中可以直接替換文本節點,而不需要移動節點的位置,確實在不綁定 key 的情況下效率要高一丟丟。
某些情況下不綁定 key 的效率更高,那為什麼大部分Eslint的規則還是要求綁定 key 呢?
因為在實際項目中,大多數情況下 v-for 的節點內並不只有文本節點,那麼 VNode 的位元組點就要進行銷毀和創建的操作。
相比替換文本帶來的一丟丟提升,這部分會消耗更多的性能,得不償失。
了解了就地修改,那麼我們在一些簡單節點上可以選擇不綁定 key,從而提高性能。
如果你喜歡我的文章,希望可以關注一下我的公眾號【前端develop】
⑶ Vue組件中prop屬性使用說明實例代碼詳解
Prop
的大小寫
(camelCase
vs
kebab-case)
HTML
中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字元解釋為小寫字元。這意味著當你使用
DOM
中的模板時,camelCase
(駝峰命名法)
的
prop
名需要使用其等價的
kebab-case
(短橫線分隔命名)
命名:
Vue.component('blog-post',
{
//
在
JavaScript
中是
camelCase
的
props:
['postTitle'],
template:
'<h3>{{
postTitle
}}</h3>'
})
<!--
在
HTML
中是
kebab-case
的
-->
<blog-post
post-title="hello!"></blog-post>
重申橋岩一次,如果你使用字元串模板,那麼這個限制就不存在了。
靜態的和動態的
Prop
像這樣,你已經知道了可以像這樣給
prop
傳入一個靜態的值:
<blog-post
title="My
journey
with
Vue"></blog-post>
你也知道
prop
可以通過
v-bind
動態賦值,例如:
<blog-post
v-bind:title="post.title"></blog-post>
在上述兩個示例中,我們傳入的值都是字元串類型的,但實際上任何類型的值都可以傳給一個
prop。
傳入一個數字
<!--
即便
`42`
是靜態的,我們仍然需要
`v-bind`
來告訴
Vue
-->
<!--
這是一個
JavaScript
表達式而不是一個字元串。-->
<blog-post
v-bind:likes="42"></blog-post>
<!--
用一個變數進行動態賦值。-->
<blog-post
v-bind:likes="post.likes"></blog-post>
傳入一個布爾值
<!--
包含該
prop
沒有值的情況在內,都意味著
`true`。-->
<blog-post
favorited></blog-post>
<!--
即便
`false`
是靜態的,我們仍然需要
`v-bind`
來告訴
Vue
-->
<!--
這是一個
JavaScript
表達式而不是一個字元串。-->
<base-input
v-bind:favorited="false">
<!--
用一個變數進行動態賦值。-->
<base-input
v-bind:favorited="post.currentUserFavorited">
傳入一個數組
<!--
即便數組是靜態的,我們仍然需要
`v-bind`
來告訴
Vue
-->
<!--
這是一個
JavaScript
表達式而不是一個字元串。-->
<blog-post
v-bind:comment-ids="[234,
266,
273]"></blog-post>
<!--
用一個變數進行動態賦值。-->
<blog-post
v-bind:comment-ids="post.commentIds"></blog-post>
傳入一個對象
<!--
即便對象是靜態的,我們仍然需要
`v-bind`
來告訴
Vue
-->
<!--
這是一個
JavaScript
表達式而不是一個字元串。-->
<blog-post
v-bind:comments="{
id:
1,
title:
'My
Journey
with
Vue'
}"></blog-post>
<!--
用一個變數進行動態賦值。-->
<blog-post
v-bind:post="post"></blog-post>
傳入一個對象的所有屬性
如果你想要將一個對象的所有屬性都作為
prop
傳入,你可以使用不帶參數的
v-bind
(取代
v-bind:prop-name)。例如,對於一個給斗漏定的對象
post:
post:
{
id:
1,
title:
'My
Journey
with
Vue'
}
下面的模板:
<blog-post
v-bind="post"></blog-post>
等價於:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
Vue的組件中的props屬性單向數據流
所有的
prop都使得其父子prop之間形成了一個單向下行綁定:父級
prop
的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
額外的,每次父級組件發生更新時,子組件中所有的
prop
都將會刷新為最新的值。這意味著你不應該在一個子組件內部改變
prop。如果你這樣做了,Vue
會在瀏覽器的控制台中發出警告。
這敏銷御里有兩種常見的試圖改變一個
prop
的情形:
這個
prop
用來傳遞一個初始值;這個子組件接下來希望將其作為一個本地的
prop
數據來使用。在這種情況下,最好定義一個本地的
data
屬性並將這個
prop
用作其初始值:
props:
['initialCounter'],
data:
function
()
{
return
{
counter:
this.initialCounter
}
}
這個
prop
以一種原始的值傳入且需要進行轉換。在這種情況下,最好使用這個
prop
的值來定義一個計算屬性:
props:
['size'],
computed:
{
normalizedSize:
function
()
{
return
this.size.trim().toLowerCase()
}
}
注意在
JavaScript
中對象和數組是通過引用傳入的,所以對於一個數組或對象類型的
prop
來說,在子組件中改變這個對象或數組本身將會影響到父組件的狀態。
Prop
驗證
我們可以為組件的
prop
指定需求。如果有一個需求沒有被滿足,則
Vue
會在瀏覽器控制台中警告你。這在開發一個會被別人用到的組件時尤其有幫助。
為了定製
prop
的驗證方式,你可以為
props
中的值提供一個帶有驗證需求的對象,而不是一個字元串數組。例如:
Vue.component('my-component',
{
props:
{
//
基礎的類型檢查
(`null`
匹配任何類型)
propA:
Number,
//
多個可能的類型
propB:
[String,
Number],
//
必填的字元串
propC:
{
type:
String,
required:
true
},
//
帶有默認值的數字
propD:
{
type:
Number,
default:
100
},
//
帶有默認值的對象
propE:
{
type:
Object,
//
對象或數組且一定會從一個工廠函數返回默認值
default:
function
()
{
return
{
message:
'hello'
}
}
},
//
自定義驗證函數
propF:
{
validator:
function
(value)
{
//
這個值必須匹配下列字元串中的一個
return
['success',
'warning',
'danger'].indexOf(value)
!==
-1
}
}
}
})
當
prop
驗證失敗的時候,(開發環境構建版本的)
Vue
將會產生一個控制台的警告。
注意那些
prop
會在一個組件實例創建之前進行驗證,所以實例的屬性
(如
data、computed
等)
在
default
或
validator
函數中是不可用的。
類型檢查
type
可以是下列原生構造函數中的一個:
String
Number
Boolean
Function
Object
Array
Symbol
額外的,type
還可以是一個自定義的構造函數,並且通過
instanceof
來進行檢查確認。例如,給定下列現成的構造函數:
function
Person
(firstName,
lastName)
{
this.firstName
=
firstName
this.lastName
=
lastName
}
你可以使用:
Vue.component('blog-post',
{
props:
{
author:
Person
}
})
來驗證
author
prop
的值是否是通過
new
Person
創建的。
非
Prop
的特性
一個非
prop
特性是指傳向一個組件,但是該組件並沒有相應
prop
定義的特性。
因為顯式定義的
prop
適用於向一個子組件傳入信息,然而組件庫的作者並不總能預見組件會被用於怎樣的場景。這也是為什麼組件可以接受任意的特性,而這些特性會被添加到這個組件的根元素上。
例如,想像一下你通過一個
Bootstrap
插件使用了一個第三方的
組件,這個插件需要在其
上用到一個
data-date-picker
特性。我們可以將這個特性添加到你的組件實例上:
<bootstrap-date-input
data-date-picker="activated"></bootstrap-date-input>
然後這個
data-date-picker=」activated」
特性就會自動添加到<bootstrap-date-input>
的根元素上。
替換/合並已有的特性
想像一下
<bootstrap-date-input>
的模板是這樣的:
<input
type="date"
class="form-control">
為了給我們的日期選擇器插件定製一個主題,我們可能需要像這樣添加一個特別的類名:
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
在這種情況下,我們定義了兩個不同的
class
的值:
form-control,這是在組件的模板內設置好的
date-picker-theme-dark,這是從組件的父級傳入的
對於絕大多數特性來說,從外部提供給組件的值會替換掉組件內部設置好的值。所以如果傳入
type=」text」
就會替換掉
type=」date」
並把它破壞!慶幸的是,class
和
style
特性會稍微智能一些,即兩邊的值會被合並起來,從而得到最終的值:form-control
date-picker-theme-dark。
禁用特性繼承
如果你不希望組件的根元素繼承特性,你可以設置在組件的選項中設置
inheritAttrs:
false。例如:
Vue.component('my-component',
{
inheritAttrs:
false,
//
...
})
這尤其適合配合實例的
$attrs
屬性使用,該屬性包含了傳遞給一個組件的特性名和特性值,例如:
{
class:
'username-input',
placeholder:
'Enter
your
username'
}
有了
inheritAttrs:
false
和
$attrs,你就可以手動決定這些特性會被賦予哪個元素。在撰寫基礎組件的時候是常會用到的:
Vue.component('base-input',
{
inheritAttrs:
false,
props:
['label',
'value'],
template:
`
<label>
{{
label
}}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input',
$event.target.value)"
>
</label>
`
})
這個模式允許你在使用基礎組件的時候更像是使用原始的
HTML
元素,而不會擔心哪個元素是真正的根元素:
<base-input
v-model="username"
class="username-input"
placeholder="Enter
your
username"
></base-input>
總結
以上所述是小編給大家介紹的Vue組件中prop屬性使用說明,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
您可能感興趣的文章:VueJs組件prop驗證簡單介紹Vue組件選項props實例詳解vue父組件通過props如何向子組件傳遞方法詳解詳解vue2父組件傳遞props非同步數據到子組件的問題詳解vue父子組件間傳值(props)Vue2.0利用
v-model
實現組件props雙向綁定的優美解決方案
⑷ Vue源碼系列之生命鉤子beforeCreate&created
在實習面試中,Vue相關被問到最多的就是圍繞生命周期函數內部所作的一些操作,很多朋友可能了解的就是官網的下圖,有時候倒霉遇上硬核點的面試官,深入問些東西就會被懟的啞口無言,本文就是在之前源碼的基礎上,分析一下,生命周期函數究竟發生了什麼
先上圖
在分析每個鉤子之間指坦究竟幹了什麼之前,先來看看鉤子是怎麼觸發的,以第一個鉤子為例
調用callHook函數並向其傳入this和'beforeCreate'字元串,那來看看callHook函數究竟是何方神聖
注意到,該函數一開始,也就是鉤子函數進入准備觸發前,進行了一個pushTarget()的操作,注釋寫的是「在鉤子函數觸發時,禁用依賴收集」,那這個操作是幹嘛呢
言歸正傳,禁用了依賴收集後,創建handlers數組存入合並後options的hook,本例中就是找自定義或者繼承來的beforeCreate鉤子,然後在invokeWithErroeHandling函數中以此觸發,順序是先觸發父級,後自己定義的
而這個invokeWithErroeHandling函數如下
該函數直接就在內部call了鉤子,所以鉤子內部的this指向vm實例
以上就是鉤子函數的觸發過程,下面來看不同的生命周期之間究竟幹了什麼
beforeCreate之前
beforeCreate之後到created
1.對於props:合法化,緩存key進數組方便下次迭代,defineReactive
2.對於data:檢驗props,methods中是否有重名屬性,defineReactive
3.對於methods:代理到vm實例上,方便使用this.method.name調用
4.對於computed:封裝成watcher並用該watcher的value緩存螞兆該計算屬性的value,再在每個計算屬性上劫持一層getter和setter,在第一次調用getter的時候,取得最新悶逗租的value,並將依賴緩存下來,之後再依賴不變的前提下,getter只返回watcher的value而不是又去取一遍值,再依賴發生變化的時候,通知watcher更新,watcher取的最新值作為value,從而實現依賴更新計算屬性才更新
5.對於watch:調用$watch封裝成一個user watcher,如果有immediate options傳入,就在封裝的時候就調用一遍callback,有deep options傳進來的話就將該屬性的所有嵌套屬性記為依賴
beforeCreate之前主要是做准備工作,將該實例的options合並整理出來,再把$那些初始話
created之前就是對options做操作,data,props設置數據劫持,methods代理在vm實例上,computed,watch封裝成不同類型的watcher
⑸ Vue中的props理解
Vue實例中的data屬性是一個對象,然而組件中的data屬性是一個函數。這是因為一個組件可以在同一個頁面上被多次引用,你大概不枝鎮希望他們共享一個data對象(因為同一個組件的每個實例的data屬性是同一個對象的引用,當該組件的某個實例修改了自身的data屬性,相當於所有組件的data屬性都被修改了。)所以組件的data屬性應該是一個函數,在組件初始化是Vue會調用這個函數來生成data對象。
App.vue
DisplayNumber.vue
App.vue
再看另外一個例子:
App.vue
結果:
DisplayNumber.vue
將Javascript表達式(Javascript expression)和HTML 元素和組件(HTML elements and components)綁定起來。
在大多數時候,可以簡寫為:
看例子:
HTML 中的 attribute 名是大小寫不敏感的,所以瀏覽器會把所有大寫字元解釋為小寫字元。這意味著當你使用 DOM 中的模板時猛叢粗,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名。
在HTML中通鄭稿過kebab-case形式指定的屬性,會在組件內部自動轉換為camelCase 形式。我們不需要作任何額外的處理,這種轉換會自動完成。
官方推薦,始終使用 kebab-case(短橫線隔開) 的事件名。
⑹ 面試中的網紅Vue源碼解析之虛擬DOM,你知多少呢深入解讀diff演算法
眾所周知,在前端的面試中,面試官非常愛考dom和diff演算法。比如,可能會出現在以下場景
滴滴滴,面試官發來一個面試邀請。接受邀請📞
我們都知道, key 的作用在前端的面試是一道很普遍的題目,但是呢,很多時候我們都只浮於知識的表面,而沒有去深挖其原理所在,這個時候我們的競爭力就在這被拉下了。所以呢,深入學習原理對於提升自身的核心競爭力是一個必不可少的過程。
在接下來的這篇文章中,我們將講解面試中很愛考的虛擬DOM以及其背後的diff演算法。 請認真閱讀本文~文末有學習資源免費共享!!!
虛擬DOM是用JavaScript對象描述DOM的層次結構。DOM中的一切屬性都在虛擬DOM中有對應的屬性。本質上是JS 和 DOM 之間的一個映射緩存。
要點:虛擬 DOM 是 JS 對象;虛擬 DOM 是對真實 DOM 的描述。
diff發生在虛擬DOM上。diff演算法是在新虛擬DOM和老虛擬DOM進行diff(精細化比對),實現最小量更新,最後反映到真正的DOM上。
我們前面知道diff演算法發生在虛擬DOM上,而虛擬DOM是如何實現的呢?實際上虛擬DOM是有一個個虛擬節點組成。
h函數用來產生虛擬節點(vnode)。虛擬節點有如下的屬性:
1)sel: 標簽類型,例如 p、div;
2)data: 標簽上的數據,例如 style、class、data-*;
3)children :子節點;
4) text: 文本內容;
5)elm:虛擬節點綁定的真實 DOM 節點;
通過h函數的嵌套,從而得到虛擬DOM樹。
我們編寫了一個低配版的h函數,必須傳入3個參數,重載較弱。
形態1:h('div', {}, '文字')
形態2:h('div', {}, [])
形態3:h('div', {}, h())
首先定義vnode節點,實際上就是把傳入的參數合成對象返回。
[圖片上傳失敗...(image-7a9966-1624019394657)]
然後編寫h函數,根據第三個參數的不同進行不同的響應。
當我們進行比較的過程中,我們採用的4種命中查找策略:
1)新前與舊前:命中則指針同時往後移動。
2)新後與舊後:命中則指針同時往前移動。
3)新後與舊前:命中則涉及節點移動,那麼新後指向的節點,移到 舊後之後 。
4)新前與舊後:命中則涉及節點移動,那麼新前指向的節點,移到 舊前之前 。
命中上述4種一種就不在命中判斷了,如果沒有命中,就需要循環來尋找,移動到舊前之前。直到while(新前<=新後&&舊前<=就後)不成立則完成。
如果是新節點先循環完畢,如果老節點中還有剩餘節點(舊前和舊後指針中間的節點),說明他們是要被刪除的節點。
如果是舊節點先循環完畢,說明新節點中有要插入的節點。
1.什麼是Virtual DOM 和Snabbdom
2.手寫底層源碼h函數
3.感受Vue核心演算法之diff演算法
4.snabbdom之核心h函數的工作原理
1、零基礎入門或者有一定基礎的同學、大中院校學生
2、在職從事相關工作1-2年以及打算轉行前端的朋友
3、對前端開發有興趣人群