深入理解vue组件

# 深入理解vue组件

参考教程:

父子组件传值及v-model实现通信 (opens new window)

[TOC]

# 一、vue组件中的细节点

# 1.1 来自html5规范的bug

<div id="root">
    <table>
        <tbody>
            <row></row>
        </tbody>
    </table>
</div>
<script>
    Vue.component('row', {
        template: '<tr><td>this is a row</td></tr>'
    })
    var vm = new Vue({
        el: '#root'
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • bug:html5规定,在table里,td必须写在tr里,所以解析row的时候没法写在table里。 dom picture
  • 除bug:借助is属性
<tbody>
    <tr is="row"></tr>
</tbody>
1
2
3

dom picture

  • 类似的case
<ul>
    <li is="example1"></li>
</ul>

<select>
    <option is="example2"></option>
</select>
1
2
3
4
5
6
7

# 1.2 非根组件的data应写成函数形式

Vue.component('row', {
    data: {
        content: "this is a content"
    },
    template: '<tr><td>this is a row</td></tr>'
})
1
2
3
4
5
6
  • [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
    • 避免子组件公用一套数据而造成的相互影响
// ES6写法
data (){
    return {
        content: "this is a content"
    }
}
1
2
3
4
5
6

# 二、父子组件的数据传递

# 2.1 单向数据流

  • 父组件可以向子组件传参,但子组件不可以改变父组件的数据,避免其他子组件受到影响。(这么做不会抛出错误,但会有警告)。

# 2.2 父传子props

// 父组件
<template>
    <child :msg='msg'></child>
</template>
<script>
import Child from './Child'
export default {
    components: {
        Child
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
// 子组件
export default {
	name: 'Child',
    props: ['msg']
}
1
2
3
4
5

# 2.3 子传父$emit

  • 父传子的数据若要实时更新,则应该这么做
watch: {
    page: {
        // 表示创建组件时 handler 回调会立即执行
        // 省去在 created 函数中再次调用了
        immediate: true,
            handler (val) {
            this.currentPage3 = val
        }
    }
}
1
2
3
4
5
6
7
8
9
10
  • 使用修饰符简化写法.sync
    • 实现父子组件双向绑定数据,子组件可以改父组件的值
//父组件
<comp :myMessage.sync="bar"></comp> 
//子组件 触发事件必须要用双引号
this.$emit('update:myMessage',params);
1
2
3
4

# 三、组件参数校验与非props特性

# 3.1 组件参数校验

content: {
    //接收的类型,可以接受两种数据类型 content: [String, Number]
    type: String,
    //参数是否必须接收
    required: false//如果没有参数则显示默认值
    default: 'default value'//校验器
    validator: function(value){
        return (value.length > 5)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 3.2 非props特性

  1. 非props就是没有写props, 就无法接受参数,会警告。
  2. content属性会显示在dom上,props特性则不会。

# 四、非父子组件通信

# 4.1 vuex

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

# 4.2 Bus/总线/发布订阅模式/观察者模式

<div id="root">
    <child content="xiao"></child>
    <child content="lin"></child>
</div>

<script>
    // 之后调用new Vue()或者创建组件的时候,每一个组件上都会有Bus这个属性
    Vue.prototype.bus = new Vue()
    Vue.component('child', {
        data () {
            return {
                selfContent: this.content
            }
        },
        props: {
            content: String
        },
        template: '<div @click="handleClick">{{selfContent}}</div>',
        methods: {
            handleClick () {
                this.bus.$emit('change', this.selfContent)
            }
        },
        //将一个组件的子组件的数据传递给另一个组件的子组件
        //el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
        mounted () {
            var _this = this
            //在父组件中使用$on监听bus上触发出来事件
            //会执行两遍(两个child都触发了一次事件监听)
            this.bus.$on('change', function(msg) {
                _this.selfContent = msg
            })
        }
    })
    var vm = new Vue({
        el: '#root'
    })
</script>
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
  • personal分析(test通过)
    • 点击<child content="xiao"></child>
    • 发生handleClick,触发change事件,将selfContent的值xiao传给了msg
    • <child content="xiao"></child>挂载完后执行mounted并监听到change,将xiao替换了selfContent的值xiao
    • <child content="lin"></child>挂载完后执行mounted并监听到change,将lin替换了selfContent的值lin

# 五、插槽(slot废弃,改为v-slot)

# 六、动态组件

注意事项:绑定事件应该是@value-updated="onUpdate",而不是@valueUpdated="onUpdate($event)"。

<div id="root">
    <component :is="type"></component>
    <!-- 等价于 -->
    <!-- <child-one v-if="type==='child-one'"></child-one> -->
    <!-- <child-two v-if="type==='child-two'"></child-two> -->
    <button @click="handleBtnClick">change</button>
</div>

<script>
    Vue.component('child-one', {
        template: `<div v-once>child-one</div>`
    })
    Vue.component('child-two', {
        template: `<div v-once>child-two</div>`
    })
    var vm = new Vue({
        el: '#root',
        data: {
            type: 'child-one'
        },
        methods: {
            handleBtnClick: function() {
                this.type = (this.type === 'child-one' ? 'child-two' : 'child-one')
            }
        }
    })
</script>
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

# 七、异步组件

# 八、创建更高层次的组件

详细教程:https://segmentfault.com/a/1190000020637062

Vue 学习笔记:$attrs 和 $listeners 的用法 (opens new window)

v-bind="$props": 可以将父组件的所有props下发给它的子组件,子组件需要在其props:{} 中定义要接受的props。

v-bind="$attrs": 将调用组件时的组件标签上绑定的非props的特性(class和style除外)向下传递。在子组件中应当添加inheritAttrs: false(避免父作用域的不被认作props的特性绑定应用在子组件的根元素上)。

vm.$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

# 8.1 $attrs

  • 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件—— 在创建高级别的组件时非常有用
<template>
  <div>
    <el-input v-model="input" v-bind="$attrs"></el-input>
  </div>
</template>
<script>
export default {
  inheritAttrs: false,	// 必不可少的代码,避免el-input的属性被添加到根元素div上
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  computed: {
    input: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit('input', val);
      }
    }
  }
}
</script>
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
  • $attrs + inheritAttrs 可以使该组件可以直接使用el-input上的所有属性。

# 8.2 $listeners

  • 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
<template>
  <div>
    <el-input v-model="input" v-bind="$attrs" v-on="$listeners"></el-input>
  </div>
</template>
1
2
3
4
5
  • $listeners 可以使该组件使用el-input绑定的事件。(注意:可以使用的是Input Event,非input Methods)

# 九、编写组件

# 9.1 v-model

props: {
	value: {
      // [key1,key2]
      // v-model
      type: Array,
      default: () => [],
    },
}

this.$emit("input", []); // 改变v-model的值
1
2
3
4
5
6
7
8
9
10