【Vue教程五】创建和使用组件、父子组件传值、自定义输入框、prop详解和验证

一、创建和使用组件

组件名的命名规范有两种:

1.1、使用 kebab-case:(短横线分隔命名的形式)

Vue.component('my-component-name', { /* ... */ })

此时引用该组件,必须使用kebab-case

<my-component-name></my-component-name>

1.2、使用PascalCase:(首字母大写、或驼峰命名的形式)

Vue.component('MyComponentName', { /* ... */ })

注意,此时引用该组件,可以使用 <my-component-name>,但是使用 <MyComponentName>会报错

基本实例如下:

<body>
    <div id="app">
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>
    <script>
        // 定义一个名为 button-counter 的新组件
        Vue.component('button-counter', {
            data: function () {
                return {
                    count: 0
                }
            },
            template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
        })
        new Vue({ el: '#app' })
    </script>
</body>

注意:data必须是一个函数!template模板只能包含一个根元素!

1、全局注册:

注册一个全局组件,基本语法格式如下:

Vue.component('my-component-name', { /* ... */ })// 第一个参数‘my-component-name’为组件名

方式1:使用 Vue.extend 配合 Vue.component 方法:

var login = Vue.extend({
  template: '<h1>登录</h1>'
}); 
Vue.component('login', login);

方式2:直接使用 Vue.component 方法:

Vue.component('register', {
  template: '<h1>注册</h1>'
}); 

 方法3:将模板字符串,定义到template标签中:(放在被控制的#app外面)

<template id="tmpl">
  <div><a href="#">登录</a> | <a href="#">注册</a></div>
</template>

同时,需要使用 Vue.component 来定义组件:

Vue.component('account', {
  template: '#tmpl'
}); 

3、局部注册

<body>
    <div id="app">
        <component-a></component-a>
        <component-b></component-b>
    </div>
    <script>
        var ComponentA = { template: `<h1>ComponentA</h1>` }
        var ComponentB = { template: '<h1>ComponentB</h1>' }
        new Vue({
            el: '#app',
            components: {
                'component-a': ComponentA,
                'component-b': ComponentB
            }
        })
    </script>
</body>

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

在模块系统中局部注册

举例:在一个假设的 ComponentB.js 或 ComponentB.vue 文件中,使用ComponentA和ComponentC:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

二、给子组件传值:

1、定义子组件时,声明props: props: ['title'] ,实际上任何类型的值都可以传给一个 prop,自定义的方法也可以传入。

2、父组件调用子组件时,定义一个属性 title=‘xxx’

<body>
    <div id="app">
        <blog-post title="My journey with Vue"></blog-post>
        <blog-post title="Blogging with Vue"></blog-post>
        <blog-post title="Why Vue is so fun"></blog-post>
    </div>
    <script>
        Vue.component('blog-post', {
            props: ['title'],
            template: '<h3>{{ title }}</h3>'
        })
        new Vue({ el: '#app' })
    </script>
</body>

在页面显示结果如下:

如果数据是以对象数组的方式获取,那么上面代码可以修改如下:

<body>
    <div id="app">
        <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></blog-post>
    </div>
    <script>
        Vue.component('blog-post', {
            props: ['title'],
            template: '<h3>{{ title }}</h3>'
        })
        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' }
                ]
            }
        })
    </script>
</body>

说明:从上面我们可以看到,我们可以使用 v-bind 来动态传递 prop,例如 v-bind:title="post.title"

三、子组件向父组件传值:监听子组件事件

1、父组件调用子组件时,添加属性 v-on:enlarge-text="..." 监听子组件实例(这种是自定义事件)。

2、定义子组件时,可以通过调用 v-on:click="$emit('enlarge-text')" 方法 并触发一个事件。

例如,子组件有一个按钮,点击后用于放大字号,即改变父组件中的postFontSize属性:

<body>
    <div id="app">
        <div :style="{ fontSize: postFontSize + 'em' }">
            <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post"
                v-on:enlarge-text="postFontSize += 0.1">
            </blog-post>
        </div>
    </div>
    <script>
        Vue.component('blog-post', {
            props: ['post'],
            template: `
            <div class="blog-post">
                <h3>{{ post.title }}</h3>
                <button v-on:click="$emit('enlarge-text')">
                    Enlarge text
                </button>
            </div>
            `
        })
        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' }
                ],
                postFontSize: 1,
            }
        })
    </script>
</body>

说明:template模板中的元素如果想要换行显示,多行的模板更易读,需要用到模板字符串(` ... `)

上面代码中的'enlarge-text'为事件名,事件名不存在任何自动化的大小写转换。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到:

this.$emit('myEvent')
<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

因此,我们推荐你始终使用 kebab-case 的事件名

如果一个事件需要抛出一个特定的值,例如要上面文本要放大多少,这时使用  $emit 的第二个参数来提供这个值,在父组件通过 $event 访问到这个值。

<body>
    <div id="app">
        <div :style="{ fontSize: postFontSize + 'em' }">
            <blog-post ... v-on:enlarge-text="postFontSize += $event">
            </blog-post>
        </div>
    </div>
    <script>
        Vue.component('blog-post', {
            props: ['post'],
            template: `
            <div class="blog-post">
                <h3>{{ post.title }}</h3>
                <button v-on:click="$emit('enlarge-text',0.1)">
                    Enlarge text
                </button>
            </div>
            `
        })
        new Vue({
            el: '#app',
            data: {
                ...
            }
        })
    </script>
</body>

如果处理函数是一个方法,那么上面抛出的值将会作为第一个参数传入这个方法,可以这么写:

<body>
    <div id="app">
        <div :style="{ fontSize: postFontSize + 'em' }">
            <blog-post ... v-on:enlarge-text="onEnlargeText">
            </blog-post>
        </div>
    </div>
    <script>
        Vue.component('blog-post', {
           ...
        })
        new Vue({
            el: '#app',
            data: {
                ...
            },
            methods: {
                onEnlargeText: function (enlargeAmount) {
                    this.postFontSize += enlargeAmount
                }
            }
        })
    </script>
</body>

还有剩下两种子传父的方法,可查看文章:https://www.csdn.net/tags/OtDaggxsNTEzNjctYmxvZwO0O0OO0O0O.html​​​​​​​

四、在组件上使用 v-model:自定义输入框 

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

当用在组件上时,v-model  则会这样:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 <input> 必须:
A、将其 value 特性绑定到一个名叫 value 的 prop 上;
B、在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出。

下面是自定义一个输入框组件:

<body>
    <div id="app">
        <custom-input v-model="searchText"></custom-input>
    </div>
    <script>
        Vue.component('custom-input', {
            props: ['value'],
            template: `
                <input
                    v-bind:value="value"
                    v-on:input="$emit('input', $event.target.value)"
                >
            `
        })
        var vm = new Vue({
            el: '#app',
            data: {
                searchText: 'hello'
            }
        })
    </script>
</body>

五、prop详解和验证

1、prop的大小写:

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

注:如果你使用字符串模板,那么这个限制就不存在了。

2、prop类型:

目前我们只看到了字符串数组形式的prop,例如:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

但是,如果你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

3、单向数据流

父级 prop 的更新会向下流动到子组件中,但是反过来则不行。但是如果子组件想要修改prop数据,下面有两种情形:

A、情形一:prop 传递过来一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。

在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值。

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

B、情形二:prop 传递过来一个初始值且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

4、prop验证

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值