1. 程式人生 > 其它 >【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)

【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)

【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)

【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)

一、元件通訊 provide 和 inject

官方文件:https://cn.vuejs.org/v2/api/#provide-inject

例項:元件通訊 provide 和 inject

<body>
  <div id="root">
    <Test></Test>
  </div>
  <script>
    function registerPlugin() {
      Vue.component('Test', {
        template: '<div>{{message}}<Test2 /></div>',
        provide() {
          return {
            elTest: this
          }
        }, // function 的用途是為了獲取執行時環境,否則 this 將指向 window
        data() {
          return {
            message: 'message from Test'
          }
        },
        methods: {
          change(component) {
            this.message = 'message from ' + component
          }
        }
      })
      Vue.component('Test2', {
        template: '<Test3 />'
      })
      Vue.component('Test3', {
        template: '<button @click="changeMessage">change</button>',
        inject: ['elTest'],
        methods: {
          changeMessage() {
            this.elTest.change(this.$options._componentTag)
          }
        }
      })
    }
    Vue.use(registerPlugin)
    new Vue({
      el: '#root'
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

先來看一下官方文件中的說明:

型別:

  • provide:Object | () => Object
  • inject:Array< string > | { [key: string]: string | Symbol | Object }

詳細:

  • 這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效。
  • provide 選項應該是一個物件或返回一個物件的函式。該物件包含可注入其子孫的屬性。
  • inject 選項應該是:
    • 一個字串陣列,或一個物件,物件的 key 是本地的繫結名,value 是:
      • 在可用的注入內容中搜索用的 key (字串或 Symbol),或
      • 一個物件,該物件的:
        • from 屬性是在可用的注入內容中搜索用的 key (字串或 Symbol)
        • default 屬性是降級情況下使用的 value

PS:provide 和 inject 主要在開發高階外掛/元件庫時使用。並不推薦用於普通應用程式程式碼中。

示例程式碼解析:

  • 可以說一個元件它的內部資源是私有的,子孫也不可及,通過provide
    將它的屬性/方法/資料/…,甚至它自身(this)暴露出去,提供給子孫後代使用:
provide() {
	return {
	    elTest: this
	}
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • this:VueComponent(祖先元件:Test)
inject: ['elTest'],
  • 1
this.elTest.change(this.$options._componentTag)
  • 1
  • this:VueComponent(孫輩元件:Test3)
  • this.elTest:VueComponent(祖先元件:Test)
  • this.$options._componentTag:這裡的this指的是呼叫change方法的this.elTest,也就是祖先元件:Test

二、過濾器 filter

官方文件:https://cn.vuejs.org/v2/api/#Vue-filter
官方文件:https://cn.vuejs.org/v2/guide/filters.html

例項:過濾器 filter

<body>
  <div id="root">
    {{message | lower}}
  </div>
  <script>
    new Vue({
      el: '#root',
      filters: {
        lower(value) {
          return value.toLowerCase()
        }
      },
      data() {
        return {
          message: 'Hello Vue'
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

先來看一下官方文件中的說明:

Vue.js 允許自定義過濾器,可被用於一些常見的文字格式化。
過濾器可以用在兩個地方:雙花括號插值和 v-bind 表示式 (後者從 2.1.0+ 開始支援)。
過濾器應該被新增在 JavaScript 表示式的尾部,由“管道”符號指示:

<!-- 在雙花括號中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
  • 1
  • 2
  • 3
  • 4
  • 5

可以在一個元件的選項中定義本地的過濾器
或者在建立 Vue 例項之前全域性定義過濾器
當全域性過濾器和區域性過濾器重名時,會採用區域性過濾器。
過濾器可以串聯:

{{ message | filterA | filterB }}
  • 1

過濾器是 JavaScript 函式,因此可以接收引數:

{{ message | filterA('arg1', arg2) }}
  • 1

這裡,filterA 被定義為接收三個引數的過濾器函式。其中 message 的值作為第一個引數,普通字串 ‘arg1’ 作為第二個引數,表示式 arg2 的值作為第三個引數。

filters執行前後過程分析(by call stack):

  • Vue,_init:實現vue初始化
  • 在Vue.$mount中通過mountComponent例項化一個Watcher物件
  • 接下來呼叫updateComponent方法
  • 方法內執行Vue._render函式
  • 函式通過對template解析
  • 然後解析過程中對傳進來的引數進行指定的過濾處理
  • 最後將結果返回,渲染

具體解析:傳送門:filter原始碼詳解

三、監聽器 watch

官方文件:https://cn.vuejs.org/v2/api/#watch
官方文件:https://cn.vuejs.org/v2/api/#vm-watch

例項:監聽器 watch

Watch 用法1:常見用法

<body>
  <div id="root">
    <h3>Watch 用法1:常見用法</h3>
    <input v-model="message">
    <span>{{copyMessage}}</span>
  </div>
  <script>
    new Vue({
      el: '#root',
      watch: {
        message(value) {
          this.copyMessage = value
        }
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

鍵值一體,鍵為message,值為message()方法

message(value) {
	this.copyMessage = value
}
  • 1
  • 2
  • 3

監聽器的一些預設值:


Watch 用法2:繫結方法

<body>
  <div id="root2">
    <h3>Watch 用法2:繫結方法</h3>
    <input v-model="message">
    <span>{{copyMessage}}</span>
  </div>
  <script>
    new Vue({
      el: '#root2',
      watch: {
        message: 'handleMessage'
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

鍵為message,值為’handleMessage()方法,每次監聽到message變化,'handleMessage()方法就會執行一次

PS:雙向繫結的值(v-model="message"data() {return {message}})和watch監聽的鍵要保持一致,同為message


Watch 用法3:deep + handler

<body>
  <div id="root3">
    <h3>Watch 用法3:deep + handler</h3>
    <input v-model="deepMessage.a.b">
    <span>{{copyMessage}}</span>
  </div>
  <script>
    new Vue({
      el: '#root3',
      watch: {
        deepMessage: {
          handler: 'handleDeepMessage',
          deep: true
        }
      },
      data() {
        return {
          deepMessage: {
            a: {
              b: 'Deep Message'
            }
          },
          copyMessage: ''
        }
      },
      methods: {
        handleDeepMessage(value) {
          this.copyMessage = value.a.b
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

預設情況下 watch方法只監聽data中的物件,而無法監聽到物件內部屬性的改變,此時就需要deep屬性對物件進行深度監聽。(預設:deep:false)

這個案例可以看出:雙向繫結的值(v-model="deepMessage.a.b"deepMessage:{a: {b:'Deep Message'}},)保持一致;watch不能直接監聽deepMessage.a.b,而是需要通過handlerdeep屬性來完成監聽


Watch 用法4:immediate

<body>
  <div id="root">
  <div id="root4">
    <h3>Watch 用法4:immediate</h3>
    <input v-model="message">
    <span>{{copyMessage}}</span>
  </div>
  <script>
    new Vue({
      el: '#root4',
      watch: {
        message: {
          handler: 'handleMessage',
          immediate: true,
        }
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

watch預設情況下在頁面首次渲染時,即使監聽的值有初始值,也不會直接執行,這種情況下想要第一次渲染後直接監聽就需要新增屬性:immediate: true

initWatch

createWatch


Watch 用法5:繫結多個 handler

<body>
  <div id="root5">
    <h3>Watch 用法5:繫結多個 handler</h3>
    <input v-model="message">
    <span>{{copyMessage}}</span>
  </div>
  <script>
    new Vue({
      el: '#root5',
      watch: {
        message: [{
          handler: 'handleMessage',
        },
        'handleMessage2',
        function(value) {
          this.copyMessage = this.copyMessage + '...'
        }]
      },
      data() {
        return {
          message: 'Hello Vue',
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        },
        handleMessage2(value) {
          this.copyMessage = this.copyMessage + '*'
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

監聽值為多個,需要用陣列形式:

  • 值為物件:執行物件的handler屬性值對應方法handleMessage
  • 值為字串:執行字串對應方法handleMessage2
  • 值為方法:直接執行方法

先監聽的先執行,各自獨立,每個都是獨立的監聽器

本示例中value都是一致的,只有handleMessage獲取了這個value,其他監聽器處理的都是上一步處理過的copyMessage

若多個監聽器監聽同一個物件,那麼只會渲染最後一次處理結果


Watch 用法6:監聽物件屬性

<body>
  <div id="root6">
    <h3>Watch 用法6:監聽物件屬性</h3>
    <input v-model="deepMessage.a.b">
    <span>{{copyMessage}}</span>
  </div>
    
  <script>
    new Vue({
      el: '#root6',
      watch: {
        'deepMessage.a.b': 'handleMessage'
      },
      data() {
        return {
          deepMessage: { a: { b: 'Hello Vue' } },
          copyMessage: ''
        }
      },
      methods: {
        handleMessage(value) {
          this.copyMessage = value
        }
      }
    })
  </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

只監聽物件的某少數個屬性值時,可以用物件.屬性字串形式進行監聽


Vue.js 原始碼分析(七) 基礎篇 偵聽器 watch屬性詳解

四、class 和 style 繫結的高階用法

官方文件:https://cn.vuejs.org/v2/guide/class-and-style.html

例項:class 和 style 繫結的高階用法

<body>
    <div id="root">
      <div :class="['active', 'normal']">陣列繫結多個class</div>
      <div :class="[{active: isActive}, 'normal']">陣列包含物件繫結class</div>
      <div :class="[showWarning(), 'normal']">陣列包含方法繫結class</div>
      <div :style="[warning, bold]">陣列繫結多個style</div>
      <div :style="[warning, mix()]">陣列包含方法繫結style</div>
      <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">style多重值</div>
    </div>
    <script>
      new Vue({
        el: '#root',
        data() {
          return {
            isActive: true,
            warning: {
              color: 'orange'
            },
            bold: {
              fontWeight: 'bold'
            }
          }
        },
        methods: {
          showWarning() {
            return 'warning'
          },
          mix() {
            return {
              ...this.bold,
              fontSize: 20
            }
          }
        }
      })
    </script>
  </body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

陣列繫結多個class

 :class="'active normal'"
 :class="'active'+'normal'"
 :class="['active', 'normal']"	// 更靈活
  • 1
  • 2
  • 3

陣列包含物件繫結class

 :class="[{active: isActive}, 'normal']"	// 老寫法:class="(isActive?'active':'') + 'normal'"
  • 1

陣列包含方法繫結class

 :class="[showWarning(), 'normal']"
  • 1

陣列繫結多個style

:style="[warning, bold]"	// 這裡的每個陣列元素都是物件而不是字串
  • 1

陣列包含方法繫結style

:style="[warning, mix()]"
  • 1

style多重值

:style="{display:['-webkit-box','-ms-flexbox','flex']}"//從後往前,相容哪個匹配哪個
  • 1

五、Vue2.6 新特性

1.Vue.observable

官方文件:https://cn.vuejs.org/v2/api/#Vue-observable

例項:Vue.observable

<body>
    <div id="root">
      {{message}}
      <button @click="change">Change</button>
    </div>
    <script>
      const state = Vue.observable({ message: 'Vue 2.6' })
      const mutation = {
        setMessage(value) {
          state.message = value
        }
      }
      new Vue({
        el: '#root',
        computed: {
          message() {
            return state.message
          }
        },
        methods: {
          change() {
            mutation.setMessage('Vue 3.0')
          }
        }
      })
    </script>
  </body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

官方解釋:

Vue.observable(object) 是vue2.6版本新增的全域性API,它可以讓一個物件可響應。Vue 內部會用它來處理 data 函式返回的物件。
返回的物件可以直接用於渲染函式和計算屬性內,並且會在發生改變時觸發相應的更新。也可以作為最小化的跨元件狀態儲存器,用於簡單的場景

在 Vue 2.x 中,被傳入的物件會直接被 Vue.observable 改變;在 Vue 3.x 中,則會返回一個可響應的代理,而對源物件直接進行修改仍然是不可響應的。因此,為了向前相容,官方推薦始終操作使用 Vue.observable 返回的物件,而不是傳入源物件。

通俗來說,Vue.observable在簡單場景下可以代替vuex

本示例中Vue.observable執行流程:

  • 首先initGlobalAPI (vue.js:5406)
  • 執行Vue.observable內容
// 2.6 explicit observable API
Vue.observable = function (obj) {
  observe(obj);
  return obj
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • vue初始化
  • 使用計算屬性將state.message渲染到頁面
  • 點選按鈕
  • 執行change()
  • 執行setMessage(),修改state.message的值
  • 使用計算屬性將新的state.message值渲染到頁面

可以看出Vue.observable實際就是封裝了observe:

  • 首先判斷是否包含__ob__這個屬性
  • 例項化一個Observer物件:
  • 由於本例項中傳入的不是陣列,進入walk()
  • 在walk中遍歷key,並使用defineReactive$$1建立響應式物件

Walk through all properties and convert them into getter/setters. This method should only be called when value type is Object.
遍歷所有屬性並將它們轉換為getter/setter。僅當值型別為Object時才應呼叫此方法。

  • 通過property.get進行取值,通過property.set進行賦值
  • 接下來呼叫Object.defineProperty()給物件定義響應式屬性(Object.defineProperty是vue.js實現「響應式系統」的關鍵之一)
    • enumerable,屬性是否可列舉,預設 false。
    • configurable,屬性是否可以被修改或者刪除,預設 false。
    • get,獲取屬性的方法。(進行依賴收集)(資料劫持)
    • set,設定屬性的方法。(進行響應式更新)
  • dep.notify():通過dep.notify()對觀察者watchers進行通知
  • 然後state就成全域性響應式物件了,

拓展:

思想來源:觀察者模式 vs 釋出訂閱模式

2.插槽 slot

官方文件:https://cn.vuejs.org/v2/guide/components.html#通過插槽分發內容
官方文件:https://cn.vuejs.org/v2/guide/components-slots.html
官方文件:https://cn.vuejs.org/v2/api/#v-slot

例項:插槽 slot

<body>
    <div id="root">
      <div>案例1:slot的基本用法</div>
      <Test>
        <template v-slot:header="{user}">
          <div>自定義header({{user.a}})</div>
        </template>
        <template v-slot="{user}">
          <div>自定義body({{user.b}})</div>
        </template>
      </Test>
    </div>
    <div id="root2">
      <div>案例2:Vue2.6新特性 - 動態slot</div>
      <Test>
        <template v-slot:[section]="{section}">
          <div>this is {{section}}</div>
        </template>
      </Test>
      <button @click="change">switch header and body</button>
    </div>
    <script>
      Vue.component('Test', {
        template: 
          '<div>' +
            '<slot name="header" :user="obj" :section="\'header\'">' +
              '<div>預設header</div>' +
            '</slot>' +
            '<slot :user="obj" :section="\'body\'">預設body</slot>' +
          '</div>',
        data() {
          return {
            obj: { a: 1, b: 2 }
          }
        }
      })
      new Vue({ el: '#root' })
      new Vue({ 
        el: '#root2',
        data() {
          return {
            section: 'header'
          }
        },
        methods: {
          change() {
            this.section === 'header' ?
              this.section = 'default' :
              this.section = 'header'
          }
        }
      })
    </script>
  </body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

渲染結果:

<div id="root">
	<div>案例1:slot的基本用法</div> 
	<div>
		<div>自定義header(1)</div>
		<div>自定義body(2)</div>
	</div>
</div>
<div id="root2">
	<div>案例2:Vue2.6新特性 - 動態slot</div> 
	<div>
		<div>this is header</div>
		預設body
	</div> 
	<button>switch header and body</button>
</div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

案例1中:

  • 帶有name="header"的插槽為具名插槽,在案例中綁定了兩個變數,變數user是元件的data中定義的物件obj,變數section是字串'header'
  • 不帶name屬性的插槽為匿名插槽(也叫預設插槽default),在案例中綁定了兩個變數,變數user是元件的data中定義的物件obj,變數section是字串'body'
  • 使用元件Testtemplate標籤引入插槽(v-slot只能用在component和template上)

PS:

  • 插槽slot不能直接獲取當前vue例項的資料,只能獲取定義插槽時繫結的資料
  • 匿名插槽,之前為slot-scope="{user}",新寫法寫全了是v-slot:default="{user}",省略寫法為v-slot:"{user}"v-slot="{user}"
  • v-slot可簡寫為#

案例2中:

  • v-slot:[section]="{section}"是動態插槽,section預設為#root2data中定義的'header'
  • 點選按鈕便可呼叫change()方法,切換section的值,然後兩個插糟的位置就會相互交換

其他用法可參考如下:


PS:developer tool 斷點除錯小技巧

  • Call Stack:顯示當前斷點的環境呼叫棧
  • Breakpoints:當前js斷點列表,新增的每個斷點都會出現在此處,點選列表中斷點就會定位到內容區的斷點上
  • DOM Breakpoints:當前DOM斷點列表列表
  • XHR Breakpoints:當前xhr斷點列表,可點選右側+新增斷點
  • Event Listener Breakpoints:事件監聽器斷點設定處
  • Event Listeners:當前事件監聽斷點列表

前端基礎進階(六):在chrome開發者工具中觀察函式呼叫棧、作用域鏈與閉包


chrome developer tool—— 斷點除錯篇


淺析javascript呼叫棧


Chrome開發者工具系列