Vue学习笔记4-组件、自定义指令

216 阅读9分钟

前言

结合官方文档+菜鸟教程学习笔记。

Vue组件

组件可以扩展 HTML 元素,封装可重用的代码。一个组件本质上是一个拥有预定义选项的一个 Vue 实例。

组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树

组件树

组件注册

Vue.component(tagName, options)//tagName 为组件名,options 为配置选项
<tagName></tagName>

全局组件

<div id="app">
	<runoob></runoob>
</div>

<script>
// 注册
Vue.component('runoob', {
  template: '<h1>自定义组件!</h1>'
})
// 创建根实例
new Vue({
  el: '#app'
})
</script>

局部组件

组件只能在这个实例中使用

<div id="app">
	<runoob></runoob>
</div>

<script>
var Child = {
  template: '<h1>自定义组件!</h1>'
}

// 创建根实例
new Vue({
  el: '#app',
  components: {
    // <runoob> 将只在父模板可用
    'runoob': Child
  }
})
</script>

组件传值

prop

prop 是子组件用来接受父组件传递过来的数据的一个自定义属性。

父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop":

<div id="app">
	<child message="hello!"></child>
</div>

<script>
// 注册
Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 同样也可以在 vm 实例中像 “this.message” 这样使用
  template: '<i>{{ message }}</i>'
})
// 创建根实例
new Vue({
  el: '#app'
})
</script>

动态prop

类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件

<div id="app">
	<ol>
    <todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
  	</ol>
</div>

<script>
Vue.component('todo-item', {
  props: ['todo'],//注意这里显式生命的是绑定的变量名,非原本变量名(注册在内部)
  template: '<li>{{ todo.text }}</li>'
})
new Vue({
  el: '#app',
  data: {
    sites: [
      { text: 'Runoob' },
      { text: 'Google' },
      { text: 'Taobao' }
    ]
  }
})
</script>

prop验证

组件可以为 props 指定验证要求。这块很有用,可以作为数据传递限制,可以在element、表单中有大量运用。

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    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
      }
    }
  }
})

//type可以是以下
String
Number
Boolean
Array
Object
Date
Function
Symbol
type 也可以是一个自定义构造器,使用 instanceof 检测。

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

在组件上使用v-model

<input v-model="searchText">
等价于
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

用在组件上

  • 将其 value attribute 绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})
<custom-input v-model="searchText"></custom-input>

自定义事件

父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回父组件,就需要使用自定义事件

可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface)

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件
  • 父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件
<div id="app">
	<div id="counter-event-example">
	  <p>{{ total }}</p>
	  <button-counter v-on:increment="incrementTotal"></button-counter>
	  <button-counter v-on:increment="incrementTotal"></button-counter>
	</div>
</div>

<script>
Vue.component('button-counter', {
  template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementHandler: function () {
      this.counter += 1
      this.$emit('increment')//传递给外层的increment参数上
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})
</script>

监听一个原生事件。可以使用 .native 修饰 v-on

<my-component v-on:click.native="doTheThing"></my-component>

data是一个函数

可以看到 button-counter 组件中的 data 不是一个对象,而是一个函数,这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例。

<div id="components-demo3" class="demo">
    <button-counter2></button-counter2>
    <button-counter2></button-counter2>
	<button-counter2></button-counter2>
</div>

<script>
var buttonCounter2Data = {
  count: 0
}
Vue.component('button-counter2', {
    
    data: function () {
	    // data 选项是一个函数,组件不相互影响
        return {
            count: 0
        }
    },
    /*
    data: function () {
        // data 选项是一个对象,会影响到其他实例
        return buttonCounter2Data
    },*/
    template: '<button v-on:click="count++">点击了 {{ count }} 次。</button>'
})
new Vue({ el: '#components-demo3' })
</script>

有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

或者
<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的

可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

已注册组件的名字,或 一个组件的选项对象

    <div id="dynamic-component-demo" class="demo">
      <button
        v-for="tab in tabs"
        v-bind:key="tab"
        v-bind:class="['tab-button', { active: currentTab === tab }]"
        v-on:click="currentTab = tab"
      >
        {{ tab }}
      </button>

      <component v-bind:is="currentTabComponent" class="tab"></component>
    </div>

    <script>
      Vue.component("tab-home", {
        template: "<div>Home component</div>"
      });
      Vue.component("tab-posts", {
        template: "<div>Posts component</div>"
      });
      Vue.component("tab-archive", {
        template: "<div>Archive component</div>"
      });

      new Vue({
        el: "#dynamic-component-demo",
        data: {
          currentTab: "Home",
          tabs: ["Home", "Posts", "Archive"]
        },
        computed: {
          currentTabComponent: function() {
            return "tab-" + this.currentTab.toLowerCase();
          }
        }
      });
    </script>

这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定。

对于像 value 这样的 property,若想让其如预期般工作,你需要使用 .prop 修饰器。

解析DOM模板注意事项

有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。 这会导致我们使用这些有约束条件的元素时遇到一些问题。

这个特殊的 is attribute 给了我们一个变通的办法:

<table>
  <blog-post-row></blog-post-row>
</table>

变更为:
<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的

字符串 (例如:template: '...')
单文件组件 (.vue)
<script type="text/x-template">

自定义指令

除了默认设置的核心指令( v-model 和 v-show ), Vue 也允许注册自定义指令。

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

<div id="app">
	<p>页面载入时,input 元素自动获取焦点:</p>
	<input v-focus>
</div>

<script>
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {////如果想注册局部指令,组件中也接受一个 directives 的选项
  // 当绑定元素插入到 DOM 中。
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
// 创建根实例
new Vue({
  el: '#app'
})
</script>

//然后你可以在模板中任何元素上使用新的 v-focus property:
<input v-focus>

-----------------------------------
局部指令注册:
<div id="app">
  <p>页面载入时,input 元素自动获取焦点:</p>
  <input v-focus>
</div>

<script>
// 创建根实例
new Vue({
  el: '#app',
  directives: {
    // 注册一个局部的自定义指令 v-focus
    focus: {
      // 指令的定义
      inserted: function (el) {
        // 聚焦元素
        el.focus()
      }
    }
  }
})
</script>

钩子函数

  • bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。

  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。

  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。但是可能发生在其子 VNode 更新之前。

  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind: 只调用一次, 指令与元素解绑时调用。

钩子函数参数

  • el: 指令所绑定的元素,可以用来直接操作 DOM 。
  • binding: 一个对象,包含以下属性:
    • name: 指令名,不包括 v- 前缀。
    • value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
    • oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression: 绑定值的表达式或变量名。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。
    • arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。
    • modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
  • vnode: Vue 编译生成的虚拟节点。
  • oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
<div id="app"  v-runoob:hello.a.b="message">
</div>

<script>
Vue.directive('runoob', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})
new Vue({
  el: '#app',
  data: {
    message: '菜鸟教程!'
  }
})
</script>
------------------
result:
name: "runoob"//自定义指令名
value: "菜鸟教程!"//data值
expression: "message"//data变量名
argument: "hello"//v-runoob:hello
modifiers: {"a":true,"b":true}//包含修饰符的对象
vnode keys: tag, data, children, text, elm, ns, context, functionalContext, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce//Vue 编译生成的虚拟节点

部分功能,可以简写钩子函数

Vue.directive('runoob', function (el, binding) {
  // 设置指令的背景颜色
  el.style.backgroundColor = binding.value.color
})

传入js对象

指令函数可接受所有合法的 JavaScript 表达式

<div id="app">
    <div v-runoob="{ color: 'green', text: '菜鸟教程!' }"></div>
</div>
 
<script>
Vue.directive('runoob', function (el, binding) {
    // 简写方式设置文本及背景颜色
    el.innerHTML = binding.value.text
    el.style.backgroundColor = binding.value.color
})
new Vue({
  el: '#app'
})
</script>

动态指令参数

指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令

<div id="baseexample">
  <p>Scroll down the page</p>
  <p v-pin="200">Stick me 200px from the top of the page</p>
</div>

Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    el.style.top = binding.value + 'px'
  }
})

new Vue({
  el: '#baseexample'
})

这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')//用binding.arg取得参数
    el.style[s] = binding.value + 'px'
  }
})

new Vue({
  el: '#dynamicexample',
  data: function () {
    return {
      direction: 'left'
    }
  }
})