Vue.js 创建一个 CNODE 社区(4)

表单与 v-model / Component

内容包含:v-model / component(全局注册/局部注册/父子组件之间传递数据/slot/动态组件等..)

哈哈,又是 demo 和基础概念理解。

边写 demo 边理解总结,花费两天终于完成,我要缓一天去学学 webpack ,接着回来再战 Vue。

这边博文会略长,因为有关于组件的各个例子的 demo~全部敲一遍才能理解得更快嘛。


v-model

你可以用 v-model 指令在表单<input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。

Vue 的官方文档:v-model

v-model 的 demo :JSbin


Component

Vue 关于 组件的官方文档:Component

定义:组件是可复用的 Vue 实例

作用:提高代码的复用性

关于 component 的 demo:JSbin

全局注册

<div id="app">
    <!-- 2.引入组件 -->
    <div-component></div-component>
</div>
// 1.注册组件
Vue.component('div-component',{
    template:'<div>I\'m component</div>'
})
var app = new Vue({
    el:'#app',
    data:{}
})

优点:所有的 Vue实例都可以使用

缺点:权限太大,容错率低

局部注册

<div id="app">
    <!-- 引入局部组件 -->
    <app-component></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{},
    // 注册局部组件
    components:{
        'app-component':{
            template:'<div>I\'m  局部 component</div>'
        }
    }
})

在 app实例 中创建的组件 只有在 app 挂载的 HTML 中才可以使用。

注意 HTML 中的某些标签受限:

比如 table 标签中嵌入组件的话将不会生效,因为他里面只有 tr/td/tbody 等属性,除非使用 is 将组件传递进去:

<div id="app">
    <!-- 引入局部组件 -->
    <table>
        <tbody :is='app-component'></tbody>
    </table>
</div>

使用技巧

1.命名规则:推荐使用小写加 - 的形式命名

2.template 中的内容必须要用一个 DOM 元素包裹,也可以嵌套

3.在组件的定义中,还可以使用除了 template 之外其他的属性,如 data / computed / methods 等

4.一个组件中的 data 必须是一个函数,返回一个对象(每个实例可以维护一份被返回对象的独立的拷贝)

如:

<div id="app">
    <!-- 引入局部组件 -->
    <app-component></app-component>
    <app-component></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{},
    // 注册局部组件
    components:{
        'app-component':{
            template:`<div><div>I\'m  局部 component</div><button @click='countNum'>已点击{{ count }}次</button></div>`,
            data:function(){
                return {
                    msg:'component 中的 属性',
                    count:0
                }
            },
            methods:{
                countNum:function(){
                this.count += 1
                }
            }
        }
    }
})

两个按钮每次点击,都会维护各自的 count,而不会影响到其他所有实例。


父组件传递数据给子组件(使用 props )

demo1:

<div id="app">
    <!-- 引入局部组件 -->
    <!-- 父组件传递数据 -->
    <app-component msg='这是父组件给子组件传递的信息'></app-component>
    <app-component msg='msg'></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{},
    // 注册局部组件
    components:{
        'app-component':{
            // 在子组件页面中渲染数据
            template:`<div><div>I\'m  局部 component</div>{{ msg }}</div>`,
            // 子组件通过 props 接收参数
            props:[
                'msg'
            ]
            data:function(){
                return {
                    msg:'component 中的 属性',
                    count:0
                }
            },
        }
    }
})

1.在组件中使用props来从父组件接收参数,注意,在props中定义的属性,都可以在组件中直接使用

2.propps来自父级,而组件中data return的数据就是组件自己的数据,两种情况作用域就是组件本身,可以在template,computed,methods中直接使用

3.props的值有两种,一种是字符串数组,一种是对象

4.可以使用v­-bind动态绑定父组件来的内容:

// 1.创建 Vue 实例
var app = new Vue({
    el:'#app',
    data:{
        posts: [
            { id: 1, title: 'My journey with Vue' },
            { id: 2, title: 'Blogging with Vue' },
            { id: 3, title: 'Why Vue is so fun' }
        ]
    },
    // 2.注册子组件
    components:{
        'app-component':{

            // 3.子组件通过 props 接收参数
            props:[
                'id',
                'titel'
            ],

            // 4.创建模板,渲染组件
            template:`<div>{{ id }} - {{ title }}</div>`
        }
    }
})
<div id='app'>
    <!-- 5.在父组件中引入子组件 -->
    <!-- 6.动态绑定属性,传递到子组件 -->
    <app-component
    v-for='item in posts'
    v-bind:id='item.id'
    v-bind:title='item.title'></app-component>
</div>

单向数据流

解释 : 通过 props 传递数据 是单向的了, 也就是父组件数据变化时会传递给子组件,但是反过来不行。

目的 :是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态。

应用场景: 业务中会经常遇到两种需要改变 prop 的情况

  • 1

父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。这种情况可以在组件 data 内再声明一个数据,引用父组件的 prop

步骤一:注册组件

步骤二:将父组件的数据传递进来,并在子组件中用props接收

步骤三:将传递进来的数据通过 初始值 保存起来:

components:{
    'app-component':{
        props:['msg'],
        data:function(){
            return {
                Message:this.msg
            }
        },
        template:`<div>{{ Message }}</div>`
    }
}
  • 2

prop 作为需要被转变的原始值传入。这种情况用计算属性就可以了

步骤一:注册组件

步骤二:将父组件的数据传递进来,并在子组件中用props接收

步骤三:将传递进来的数据通过 计算属性 进行重新计算

<div id='app'>
    <input text='text' v-model='width'>
    <app-component :width='width'></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        width:0
    },
    components:{
        'app-component':{
            props:['width'],
            template:`<div :style='style'></div>`,
            computed:{
                style:function(){
                    return {
                        width:this.width +'px',
                        background:'yellow',
                        height:'20px'
                    }
                }
            }
        }
    }
})

因为父组件中的 input 用 v-model 绑定了父组件 data 中的 width,所以当在 input 中输入数字时,width 也会跟着变化;而子组件中使用 :width 传递着父组件的 width,template 中使用了动态绑定 :style 更新 div 的样式,所以当 width 变化时,计算属性 style 开始计算,返回一个对象,重新渲染 div 的宽度 width。


命名规则

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

在html中, myMessage 和 mymessage 是一致的,因此在组件中的html中使用必须使用kebab­case(短横线)命名方式。在html中不允许使用驼峰!:

<div id='app'>
    <app-component my-msg='xxx'></app-component>
</div>

在组件中, 父组件给子组件传递数据必须用短横线。在template中,必须使用驼峰命名方式,若为短横线的命名方式。则会直接保错。

components:{
    props:['myMsg'],
    template:`<div>{{ myMsg }}</div>`
}

在组件的data中,用this.XXX引用时,只能是驼峰命名方式。若为短横线的命名方式,则会报错。

components:{
    props:['myMsg'],
    data:function(){
        return {
            name = this.myMsg
        }
    },
    template:`<div>{{ myMsg }}</div>`
}

数据验证

验证父组件传递给子组件的数据的类型:

String / Number / Boolean / Object / Array / Function

<div id='app'>
    <app-component :a='a' :b='b' :c='c' :d='d' :e='e' :f='f'></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        a:'1',
        b:1,
        c:true,
        d:{'name':'sgt'},
        e:[],
        f:console.log(),
        g:67,
    },
    components:{
        'app-component':{
            // required 必传 / default 默认 / type 类型
            // 如果父组件没有向子组件 传递数据,则使用 default 值
            props:{
                // 传入的 a 的值需要是 String/Number 类型,如果不是,则报错
                a:[String,Number]

                // 必须传入 b 且 b 的值是 Number类型,如果不传入(即没有 :b='b'),则报错;如果传入是其他类型,则报错
                b:{type:Number,required:true},

                // 传入的值需为 Boolean 类型,如果不传入(即没有 :c='c'),则使用 default 值,true;如果传入值不为 Boolean,则报错
                c:{type:Boolean,default:true},

                // 传入的值需为 Object 类型,如果不传入(即没有 :d='d'),则使用 default 值;如果传入值不为 Object,则报错
                d:{type:Object,defult:function(){return {'name':'xxx'}}},

                // 传入的值需为 Array 类型,如果不传入(即没有 :e='e'),则使用 default 值;如果传入值不为 Array,则报错
                e:{type:Array,defult:function(){return [666]}},

                // 传入的值需为 Function 类型
                f:{type:Function}

                // 自定义验证函数
                g:{validator:function(value){return value>10}},
            },
            template:`<div>{{a}}-{{b}}-{{c}}-{{d}}-{{e}}-{{f}}-{{g}}</div>`
        }
    }
})

子组件传递给父组件

利用自定义事件,子组件传递数据给父组件

子组件用$emit()来触发事件 ,父组件用$on()来监昕子组件的事件

<div id="app">
    <!-- 引入局部组件 -->
    <!-- 自定义事件 -->
    <app-component @change='handleTotal'></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        // 父组件中的数据
        total:1000
    },
    methods:{
        handleTotal:function(value){
            this.total = value
        }
    },
    components:{
        'app-component':{
            template:`<div>
            <button @click='handleCrease'>点击 +1000</button>
            <button @click='handleReduce'>点击 -1000</button>
            </div>`,
            data:function(){
                return {
                    count:1000
                }
            },
            methods:{
                handleCrease:function(){
                    this.count += 1000
                    this.$emit('change',this.count)
                },
                handleReduce:function(){
                    this.count -= 1000
                    this.$emit('change',this.count)
                }
            }
        }
    }
})

过程解析:在子组件中定义了一个数据 count 和两个监听事件 handleCrease / handleReduce,每当点击按钮,触发监听事件,则改变 count 的值,并且把改变后的 count 作为参数通过 $emit 传给自定义事件 change,change 绑定的是父组件中的方法 handleTotal,所以他执行了父组件中的方法 handleTotal,把父组件中的 total 值修改成通过自定义事件传递进来的参数 count,实现子组件向父组件传递信息。


在组件中使用 v-model

其实使用 v-model 可以用更少的代码实现上面的功能,而且不需要自定义事件:

<div id="app">
    <!-- 引入局部组件 -->
    <!-- 自定义事件 -->
    <app-component v-model='total'></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        // 父组件中的数据
        total:1000
    },
    components:{
        'app-component':{
            template:`<div>
            <button @click='handleCrease'>点击 +1000</button>
            <button @click='handleReduce'>点击 -1000</button>
            </div>`,
            data:function(){
                return {
                    count:1000
                }
            },
            methods:{
                handleCrease:function(){
                    this.count += 1000
                    this.$emit('input',this.count)
                },
                handleReduce:function(){
                    this.count -= 1000
                    this.$emit('input',this.count)
                }
            }
        }
    }
})

$emit的代码,实际上会触发一个input事件, ‘input’后的参数就是传递给v­-model绑定的属性的值

v­-model 其实是一个语法糖,这背后其实做了两个操作:

1.v­-bind 绑定一个 value 属性

2.v­-on 指令给当前元素绑定 input 事件

<input v-model="searchText">

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

要使用v-­model,要做到:

接收一个 value 属性。

在有新的 value 时触发 input 事件


非父组件之间的通信

有时候两个组件也需要通信(非父子关系),在简单的场景下,可以使用一个空的Vue实例作为中央事件总线

<div id="app">
    <app-component></app-component>
    <bpp-component></bpp-component>
</div>
var app = new Vue({
    el:'#app',
    data:{

        // 1.创建空的 Vue 实例,this.$root 代表着当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
        bus:new Vue()
    }
    components:{
        'app-component':{
            data:function(){
                return {
                    a:'通讯信息 SOS'
                }
            },
            template:`<div><button @click='handleA2B'>点击我从 A 组件发送到 B 组件</button></div>`,
            methods:{

                // 2.点击执行函数,触发 $emit ,把 this.a 当成参数传递给 aEvent事件
                handleA2B:function(){
                    this.$root.bus.$emit('aEvent',this.a)
                }
            }
        },
        'bpp-component':{
            template:`<div>我是 B 组件</div>`

            // 3.B 组件在实例创建的时候就监听 aEvent 事件,一旦监听到变化,即值传入函数进行处理
            created:function(){
                this.$root.bus.$on('aEvent',function(value){
                    alert('我是 B 组件,已接收到 A 组件发出的信息:' + value)
                })
            }
        }
    }
})

父链

父实例,如果当前实例有的话。子组件可以拿到父组件中的内容

<div id="app">
    {{ msg }}
    <app-component></app-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        msg:'我是父组件中的 msg'
    },
    components:{
        'app-component':{
            template:`<div><button @click='changeFather'>点击设置父组件中的 msg </button></div>`,
            methods:{
                changeFather:function(){
                    this.$parent.msg = '哈哈,我已经改变了这个 msg'
                }
            }
        }
    }
})

子链

提供了为子组件提供索引的方法,用特殊的属性ref为其增加一个索引,拿到子组件中的内容

<div id="app">
    {{ msg }}
    <button @click='changeChild'>点击拿到子组件中的 msg</button>
    <app-component ref='a'></app-component>
    <bpp-component ref='b'></bpp-component>
</div>
var app = new Vue({
    el:'#app',
    data:{
        msg:'这是父组件原始的 msg'
    }
    methods:{
        changeChild:function(){

            // 这里是 refs,而不是 ref
            this.msg = this.$refs.a.msg
        }
    },
    components:{
        'app-component':{
            data:function(){
                return {
                    msg:'这是子组件中的 msg'
                }
            },
        }
    }
})

slot(插槽)

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发。Vue.js 实现了一个内容分发 API,用特殊的 ‘slot’ 元素作为原始内容的插槽。

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

插槽嘛,顾名思义,就是在子组件中的 template 中开一个槽,槽里面可以不放数据,也可以用放默认的数据;当父组件往子组件里面插数据的时候,这个数据就会安放在槽里;如果父组件没有网子组件里面插数据,则显示槽里面的默认内容。

  • 单个插槽
<div id="app">
    <app-component>
        <span>这是父组件安插在子组件中的内容</span>
    </app-component>
</div>
var app = new Vue({
    el:'#app',
    components:{
        'app-component':{
            template:`<div>我是组件 A <slot>如果父组件没有插入内容,则显示此条消息</slot></div>`
        }
    }
})
  • 具名插槽
<div id="app">
    <app-component>
        <h2 slot='header'>具名插槽</h2>
        <span>这是父组件安插在子组件中的内容</span>
        <div slot='footer'>插槽尾部</div>
    </app-component>
</div>
var app = new Vue({
    el:'#app',
    components:{
        'app-component':{
            template:`
            <div>我是组件 A
                <div class='header'>
                    <slot name='header'></slot>
                </div>
                <div class='container'>
                    <slot>如果父组件没有插入内容,则显示此条消息</slot>
                </div>
                <div class='footer'>
                    <slot name='footer'></slot>
                </div>
            </div>`
        }
    }
})

关于 slot 注意事项

有对应的插槽,则使用对应插槽(slot='abc'/ name='abc')

插槽名称不对应,则不显示(slot='abc'/name='cba')

插入数据没有指定具名 slot,则插入到默认插槽(节点/slot)

节点没有指定具名 slot,插槽有名字(name='abc'),则不会渲染节点内容,显示插槽默认内容


作用域插槽

作用域插槽是一种特殊的slot,使用一个可以复用的模板来替换已经渲染的元素 - 从子组件获取数据

template模板是不会被渲染的,在 Vue 2.5 之后可以直接用 标签 + slot-scope 获取 slot 中的数组,而不是指定的 template 模板了

<div id="app">
    <app-component>
        <p slot='abc' slot-scope='temp'>
            这是 slot 中的内容
            {{ temp.text }}
            {{ temp.prop }}
        </p>
    </app-component>
</div>
var app = new Vue({
    el:'#app',
    components:{
        'app-component':{
            template:`
            <div>
                <slot name='abc' text='这是 slot 中的 text' prop='这是 slot 中的 prop'></slot>
            </div>`
        }
    }
})

访问 slot

通过this.$slots.(插槽名称)可以访问 slot

<div id="app">
    <app-component>
        <p slot='abc'>
            slot is ready.
        </p>
    </app-component>
</div>
var app = new Vue({
    el:'#app',
    components:{
        'app-component':{
            template:`
            <div>
                <slot name='abc'></slot>
            </div>`,

            // 在 Vue 实例挂载后
            mounted:function(){
                let abc = this.$slots.abc   // 获取了一个 Vue 所特有的 VNODE 节点
                let text = abc[0].ele.innerHTML     // 根据节点 API 获取内容
                console.log(text)   // slot is ready.
            }
        }
    }
})

组件高级用法–动态组件

Vue 给我们提供 了一个元素叫 component

作用是: 用来动态的挂载不同的组件

实现:使用is特性来进行实现的

<div id="app">
    <!-- :is 绑定的是组件名 -->
    <component :is='thisView'></component>
    <button @click='changeTo("A")'>点击跳到组件A</button>
    <button @click='changeTo("B")'>点击跳到组件B</button>
</div>
var app = new Vue({
    el:'#app',
    data:{

        // 默认显示的组件
        thisView:'componentA'
    },
    methods:{
        // 点击后显示改变后的组件
        changeTo:function(value){
            this.thisView = 'component' + value
        }
    },
    components:{
        'componentA':{
            template:`
            <div>
                <slot name='abc'>这是组件A</slot>
            </div>`,
        },
        'componentB':{
            template:`
            <div>
                <slot name='cba'>这是组件B</slot>
            </div>`,
        }
    }
})

关于 v-model 和 component 的内容就到这里啦有更深入的理解的话会回来更新的

posted @ 2018-10-04 19:05  小粒旬  阅读(232)  评论(0编辑  收藏  举报