VueJS组件篇

首先介绍一下什么是组件

  • 组件可以扩展HTML元素,封装可重用的代码。
  • 组件是自定义元素,VueJS的编译器为它添加特殊功能。
  • 组件也可以是原生HTML元素形式,以is特性扩展。

如何使用组件

这里我先介绍两种注册组件的方法

  • 全局注册(所谓全局注册就是使用Vue的静态注册)
    Vue.component('my-component',{
        template:'<p>我是全局注册</p>'
    });
    new Vue({
        el:"#app",
    });
  • 局部注册(需要写在实例中的components属性中)

      var vm = new Vue({
          el:'#app',
          components:{
              'my-component':{
                  template:'<p>我是局部注册</p>'
              }
          }
      });
      new Vue({
          el:"#app",
      });
    

组件中的data

  • data必须是函数
  • 如果不是函数,每个组件共享一个数据,会相互影响
  • 当使用函数的时候,把数据返回,每个组件都有自己的数据

      Vue.component('my-component',{
          template:'<h1>{{ name }}</h1>'
          data(){
              return{
                  name:'luoxue'
              }
          }
      })
    

组件的构成

组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。

在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。

prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项声明 “prop”:

组件中的props属性

由于组件中的data是一个函数,拥有自己的作用域,所以不能直接传值,我们就需要用props属性来进行数据的传递。也就是说,组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。

//传入普通字符串
<child my-message="hello!"></child>

Vue.component('child',{
    // 声明props
    props:['message'],
    // 就像 data 一样,prop 可以用在模板内
    // 同样也可以在 vm 实例中像 “this.message” 这样使用
    template: '<span>{{ message }}</span>'
})
new Vue({
    el:"#app",
})

camelCase vs. kebab-case

HTML 特性不区分大小写。当使用非字符串模版时,prop的名字形式会从 camelCase 转为 kebab-case(短横线隔开):

Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

再次说明,如果你使用字符串模版,不用在意这些限制。

动态Prop

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

html代码

<div>
    <input v-model="parentMsg">
    <br>
    <child v-bind:my-message="parentMsg"></child>
</div>

js代码

Vue.component('child',{
        // 声明props
        props:['myMessage'],
        // 就像 data 一样,prop 可以用在模板内
        // 同样也可以在 vm 实例中像 “this.message” 这样使用
        template: '<span>{{ myMessage }}</span>'
    })
    new Vue({
        el:"#app",
        data:{
            name:"luoxue"
        }
    })

字面量语法 VS 动态语法

html:如果在为vue组件prop定义或者赋值的时候用了“v-bind:”的动态语法,则会先将其转化为js表达式,然后计算结果赋值,下面示例中,第一个传递的是字符串,第二个传递的是1+2即3的数值

<h3>#字面量语法 VS 动态语法</h3>
<div id="app">
    <!-- 这里传递是字符串 -->
    <child my-message="1+2"></child>
    <!-- 这里用了动态语法,传递的值会通过js的表达式计算,传递的是数字 -->
    <child :my-message="1+2"></child>
</div>

<script type="text/javascript">
    //字面量语法 VS 动态语法
    Vue.component("child", {
        props: ["myMessage"],
        template: "<div>myMessage: {{ myMessage }}</div>"
    });
    var vm = new Vue({
        el: "#app",
    });
</script>

单项数据流

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。
通常有两种改变 prop 的情况:

  • prop 作为初始值传入,子组件之后只是将它的初始值作为本地数据的初始值使用;
  • prop 作为需要被转变的原始值传入。

单项数据流 VS 双向数据流

<h3>Prop类型绑定(单向、双向)</h3>
<div id="dr05">
    <div>
        <div>vue实例:</div>
        <input v-model="name" />
    </div>
    <br />
    <div>
        <div>component组件(默认):</div>
        <child05 :my-message05="name"></child05>
        <div>component组件(once):</div>
        <child05 :my-message05.once="name"></child05>
        <div>component组件(sync):</div>
        <child05 :my-message05.sync="name"></child05>
    </div>
</div>

<script type="text/javascript">
    //Prop类型绑定
    Vue.component("child05", {
        props: ["myMessage05"],
        template: '<input type="text" v-model="myMessage05" />'
    });
    var dr05 = new Vue({
        el: "#dr05",
        data: {
            name: "DarkRanger",
        }
    });
</script>

Prop验证(转)

组件可以为 props 指定验证要求。如果未指定验证要求,Vue 会发出警告。当组件给其他人使用时这很有用。

prop 是一个对象而不是字符串数组时,它包含验证要求:

<h3>#Prop验证</h3>
<div id="dr06">
    <div>
        <div>
            name:{{dr.name}}, age:{{dr.age}}.</div>
        <div><input v-model="telphone" /></div>
    </div>
    <div>
        <child06 :msg_null="123+456" msg_string="this is string" :msg_number="99" :msg_twoway.sync="telphone" :msg_validate="mobilephone" :msg_number2String="mobilephone" :msg_obj2json="dr" :msg_json2obj="drJson"></child06>
    </div>
</div>


//Prop验证
Vue.component("child06", {
    props: {
        //基础类型检测("null"意思是任何类型都可以)
        msg_null: null,
        //String类型,必须是定义过的,可以是空字符串""
        msg_string: {
            type: String,
            required: true,
        },
        //Number类型,默认值100
        msg_number: {
            type: Number,
            default: 100,
        },
        //Object类型,返回值必须是js对象
        msg_obj: {
            type: Object,
            default: function() {
                return {
                    name: "DarkRanger",
                    age: "18",
                }
            }
        },
        //指定这个prop为双向绑定
        //如果绑定类型不对将抛出一条警告
        msg_twoway: {
            type: String,
            twoWay: true,
        },
        //自定义验证,必须是Number类型,验证规则:大于0
        msg_validate: {
            type: Number,
            validator: function(val) {
                return val > 0;
            }
        },
        //将值转为String类型
        //在设置值之前转换值(1.0.12+)
        msg_number2string: {
            coerce: function(val) {
                return val + ""
            }
        },
        //js对象转JSON字符串
        msg_obj2json: {
            coerce: function(obj) {
                return JSON.stringify(obj);
            }
        },
        //JSON转js对象
        msg_json2obj: {
            coerce: function(val) {
                return JSON.parse(val);
            }
        },
    },
    template: '<div>msg_null: {{msg_null}}</div>' 
    + '<div>msg_string:{{msg_string}}</div>' 
    + '<div>msg_number: {{msg_number}}</div>' 
    + '<div>msg_obj:{{"name-->"+msg_obj.name+", age-->"+msg_obj.age}}</div>' 
    + '<div>msg_twoway:<input v-model="msg_twoway"></div>' 
    + '<div>msg_validate:{{msg_validate}}</div>' 
    + '<div>msg_number2String: {{msg_number2string}}</div>' 
    + '<div>msg_obj2json: {{msg_obj2json}}</div>'
    +'<div>msg_json2obj:{{"name: "+msg_json2obj.name+"; age: "+msg_json2obj.age}}</div>'
});
var dr06 = new Vue({
    el: "#dr06",
    data: {
        telphone: "0356-1234567",
        mobilephone: 15912345678,
        dr: {
            name: "DarkRanger",
            age: 25
        },
        drJson: '{"name":"DarkRanger","age":25}',
    }
})

动态组件的tab切换

<h3>动态组件</h3>
<!-- 定义三个temp模板,用于切换 -->
<template id="temp-tab01">
    <div>this is tab01</div>
</template>
<template id="temp-tab02">
    <div>this is tab02</div>
</template>   
<template id="temp-tab03">
    <div>this is tab03</div>
</template>

<div id="dr01">
    <!-- 导航栏 -->
    <div class="border cf">
        <ul>
            <li><a href="javascript:void(0);" @click="toggleTabs(tab01Text);">{{tab01Text}}</a></li>
            <li><a href="javascript:void(0);" @click="toggleTabs(tab02Text);">{{tab02Text}}</a></li>
            <li><a href="javascript:void(0);" @click="toggleTabs(tab03Text);">{{tab03Text}}</a></li>
        </ul>
    </div>
    <!-- 点击导航后要切换的内容 -->
    <div class="border" style="height: 100px;">
        <!-- 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数 -->
        <component :is="currentView" keep-alive></component>
    </div>
</div>


<script type="text/javascript">
    //扩展组件tab01
    var tab01 = Vue.extend({
        template: "#temp-tab01",
    });
    //扩展组件tab02
    var tab02 = Vue.extend({
        template: "#temp-tab02",
    });
    //扩展组件tab03
    var tab03 = Vue.extend({
        template: "#temp-tab03",
    });
    //新建vue实例
    var dr01 = new Vue({
        el: "#dr01",
        data: {
            tab01Text: "tab01", //导航栏文本1
            tab02Text: "tab02", //导航栏文本2
            tab03Text: "tab03", //导航栏文本3
            currentView: 'tab01', //默认选中的导航栏
        },
        //局部注册组件
        components: {
            tab01: tab01,
            tab02: tab02,
            tab03: tab03,
        },
        methods: {
            //绑定tab的切换事件
            toggleTabs: function(tabText) {
                this.currentView = tabText;
            }
        }
    });     
</script>

使用slot分发内容

单个slot

除非子组件模板包含至少一个 <code><slot></code> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。

最初在 <code><slot></code> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

<h3>#单个slot</h3>
<!-- 定义child01模板 -->
<template id="child01">
    <div>this is temp01 component!</div>
    <slot>
        如果没有分发内容,这里将会被显示
    </slot>
</template>
<div id="dr01">
    <child01></child01>
    <br /><br />
    <child01>
        <div>这里有新的内容01</div>
        <div>这里有新的内容02</div>
    </child01>
</div>

<script type="text/javascript">
    //单个slot
    var child01 = Vue.extend({
        template: "#child01",
    });
    var dr01 = new Vue({
        el: "#dr01",
        components: {
            "child01": child01
        }
    });
</script>

具名slot

<code><slot></code> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。

仍然可以有一个匿名 slot ,它是默认 slot ,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot ,这些找不到匹配的内容片段将被抛弃。

<h3>#具名Slot(有名称的slot)</h3>
<!-- 定义模板child02 -->
<template id="child02">
    <slot name="s1"></slot>
    <slot></slot>
    <slot name="s2"></slot>
</template>
<div id="dr02">
    <child02>
        <div slot="s1">this is slot01</div>
        <div slot="s2">this is slot02</div>
        <div>this is a simple div01</div>
        <div>this is a simple div02</div>
    </child02>
</div>

<script type="text/javascript">
    //具名slot
    var child02 = Vue.extend({
        template: "#child02"
    });
    var dr02 = new Vue({
        el: "#dr02",
        components: {
            "child02": child02
        }
    });
</script>

编译作用域

作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。

当组件中拥有了slot插口的时候,组件内的内容片断都将替换掉slot

在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样:

<h3>编译作用域</h3>
<template id="child03">
    <div>the two items following is child msg:</div>
    <div>{{cmsg_01}}</div>
    <div>{{cmsg_02}}</div>
    <br />
    <div>the three items following is parent msg:</div>
    <slot name="s1"></slot>
    <slot></slot>
    <slot name="s2"></slot>
</template>
<div id="dr03">
    <child03>
        <div slot="s1">{{msg01}}</div>
        <div slot="s2">{{msg02}}</div>
        <div>{{msg03}}</div>
    </child03>
</div>
</body>

<script type="text/javascript">
    var child03 = Vue.extend({
        data: function() {
            return {
                cmsg_01: "this is child msg_01",
                cmsg_02: "this is child msg_02",
            }
        },
        template: "#child03",
    })
    var dr03 = new Vue({
        el: "#dr03",
        data: {
            msg01: "this is parent msg01",
            msg02: "this is parent msg02",
            msg03: "this is parent msg03",
        },
        components: {
            "child03": child03
        }
    });
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • 9.1 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,...
    白水螺丝阅读 767评论 0 2
  • 什么是组件 组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用...
    angelwgh阅读 780评论 0 0
  • 此文基于官方文档,里面部分例子有改动,加上了一些自己的理解 什么是组件? 组件(Component)是 Vue.j...
    陆志均阅读 3,806评论 5 14
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,046评论 0 29
  • Vue笔记系列1、Vue.js入门3、Vue.js进阶 API 以下会随用随记一些API,可能会不定期更新。 Vu...
    其心阅读 2,055评论 0 10