亚洲国产日韩人妖另类,久久只有这里有精品热久久,依依成人精品视频在线观看,免费国产午夜视频在线

      
      

        vue3源碼分析-實(shí)現(xiàn)props,emit,事件處理等

        引言

      1. vue3源碼分析——rollup打包monorepo
      2. vue3源碼分析——實(shí)現(xiàn)組件的掛載流程
      3. 本期來實(shí)現(xiàn),setup里面使用props,父子組件通信props和emit等,所有的源碼請(qǐng)查看

        本期的內(nèi)容與上一期的代碼具有聯(lián)動(dòng)性,所以需要明白本期的內(nèi)容,最后是先看下上期的內(nèi)容哦!

        實(shí)現(xiàn)render中的this

        在render函數(shù)中,可以通過this,來訪問setup返回的內(nèi)容,還可以訪問this.$el等

        測(cè)試用例

        由于是測(cè)試dom,jest需要提前注入下面的內(nèi)容,讓document里面有app節(jié)點(diǎn),下面測(cè)試用例類似在html中定義一個(gè)app節(jié)點(diǎn)哦

        let appElement: Element; beforeEach(() => { appElement = document.createElement(‘p’); appElement.id = ‘app’; document.body.appendChild(appElement); }); afterEach(() => { document.body.innerHTML = ”; })復(fù)制代碼

        功能的測(cè)試用例正式開始

        test(‘實(shí)現(xiàn)代理對(duì)象,通過this來訪問’, () => { let that; const app = createApp({ render() { // 在這里可以通過this來訪問 that = this; return h(‘p’, { class: ‘container’ }, this.name); }, setup() { return { name: ‘123’ } } }); const appDoc = document.querySelector(‘#app’) app.mount(appDoc); // 綁定值后的html expect(document.body.innerHTML).toBe(‘123’); const elDom = document.querySelector(‘#container’) // el就是當(dāng)前組件的真實(shí)dom expect(that.$el).toBe(elDom); })復(fù)制代碼

        分析

        上面的測(cè)試用例

      4. setup返回是對(duì)象的時(shí)候,綁定到render的this上面
      5. $el則是獲取的是當(dāng)前組件的真實(shí)dom
      6. 解決這兩個(gè)需求:

      7. 需要在render調(diào)用的時(shí)候,改變當(dāng)前函數(shù)的this指向,但是需要思考的一個(gè)問題是:this是啥,它既要存在setup,也要存在el,咋們是不是可以用一個(gè)proxy來綁定呢?在哪里創(chuàng)建呢 可以在處理組件狀態(tài)setupStatefulComponent來完成改操作
      8. el則是在mountElement中掛載真實(shí)dom的時(shí)候,把當(dāng)前的真實(shí)dom綁定在vnode當(dāng)中
      9. 編碼

        針對(duì)上面的分析,需要在setupStatefulComponent中來創(chuàng)建proxy并且綁定到instance當(dāng)中,并且setup的執(zhí)行結(jié)果如果是對(duì)象,也已經(jīng)存在instance中了,可以通過instance.setupState來進(jìn)行獲取

        function setupStatefulComponent(instance: any) { instance.proxy = new Proxy({}, { get(target, key){ // 判斷當(dāng)前的key是否存在于instance.setupState當(dāng)中 if(key in instance.setupState){ return instance.setupState[key] } } }) // …省略其他}// 然后在setupRenderEffect調(diào)用render的時(shí)候,改變當(dāng)前的this執(zhí)行,執(zhí)行為instance.proxyfunction setupRenderEffect(instance: any, vnode: any, container: any) { // 獲取到vnode的子組件,傳入proxy進(jìn)去 const { proxy } = instance const subtree = instance.render.call(proxy) // …省略其他}復(fù)制代碼

        通過上面的操作,從render中this.xxx獲取setup返回對(duì)象的內(nèi)容就ok了,接下來處理el

        需要在mountElement中,創(chuàng)建節(jié)點(diǎn)的時(shí)候,在vnode中綁定下,el,并且在setupStatefulComponent 中的代理對(duì)象中判斷當(dāng)前的key

        // 代理對(duì)象進(jìn)行修改 instance.proxy = new Proxy({}, { get(target, key){ // 判斷當(dāng)前的key是否存在于instance.setupState當(dāng)中 if(key in instance.setupState){ return instance.setupState[key] }else if(key === ‘$el’){ return instance.vnode.el } } }) // mount中需要在vnode中綁定el function mountElement(vnode: any, container: any) { // 創(chuàng)建元素 const el = document.createElement(vnode.type) // 設(shè)置vnode的el vnode.el = el //…… 省略其他 }復(fù)制代碼

        看似沒有問題吧,但是實(shí)際上是有問題的,請(qǐng)仔細(xì)思考一下,mountElement是不是比setupStatefulComponent 后執(zhí)行,setupStatefulComponent執(zhí)行的時(shí)候,vnode.el不存在,后續(xù)mountelement的時(shí)候,vnode就會(huì)有值,那么上面的測(cè)試用例肯定是報(bào)錯(cuò)的,$el為null

        解決這個(gè)問題的關(guān)鍵,mountElement的加載順序是 render -> patch -> mountElement,并且render函數(shù)返回的subtree是一個(gè)vnode,改vnode中上面是mount的時(shí)候,已經(jīng)賦值好了el,所以在patch后執(zhí)行下操作

        function setupRenderEffect(instance: any, vnode: any, container: any) { // 獲取到vnode的子組件,傳入proxy進(jìn)去 const { proxy } = instance const subtree = instance.render.call(proxy) patch(subtree, container) // 賦值vnode.el,上面執(zhí)行render的時(shí)候,vnode.el是null vnode.el = subtree.el}復(fù)制代碼

        至此,上面的測(cè)試用例就能ok通過啦!

        實(shí)現(xiàn)on+Event注冊(cè)事件

        在vue中,可以使用onEvent來寫事件,那么這個(gè)功能是怎么實(shí)現(xiàn)的呢,咋們一起來看看

        測(cè)試用例

        test(‘測(cè)試on綁定事件’, () => { let count = 0 console.log = jest.fn() const app = createApp({ render() { return h(‘p’, { class: ‘container’, onClick() { console.log(‘click’) count++ }, onFocus() { count– console.log(1) } }, ‘123’); } }); const appDoc = document.querySelector(‘#app’) app.mount(appDoc); const container = document.querySelector(‘.container’) as HTMLElement; // 調(diào)用click事件 container.click(); expect(console.log).toHaveBeenCalledTimes(1) // 調(diào)用focus事件 container.focus(); expect(count).toBe(0) expect(console.log).toHaveBeenCalledTimes(2) })復(fù)制代碼

        分析

        在本功能的測(cè)試用例中,可以分析以下內(nèi)容:

      10. onEvent事件是在props中定義的
      11. 事件的格式必須是 on + Event的格式
      12. 解決問題:

        這個(gè)功能比較簡(jiǎn)單,在處理prop中做個(gè)判斷, 屬性是否滿足 /^on[A-Z]/i這個(gè)格式,如果是這個(gè)格式,則進(jìn)行事件注冊(cè),但是vue3會(huì)做事件緩存,這個(gè)是怎么做到?

        緩存也好實(shí)現(xiàn),在傳入當(dāng)前的el中增加一個(gè)屬性 el._vei || (el._vei = {}) 存在這里,則直接使用,不能存在則創(chuàng)建并且存入緩存

        編碼

        在mountElement中增加處理事件的邏輯 const { props } = vnode for (let key in props) { // 判斷key是否是on + 事件命,滿足條件需要注冊(cè)事件 const isOn = (p: string) => p.match(/^on[A-Z]/i) if (isOn(key)) { // 注冊(cè)事件 el.addEventListener(key.slice(2).toLowerCase(), props[key]) } // … 其他邏輯 el.setAttribute(key, props[key]) }復(fù)制代碼

        事件處理就ok啦

        父子組件通信——props

        父子組件通信,在vue中是非常常見的,這里主要實(shí)現(xiàn)props與emit

        測(cè)試用例

        test(‘測(cè)試組件傳遞props’, () => { let tempProps; console.warn = jest.fn() const Foo = { name: ‘Foo’, render() { // 2. 組件render里面可以直接使用props里面的值 return h(‘p’, { class: ‘foo’ }, this.count); }, setup(props) { // 1. 此處可以拿到props tempProps = props; // 3. readonly props props.count++ } } const app = createApp({ name: ‘App’, render() { return h(‘p’, { class: ‘container’, }, [ h(Foo, { count: 1 }), h(‘span’, { class: ‘span’ }, ‘123’) ]); } }); const appDoc = document.querySelector(‘#app’) app.mount(appDoc); // 驗(yàn)證功能1 expect(tempProps.count).toBe(1) // 驗(yàn)證功能3,修改setup內(nèi)部的props需要報(bào)錯(cuò) expect(console.warn).toBeCalled() expect(tempProps.count).toBe(1) // 驗(yàn)證功能2,在render中可以直接使用this來訪問props里面的內(nèi)部屬性 expect(document.body.innerHTML).toBe(`1123`) })復(fù)制代碼

        分析

        根據(jù)上面的測(cè)試用例,分析props的以下內(nèi)容:

      13. 父組件傳遞的參數(shù),可以給到子組件的setup的第一個(gè)參數(shù)里面
      14. 在子組件的render函數(shù)中,可以使用this來訪問props的值
      15. 在子組件中修改props會(huì)報(bào)錯(cuò),不允許修改
      16. 解決問題:

        問題1: 想要在子組件的setup函數(shù)中第一個(gè)參數(shù),使用props,那么在setup函數(shù)調(diào)用的時(shí)候,把當(dāng)前組件的props傳入到setup函數(shù)中即可 問題2: render中this想要問題,則在上面的那個(gè)代理中,在加入一個(gè)判斷,key是否在當(dāng)前instance的props中 問題3: 修改報(bào)錯(cuò),那就是只能讀,可以使用以前實(shí)現(xiàn)的api shallowReadonly來包裹一下既可

        編碼

        1. 在setup函數(shù)調(diào)用的時(shí)候,傳入instance.props之前,需要在實(shí)例上掛載propsexport function setupComponent(instance) { // 獲取props和children const { props } = instance.vnode // 處理props instance.props = props || {} // ……省略其他 } //2. 在setup中進(jìn)行調(diào)用時(shí)作為參數(shù)賦值 function setupStatefulComponent(instance: any) { // ……省略其他 // 獲取組件的setup const { setup } = Component; if (setup) { // 執(zhí)行setup,并且獲取到setup的結(jié)果,把props使用shallowReadonly進(jìn)行包裹,則是只讀,不能修改 const setupResult = setup(shallowReadonly(instance.props)); // …… 省略其他 }}// 3. 在propxy中在加入判斷 instance.proxy = new Proxy({}, { get(target, key){ // 判斷當(dāng)前的key是否存在于instance.setupState當(dāng)中 if(key in instance.setupState){ return instance.setupState[key] }else if(key in instance.props){ return instance.props[key] }else if(key === ‘$el’){ return instance.vnode.el } } })復(fù)制代碼

        做完之后,可以發(fā)現(xiàn)咋們的測(cè)試用例是運(yùn)行沒有毛病的

        組件通信——emit

        上面實(shí)現(xiàn)了props,那么emit也是少不了的,那么接下來就來實(shí)現(xiàn)下emit

        測(cè)試用例

        test(‘測(cè)試組件emit’, () => { let count; const Foo = { name: ‘Foo’, render() { return h(‘p’, { class: ‘foo’ }, this.count); }, setup(props, { emit }) { // 1. setup對(duì)象的第二個(gè)參數(shù)里面,可以結(jié)構(gòu)出emit,并且是一個(gè)函數(shù) // 2. emit 函數(shù)可以父組件傳過來的事件 emit(‘click’) // 驗(yàn)證emit1,可以執(zhí)行父組件的函數(shù) expect(count.value).toBe(2) // 3 emit 可以傳遞參數(shù) emit(‘clickNum’, 5) // 驗(yàn)證emit傳入?yún)?shù) expect(count.value).toBe(7) // 4 emit 可以使用—的模式 emit(‘click-num’, -5) expect(count.value).toBe(2) } } const app = createApp({ name: ‘App’, render() { return h(‘p’, {}, [ h(Foo, { onClick: this.click, onClickNum: this.clickNum, count: this.count }) ]) }, setup() { const click = () => { count.value++ } count = ref(1) const clickNum = (num) => { count.value = Number(count.value) + Number(num) } return { click, clickNum, count } } }) const appDoc = document.querySelector(‘#app’) app.mount(appDoc); // 驗(yàn)證掛載 expect(document.body.innerHTML).toBe(`1`) })復(fù)制代碼

        分析

        根據(jù)上面的測(cè)試用例,可以分析出:

      17. emit 的參數(shù)是在父組件的props里面,并且是以 on + Event的形式
      18. emit 作為setup的第二個(gè)參數(shù),并且可以結(jié)構(gòu)出來使用
      19. emit 函數(shù)里面是觸發(fā)事件的,事件名稱,事件名稱可以是小寫,或者是 xxx-xxx的形式
      20. emit 函數(shù)的后續(xù)可以傳入多個(gè)參數(shù),作為父組件callback的參數(shù)
      21. 解決辦法: 問題1: emit 是setup的第二個(gè)參數(shù),那么可以在setup函數(shù)調(diào)用的時(shí)候,傳入第二個(gè)參數(shù) 問題2: 關(guān)于emit的第一個(gè)參數(shù),可以做條件判斷,把xxx-xxx的形式轉(zhuǎn)成xxxXxx的形式,然后加入on,最后在props中取找,存在則調(diào)用,不存在則不調(diào)用 問題3:emit的第二個(gè)參數(shù),則使用剩余參數(shù)即可

        編碼

        // 1. 在setup函數(shù)執(zhí)行的時(shí)候,傳入第二個(gè)參數(shù) const setupResult = setup(shallowReadonly(instance.props), { emit: instance.emit });// 2. 在setup中傳入第二個(gè)參數(shù)的時(shí)候,還需要在實(shí)例上添加emit屬性哦export function createComponentInstance(vnode) { const instance = { // ……其他屬性 // emit函數(shù) emit: () => { }, } instance.emit = emit.bind(null, instance); function emit(instance, event, …args) { const { props } = instance // 判斷props里面是否有對(duì)應(yīng)的事件,有的話執(zhí)行,沒有就不執(zhí)行,處理emit的內(nèi)容,詳情請(qǐng)查看源碼 const key = handlerName(capitalize(camize(event))) const handler = props[key] handler && handler(…args) } return instance}復(fù)制代碼

        到此就圓滿成功啦!

        鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
        上一篇 2022年6月19日 15:13
        下一篇 2022年6月19日 15:14

        相關(guān)推薦

        聯(lián)系我們

        聯(lián)系郵箱:admin#wlmqw.com
        工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息