【Ts重構Vue】00-Ts重構Vue前言

爲何重構

本科機械設計製造及其自動化,16年稀裏糊塗的進了一家幹變廠,17年自學了大半年,18年正式跨行來到前端。工做中主要寫業務代碼,不多涉及造輪子工做,一直但願可以提升編程能力。剛好,公司業務棧以vue爲主,理解它的邏輯,相信對從此確定會有幫助。因而就有了使用ts重構vue的衝動。更甚者,但願可以參與到開源社區的建設,努力變得更好。html

Vue的功能仍是很複雜的,源碼也涉及到跨平臺部分,本次僅學習web方向的源碼,指望經過重構引導閱讀,增強體會。前端

重構計劃

使用到的技術棧以下:vue

  1. TypeScript
  2. Jest
  3. es6
  4. rollup

使用TypeScript編寫,使用Jest作單元測試,使用rollup進行構建。這次重構並非徹底的照(拷)搬(貝),將選取經常使用功能去實現。指望最佳的開發模式是:以問題(feature)引領,去閱讀源碼,理解後經過本身的方式去實現。以虛擬DOM爲例,重構過程可能至少分3步實現,一、建立虛擬DOM,二、虛擬DOM映射爲真實DOM,三、給真實DOM設置其餘屬性(style、event等)。node

Vue分爲運行版和完整版,完整版本包含compile模塊,對如<div id="app">{{message}}</div>的模版語法進行了編譯。爲了簡化理解邏輯,筆者重構時直接將compile模塊忽略了,所有經過渲染函數render進行編寫。git

必備基礎知識

  1. 虛擬DOM

談到三大框架,一定要了解虛擬DOM。經過訪問vm._vnode能夠查看vue虛擬DOM的結構,藉助children屬性實現了DOM的樹形結構。es6

虛擬DOM是什麼? 怎麼定義虛擬DOM? 虛擬DOM有什麼好處?github

推薦閱讀snabbdom開源庫,snabbdom的核心很是精簡,總共318行,最關鍵一點,Vue的虛擬DOM是參考它改進的。web

  1. Proxy和Reflect使用

談到Vue的特性,一定要了解數據驅動和響應式。經過訪問app._data能夠查看處理後的data結構。express

Vue源碼使用Object.defineProperty,筆者將使用Proxy進行屬性攔截,以下述代碼,實例化後經過p.name能夠訪問到this.data={name: 'xiaoming'}的屬性。編程

class P {
  constructor () {
    this.data = {name: 'xiaoming'}
    return new Proxy(this, {
      get (target, key) {
        return Reflect.get(target.data, key)
      }
    })
  }
}
let p = new P()
console.log(p.name)
複製代碼
  1. 事件循環(nextTick)

觀察下面demo,在定時器中修改namemessage屬性,能夠發現視圖更新了。此時,runCount的值是多少?若是咱們同步修改了更多的屬性,會影響runCount的值嗎?

let runCount = 0
let vm = new Vue({
  el: '#app',
  data: {
    name: 'xiaoming',
    message: 'Hello Vue!'
  },
  render (h) {
    runCount += 1
    return h('h1', this.name + this.message)
  }
})
setTimeout(() => {
  vm.name = 'xiaohong'
  vm.message = 'Hello world'
}, 1000)

複製代碼

Vue源碼對渲染過程進行了優化,其每次更新都是異步的。此外,你是否在業務中使用到$nextTick,爲何會用到它?瞭解Js的事件循環,有助於理解Vue的更新原理。

定下小目標

  1. 基本使用

觀察下面demo,用戶首先看到Hello Vue!,1秒鐘以後觀看到UI變化Hello world

vue是如何進行渲染的?當修改message值,又是如何進行更新的?

let vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  render (h) {
    return h('h1', this.message)
  }
})

setTimeout(() => {
  vm.message = 'Hello world'
}, 1000)
複製代碼
  1. 事件綁定

觀察下面demo,當用戶點擊按鈕時,點擊次數加1,同時控制檯輸出customClick click

Vue是如何綁定事件的?自定義事件和原生事件的處理方式有何不一樣?

有時候在項目中可能會使用到eventBus,這又是如何實現的?

Vue.component('button-count', {
  data () {
    return {
      count: 0
    }
  },
  render (h) {
    const self = this
    return h('button', {
      on: {
        click () {
          self.count += 1
          self.$emit('customClick', self.count)
        }
      }
    }, `點擊次數:${this.count}`)
  }
})
let vm = new Vue({
  el: '#app',
  render (h) {
    return h('button-count', {
      nativeOn: {
        click () {
          console.log('click')
        }
      },
      on: {
        customClick () {
          console.log('customClick')
        }
      }
    })
  }
})
複製代碼
  1. 組件

咱們定義了button-count組件,當用戶點擊時,組件自動記錄點擊次數並更新視圖。假設用戶點擊了2次,此時的runButtonCountrunCount的值分別是多少?爲何是這樣的?

Vue不只支持自定義組件,也內置了transition/keep-alive等組件,Vue是如何實現組件功能的?父子組件如何進行消息傳遞?

let runButtonCount = 0
let runCount = 0
Vue.component('button-count', {
  data () {
    return {
      count: 0
    }
  },
  render (h) {
    runButtonCount += 1
    const self = this
    return h('button', {
      on: {
        click () {
          self.count += 1
        }
      }
    }, `點擊次數:${this.count}`)
  }
})
let vm = new Vue({
  el: '#app',
  render (h) {
    runCount += 1
    return h('button-count')
  }
})
複製代碼
  1. 指令

觀察下面demo,用戶首先看不到任何文字,1秒鐘以後觀看到111&222&333

Vue不只支持內置指令,也容許用戶自定義指令。指令代碼是如何控制UI的?

ps:新的項目使用vue進行開發,以iframe形式被嵌入在老頁面中。iframe技術仍是很是有效,但咱們發現新項目中的彈窗沒法全屏展現(半透明mask沒法全屏),後來藉助指令解決了問題。推薦開源庫https://github.com/calebroseland/vue-dom-portal。

let vm = new Vue({
  el: '#app',
  data () {
    return  {
        news: [111, 222]
    }
  },
  render (h) {
    const self = this
    return h('h1', {
      directives: [
        {
          name: 'show',
          value: self.news.length > 2,
          expression: 'news.length > 2', 
          arg: '',
          modifiers: { }
        }
      ]
    }, self.news.join('&'))
  }
})

setTimeout(() => {
  vm.news.push(333)
}, 1000)
複製代碼
  1. 插槽

觀察下面demo,頁面最終輸出什麼內容?插槽功能是如何實現的?

Vue.component('app-layout', {
  render (h) {
    const self = this
    return h('div', [
      h('header', [self._t('header')]),
      h('main', [self._t('default', [h('', '默認內容')])]),
      h('footer', [self._t('footer')])
    ])
  }
})

let v = new Vue({
  el: '#app',
  data () {
    return  {
      title: 'hello world!',
      msg: 'msg',
      desc: 'desc'
    }
  },
  render (h) {
    return h('div', [
      h('app-layout', [
        h('h1', {attrs: {slot: 'header'}, slot: 'header'}, this.title),
        h('p', this.msg),
        h('p', {attrs: {slot: 'footer'}, slot: 'footer'}, this.desc)
      ])
    ])
  }
})
複製代碼

頁面渲染後,最終的demo結構以下:

<div>
  <header>
    <h1>hello world!</h1>
  </header>
  <main>
    <p>msg</p>
  </main>
  <footer>
    <p>desc</p>
  </footer>
</div>
複製代碼
  1. 路由(拓展)

待完善

總結

前面咱們定下了不少小目標,接下來就同樣樣去實現。咱們的目標很簡單,就是demo可以按照要求運行。

vue源碼很複雜,有跨平臺代碼(web、weex、server),有性能監控代碼。看源碼時切記不要完美主義,不必必須理解全部的代碼。經過問題主線去閱讀,去了解vue實現的原理。

槓精一下

爲何使用ts?

爲何使用rollup?

推薦閱讀

筆者重構Vue的儲備均來黃老師的兩套課程,再次着重推薦。文章有些demo來自黃老師,不知道是否侵權(若有侵權一定刪除)。

ts學習推薦:coding.imooc.com/class/chapt…

vue源碼學習:ustbhuangyi.github.io/vue-analysi…

系列文章

【Ts重構Vue】00-Ts重構Vue前言

【Ts重構Vue】01-如何建立虛擬節點

【Ts重構Vue】02-數據如何驅動視圖變化

【Ts重構Vue】03-如何給真實DOM設置樣式

【Ts重構Vue】04-異步渲染

【Ts重構Vue】05-實現computed和watch功能

相關文章
相關標籤/搜索