Vue 基础 - 组件

2018-08-24  本文已影响3人  千反田爱瑠爱好者
https://cn.vuejs.org/

组件

使用组件可提高代码的复用性

命名规则

vue组件中camelCased(驼峰式)命名与kebab­case(短横线命名):

  1. 在html中,myMessagemymessage是一致的,因此在组件中的html中使用必须使用kebab­case(短横线)命名。不允许使用驼峰!
  2. 在组件中, 父组件给子组件传递数据必须用短横线。在template中,必须使用驼峰命名方式,若为短横线命名则会报错;
  3. 在组件的data中,用this.XXX引用时只能是驼峰命名方式,若为短横线命名则会报错。

组件注册

全局注册(所有vue对象都可以使用,但权限太大,容错率低)

<div id="app">
    <my-component></my-component>
</div>

<script>
Vue.component(
    'my-component', {
        template:'<div>我是组件的内容</div>'       // 组件中的内容会被模板替换
    }
)

var app = new Vue({
    el: "#app",
    data: {
    
    }
})
</script>

局部注册(Vue对象中可用)

var app = new Vue({
    el: '#app',
    components:{
        'my-component':{
            template: '<div>我是组件的内容</div>'
        }
    }
})

vue组件模板在某些情况下会受到HTML标签的限制:此时可以使用is属性来挂载组件:

<!-- 比如<table>中只能有<tbody>,<tr>,<td>等元素,所以直接在table中使用组件是无效的, -->

<table>
    <tbody is="my-component"></tbody>
</table>
  1. 必须使用小写字母加“­”命名组件(child、my­componnet);
  2. template中的内容必须被一个DOM元素包括,也可以嵌套;
  3. 在组件的定义中,可以使用除了template之外的其他选项:data、computed、methods...
  4. data必须是一个方法:
// ...
    'btn-component': {
        template: '<button @click="count++">'{{count}}</button>,
        data: function () {
            return {            // 专属于组件的对象
                count: 0        
            }
        }
        
    }

数组验证

Vue中验证的数据类型有:

<div id="app">
    <type-component :a="a" :b="b" :d="d" :f="f" :g="g"></type-component>
</div>
<script>
    // 数据验证组件
    Vue.component('typeComponent', {
        props: {
            a: Number,
            b: [String, Number],        // 传入的b只允许是String或Number
            c: {
                type: Boolean,
                default: true
            },
            d: {
                type: Number,
                required: true

            },
            e: {
                type: Array,
                default: function () {
                    return [];
                }
            },
            f: {
                validator: function (value) {
                    return value > 10;
                }
            },
            g: {
                type: Function
            }
        },
        template: '<div>{{a}} - {{b}} - {{d}} - {{f}}</div>',
    })

    let app = new Vue({
        el: "#app",
        data: {
            a: 1, b: 567, d: 789, f: 99, g: 1111
        }

    })
</script>

组件通信

父组件向子组件传递数据

  1. 在组件中使用props来从父亲组件接收参数,在props中定义的属性,都可以在组件中直接使用;
  2. 组件中propps来自父级,而datareturn的数据就是组件自己的数据,两种情况作用域就是组件本身,可以在template,computed,methods中直接使用;
  3. props可以设置字符串数组或对象,以使用v­-bind动态绑定父组件来的内容。

父组件data: {parentMsg: "Hello World!"}
-> 子组件<bind-component v-bind:msg="parentMsg"></bind-component>
-> 子组件"bind-component": {props: ["msg"]}
-> 子组件"child-component": {template: "<div>{{msg}}</div>"}

<div id="father" style="border: 2px solid chartreuse; height: 160px">
    <h5 style="text-align: center">父组件</h5>
    <child-component msg="来自父组件的内容"></child-component>      <!-- 父组件向子组件传递数据 -->
    <input type="text" v-model="parentMsg">
    <bind-component v-bind:msg="parentMsg"></bind-component>        <!-- 父组件的parentMsg绑定在子组件的msg中 -->
</div>

<script>
    let app = new Vue({
        el: "#father",
        data: {
            parentMsg: "Hello World!"
        },
        components: {
            "child-component": {
                template: "<div>{{msg}}</div>",
                props: ["msg"],
            },
            "bind-component": {
                template: "<div>{{msg}}</div>",
                props: ["msg"],
            }
        }
    })
</script>

单向数据流:

  1. 通过props传递数据是单向的,父组件数据变化时会传递给子组件,但不能反过来;
  2. 单向传递的目的是尽可能将父子组件解稿,避免子组件无意中修改了父组件的状态。

当父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改:

<div id="app">
    <my-component msg="父组件传递到子组件的数据"></my-component>
</div>

<script>
    // 注册组件
    Vue.component('my-component', {
        props: ['msg'],        // 将父组件的数据传递进来,并在子组件中用props接收
        template: '<div>子组件</div>',
        data: function () {
            return {
                count: this.msg     // 将传递进来的数据通过初始值保存起来
                // props中的值可以通过this.XXX直接获取
            }
        }
    })      
    let app = new Vue({
        el: "#father",
        data: {}
    })
</script>

prop作为需要被转变的原始值传入:

<div id="app">

    <input type="text" v-model="width">
    <my-comp :width="width"></my-comp>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            width: '100'
        },
        components: {
            'my-comp': {
                props: ['width'],
                template: '<div :style="style">{{width}}px</div>',
                computed: {
                    style: function () {
                        return {
                            width: this.width + 'px',
                            background: 'red'
                        }
                    }
                }
            }
        }
    })
</script>

子组件向父组件传递数据

子组件<button @click='increase'>+1</button>
-> 子组件this.$emit('change', this.count)
-> 父组件<child @change="handldTotal"></child>
-> 父组件handldTotal: function(value) {this.total = value}

<div id="app">
    {{total}}
    <child @change="handldTotal"></child>       <!-- 也可以在组件中使用v-model的方法实现 -->
    <!--<child v-model="total"></child>-->
</div>

<script>
    Vue.component('child', {
        template: "<div><button @click='increase'>+1</button></div>",
        methods: {
            increase: function () {
                this.count += 1
                this.$emit('change', this.count)        // 子组件向父组件发出发生change事件的通知
                // this.$emit('input', this.count)      // 使用v-model时指定为input事件通知
            }
        },
        data: function () {
            return {
                count: 200
            }
        }
    })
    let app = new Vue({
        el: "#app",
        data: {
            total: 200
        },
        methods: {
            handldTotal: function(value) {
                this.total = value
            }
        }
    })
</script>
  1. $emit实际上会触发一个input事件, 其后的参数就是传递给v­model绑定的属性的值
  2. v­model其实是一个语法糖,这背后做了两个操作:
    i. v­bind绑定一个value属性
    ii. v­on指令给当前元素绑定input事件

非父组件之间的通信

可以使用一个空的Vue实例作为中央事件总线:


<div id="app">
    <a-component></a-component>
    <b-component></b-component>
    <br>
    <child-component></child-component>
    {{msg}}
</div>

<script>
    Vue.component('a-component', {
        template: '<div style="width: 100px; height: 100px; border: 1px solid black"><button @click="handle">点击向B组件传递数据</button></div>',
        data: function () {
            return {
                a: '来自A组件的内容'
            }
        },
        methods: {
            handle: function () {
                this.$root.bus.$emit('send', this.a)
            }
        }
    })
    Vue.component('b-component', {
        template:'<div style="width: 100px; height: 100px; border: 1px solid black">{{b}}</div>',
        data: function() {
            return {
                b: ""
            }
        },
        created: function () {
            let self = this
            this.$root.bus.$on('send', function (value) {
                self.b = value
            })
        }
    })

    Vue.component('child-component', {
        template: '<button @click="setFatherData">通过点击修改父亲的数据</button>',
        methods: {
            setFatherData: function () {
                this.$parent.msg = '数据已修改'
            }
        }
    })

    let app = new Vue({
        el: "#app",
        data: {
            bus: new Vue(),
            msg: '数据未修改',
            formchild: '未取得数据'
        },
        methods: {
            getChildData: function() {
                this.formchild = this.$refs.c.msg;
            }
        }
    })
</script>

使用Slot分发内容

让组件可以组合的过程被称为内容分发,使用特殊的slot元素作为原始内容的插槽。

编译的作用域

<child-component>
    {{message}}
</child-component>

其中message应该绑定到父组件的数据,组件作用域简单地说是:

插槽的用法

将父组件的内容与子组件相混合,从而弥补了视图的不足:

<div id="app">
    <my-component>
        <p>我是父组件的内容</p>
        <!-- 最外层组件内部的所有内容都由最外层组件控制 -->
        <!-- 如果没有使用slot,则子组件无法在其内部插入内容 -->
    </my-component>
</div>
<script>
    Vue.component('my-component', {
        template:
            `<div>
                <slot>如果父组件没有插入内容,我就作为默认出现</slot>
            </div>`
    })    
    new Vue({
        el: "#app"

    })
</script>

其中<slot>标签中可以指定name,称为“具名插槽”,对应DOM标签的slot属性:

<p slot="footer">底部</p>

// ...
template: `
    <div class="footer">
        <slot name="footer"></slot>
    </div>`

作用域插槽

使用一个可以复用的模板来替换已经渲染的元素

从子组件获取数据

<div id="app">
    <my-component>
        <template slot="abc" slot-scope="prop">    <!-- template是不会被渲染的,2.5.0版本后不需要写template -->
            {{prop.text}}       <!-- text在slot中定义 -->
        </template>
    </my-component>
</div>
<script>
    Vue.component('my-component', {
        template: `
            <div>
                <slot text="来自子组件的数据" name="abc"></slot>
            </div>
        `
    })
    new Vue({
        el: "#app"
    })
</script>

访问插槽

通过this.$slots.name可以访问名称为name的插槽

mounted:function () {
// 访问插槽
    var header = this.$slots.header;
    var text = header[0].elm.innerText;
    var html = header[0].elm.innerHTML;
    console.log(header)
    console.log(text)
    console.log(html)
}

动态组件

Vue提供了component元素用来动态的挂载不同的组件,使用is特性实现。

<div id="app">
    <!-- 点击不同按钮实现切换不同组件 -->
    <component :is="thisView"></component>
    <button @click="handleView('A')">1</button>
    <button @click="handleView('B')">2</button>
    <button @click="handleView('C')">3</button>
</div>
<script>
    Vue.component('compA', {
        template: '<div>JavaScript</div>'
    })
    Vue.component('compB', {
        template: '<div>CSS</div>'
    })
    Vue.component('compC', {
        template: '<div>HTML</div>'
    })
    let app = new Vue({
        el: "#app",
        data: {
            thisView: "compA"
        },
        methods: {
            handleView: function (tag) {
                this.thisView = "comp" + tag;
            }
        }
    })
</script>
上一篇下一篇

猜你喜欢

热点阅读