[vue]8-vue组件1

父组件和子组件

在前面我们看到了组件树:

  • 组件和组件之间存在层级关系
  • 而其中一种非常重要的关系就是父子组件的关系

我们来看通过代码如何组成的这种层级关系:

    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script>
        const myComponent1 = Vue.extend({
            template:`
                <div>
                    <h2>子组件标题</h2>
                    <p>我是子组件中的段落</p>
                </div>
            `
        })
        const myComponent = Vue.extend({
            template: `
            <div>
                <h2>组件标题</h2>
                <p>我是组件中的段落内容</p>
                <my-cpn2></my-cpn2>
            </div>s
            `,
            components: {
                'my-cpn2': myComponent1
            }
        })
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': myComponent
            }
        })
    </script>

在这里插入图片描述

上面有两个父子关系,也算是 爷-爸-儿

两两之间属于父子关系。

父子组件错误用法:以子标签的形式在Vue实例中使用

这个时候在vue中写未注册的组件,浏览器是不会识别的

所以,当子组件注册到父组件的components时,Vue会编译好父组件的模块

该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)

注册组件语法糖

在上面注册组件的方式,可能会有些繁琐。

  • Vue为了简化这个过程,提供了注册的语法糖。
  • 主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。

语法糖注册全局组件和局部组件:

没有语法糖的全局注册组件

    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script>
        const myCpn = Vue.extend({
            template: `
                <div>
                    <h2>我是全局注册组件</h2>
                    <p>我是全局注册组件,我没有使用语法糖</p>
                </div>
            `
        })
        Vue.component('my-cpn', myCpn)
        let app = new Vue({
            el: '#app',
        })
    </script>

全局注册组件语法糖

    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script>
        Vue.component('my-cpn', {
            template: `
            <div>
                <h2>我是全局注册组件</h2>
                <p>我是全局注册组件的内容,我使用了语法糖</p>
            </div>
          `
        })
        let app = new Vue({
            el: '#app',
        })
    </script>

没有语法糖的局部注册组件:

    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script>
        const myCpn = Vue.extend({
            template: `
                <div>
                    <h2>我是局部注册组件</h2>
                    <p>我是局部注册组件,我没有使用语法糖</p>
                </div>
            `
        })
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': myCpn
            }
        })
    </script>

没有语法糖的局部注册组件:

    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': {
                    template: `
                    <div>
                        <h2>我是局部注册组件</h2>
                        <p>我是局部注册组件,我使用了语法糖</p>
                    </div>
                    `
                }
            }
        })
    </script>

模板的分离写法

刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。

如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。

Vue提供了两种方案来定义HTML模块内容:

  • 使用
    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <script type="text/x-template" id="myCpn">
        <div>
            <h2>组件标题</h2>
            <p>我是组件的内容,我通过script来写的</p>
        </div>
    </script>
    <script>
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': {
                    template: '#myCpn'
                }
            }
        })
    </script>
  • 使用标签
    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <h2>组件标题</h2>
            <p>我是组件的内容,我通过script来写的</p>
        </div>
    </template>
    <script>
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': {
                    template: '#myCpn'
                }
            }
        })
    </script>

组件的数据存放

组件可以访问Vue实例数据吗?

组件是一个单独功能模块的封装:

  • 这个模块有属于自己的HTML模板,也应该有属性自己的数据data。

组件中的数据是保存在哪里呢?顶层的Vue实例中吗?

  • 我们先来测试一下,组件中能不能直接访问Vue实例中的data

在这里插入图片描述

在这里插入图片描述

它是不会显示的,而且告诉我们,message没有定义,但是我们在父组件是定义了的,所以,得出一个结论,子组件是取不到父组件中的data的

结论:Vue组件应该有自己保存数据的地方。

组件自己的数据存放在哪里呢?

  • 组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
  • 只是这个data属性必须是一个函数
  • 而且这个函数返回一个对象,对象内部保存着数据
    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <h2>组件标题</h2>
            <p>{{message1}}</p>
        </div>
    </template>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                message: '我是数据'
            },
            components: {
                'my-cpn': {
                    template: '#myCpn',
                    data() {
                        return {
                            message1: '我是子组件的数据'
                        }
                    }
                }
            }
        })
    </script>

在这里插入图片描述

为什么是一个函数呢?

  • 首先,如果不是一个函数,Vue直接就会报错。
  • 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

如果返回的是一个固定对象

    <div id="app">
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <h2>{{counter}}</h2>
            <button @click="btnClick">点击</button>
        </div>
    </template>
    <script>
        const obj = {
            counter: 0
        }
        let app = new Vue({
            el: '#app',
            data: {
                message: '我是数据'
            },
            components: {
                'my-cpn': {
                    template: '#myCpn',
                    data() {
                        // 这里返回的始终是一个obj,所以返回的地址始终是一个
                        return obj
                    },
                    methods:{
                        btnClick() {
                            this.counter++
                        }
                    }
                }
            }
        })
    </script>

在这里插入图片描述

每一次返回的都是一个新的对象

    <div id="app">
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
        <my-cpn></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <h2>{{counter}}</h2>
            <button @click="btnClick">点击</button>
        </div>
    </template>
    <script>
        const obj = {
            counter: 0
        }
        let app = new Vue({
            el: '#app',
            data: {
                message: '我是数据'
            },
            components: {
                'my-cpn': {
                    template: '#myCpn',
                    data() {
                        // 这里返回的始终是一个对象,但是每一次返回的都是一个新对象,也就是每返回一次,都重新建立一个对象返回出去,所以每一次的地址都不同。
                        return {
                            counter: 0
                        }
                    },
                    methods:{
                        btnClick() {
                            this.counter++
                        }
                    }
                }
            }
        })
    </script>

在这里插入图片描述

每写一个组件,其实就建立了一个data,所以它们都会不一样,不同组件的data是不同的。

组件通信

父子通信

props基本用法

在组件中,使用选项props来声明需要从父级接收到的数据。

props的值有两种方式:

  • 方式一:字符串数组,数组中的字符串就是传递时的名称。
    <div id="app">
        <my-cpn :message1="message"></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <h2>{{message1}}</h2>
        </div>
    </template>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                message: '父组件向子组件传递的数据'
            },
            components: {
                'my-cpn': {
                    template: '#myCpn',
                    props:['message1']
                }
            }
        })
    </script>

在这里插入图片描述

  • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。

在前面,我们的props选项是使用一个数组。

我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

验证都支持哪些数据类型呢?

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
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
      }
    }
  }

另外需要注意:官网中介绍了

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 datacomputed 等) 在 defaultvalidator 函数中是不可用的。

场景大概是,如果你想在类型检查的时候和data中的数据进行对比,是不支持的。

子组件向父组件传递数据

  • props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
  • 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
  • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

自定义事件的流程:

  • 在子组件中,通过$emit()来触发事件。
  • 在父组件中,通过v-on来监听子组件事件。

我们来看一个简单的例子:

  • 我们之前做过一个两个按钮+1和-1,点击后修改counter。
  • 我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
  • 这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total。

自定义事件代码

自定义事件

emit 可以触发绑定在组件身上自定义事件。

  • 在组件标签上绑定一个自定义事件。
  • 在组件的内部通过$emit触发这个自定义事件。参数1自定义事件名,第二个参数可以传参。
  • 从而达到在子组件的事件里触发父组件的方法。
    <div id="app">
        <my-cpn @plus='watchSon'></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <button @click="btn">点击</button>
        </div>
    </template>
    <script>
        const myCpn = {
            template: '#myCpn',
            methods: {
                btn() {
                    this.$emit('plus')
                }
            }
        }
        z
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn': myCpn
            },
            methods: {
                watchSon() {
                    console.log(123)
                }
            }
        })
    </script>

在这里插入图片描述

实现计数器

    <div id="app">
        <p>当前计数:{{counter}}</p>
        <my-cpn @plus='watchSon'></my-cpn>
    </div>
    <template id="myCpn">
        <div>
            <button @click="btn">点击</button>
        </div>
    </template>
    <script>
        const myCpn = {
            template: '#myCpn',
            methods: {
                btn() {
                    this.$emit('plus')
                }
            }
        }
        
        let app = new Vue({
            el: '#app',
            data:{
                counter: 0
            },
            components: {
                'my-cpn': myCpn
            },
            methods: {
                watchSon() {
                    this.counter += 1;
                }
            }
        })
    </script>

父子组件的访问方式-1

$children

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

  • 父组件访问子组件:使用 c h i l d r e n 或 children或 childrenrefs
  • 子组件访问父组件:使用$parent

我们先来看下$children的访问

  • this.$children是一个数组类型,它包含所有子组件对象。
  • 我们这里通过一个遍历,取出所有子组件的message状态。
    <div id="app">
        <my-cpn1></my-cpn1>
        <my-cpn2></my-cpn2>
        <my-cpn3></my-cpn3>
        <button @click="showSonMessage">展示子组件信息</button>
    </div>
    <template id="myCpn">
        <div>
            我是子组件
        </div>
    </template>
    <script>
        const myCpn1 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息1'
                }
            }
        }
        const myCpn2 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息2'
                }
            }
        }
        const myCpn3 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息3'
                }
            }
        }
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn1': myCpn1,
                'my-cpn2': myCpn2,
                'my-cpn3': myCpn3
            },
            methods: {
                showSonMessage() {
                    console.log(this.$children)
                    this.$children.map((item, index) => {
                        console.log(item.message)
                    })
                }
            }
        })
    </script>

在这里插入图片描述

$children的缺陷:

  • 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
  • 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
  • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs

父子组件的访问方式-2

$refs(获取dom元素)

$refs的使用:

  • $refs和ref指令通常是一起使用的。
  • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
  • 其次,通过this.$refs.ID就可以访问到该组件了。
    <div id="app">
        <my-cpn1 ref="cpn1"></my-cpn1>
        <my-cpn2 ref="cpn2"></my-cpn2>
        <my-cpn3 ref="cpn3"></my-cpn3>
        <button @click="showSonMessage">展示子组件信息</button>
    </div>
    <template id="myCpn">
        <div>
            我是子组件
        </div>
    </template>
    <script>
        const myCpn1 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息1'
                }
            }
        }
        const myCpn2 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息2'
                }
            }
        }
        const myCpn3 = {
            template: '#myCpn',
            data() {
                return {
                    message: '子组件信息3'
                }
            }
        }
        let app = new Vue({
            el: '#app',
            components: {
                'my-cpn1': myCpn1,
                'my-cpn2': myCpn2,
                'my-cpn3': myCpn3
            },
            methods: {
                showSonMessage() {
                    console.log(this.$refs.cpn1)
                    console.log(this.$refs.cpn2)
                    console.log(this.$refs.cpn3)
                }
            }
        })
    </script>

在这里插入图片描述

一样可以获取到子组件信息

子父组件的访问方式

$parent

如果我们想在子组件中直接访问父组件,可以通过$parent

注意事项:

  • 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
  • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
  • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
  • 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
    <div id="app">
        <my-cpn></my-cpn>
    </div>
    <template id="myCpn">
        <button @click="showFatherMessage">展示父组件信息</button>
    </template>
    <script>
        const myCpn = {
            template: '#myCpn',
            methods: {
                showFatherMessage() {
                    console.log(this.$parent.message)
                }
            }
        }
        let app = new Vue({
            el: '#app',
            data: {
                message: '我是父组件的信息'
            },
            components: {
                'my-cpn': myCpn,
            },
        })
    </script>

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值