1.组件的嵌套

1.1 组件嵌套的基本使用

Vue 组件跟 Vue 实例是一样的,因此在 Vue 中一个组件中也可以定义并使用自己的局部组件,这就是组件的嵌套使用.

示例代码如下:

    <div id="app">
        <father-com></father-com>
    </div>

   <!--  定义父组件模板-->
    <template id="father">
        <div>
            <p>我是父组件</p>
            <son-com></son-com>
            <son-com />
        </div>
    </template>

   <!-- 定义子组件模板-->
    <template id = "son">
        <p>我是子组件</p>
    </template>
    <script>

        //子组件选项对象
        let SonCom = ({
            template:"#son",
        })

        //父组件选项对象
        let FatherCom = ({
            template: "#father",
            //将子组件定义为父组件的子组件
            components:{
                SonCom,
            }
        })

        let vm = new Vue({
            el:"#app",
            components: {
                FatherCom,
            }
        })
    </script>

结果
在这里插入图片描述
在这里父组件 FatherCom 也有自己的子组件就是 SonCom,.

通过示例我们就了解到,组件可以嵌套使用,那么我们就不得不思考一个问题,他们各自之间的数据关系如何?能否相互使用对方的数据呢?

1.2 组件间的数据关系

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。

示例:

<div id="app">
    <father-com></father-com>
</div>

<!--定义父组件模板-->
<template id="father">
    <div>
        <h3>我是父组件</h3>
        <p>父组件中的数据  {{msg}}</p>
        <son-com></son-com>

    </div>
</template>

<!--定义子组件模板-->
<template id = "son">
    <!--强行在子组件中使用父组件数据就会报错-->
    <h3>我是子组件{{msg}}</h3>
</template>

<script>
    let SonCom = ({
        template:"#son",
    })

    let FatherCom = ({
        template: "#father",
        //将子组件定义为父组件的子组件
        components:{
            SonCom,
        },
        data(){
            return{
                msg:"hello world"
            }
        }
    })

    let vm = new Vue({
        el:"#app",
        //注册局部组件
        components: {
            FatherCom,
        }
    })

在这里插入图片描述
根据我们上面展示的代码:我们在子组件中强行的使用了父组件中的数据 msg ,可以看到子组件中并没有接收到这个数据并且还会报错 : msg 没有定义

但是很多情况下,我们在子组件中是需要使用父组件中的数据的,这时候我们应该怎么办呢??

2. props属性

父组件可以使用 props 把数据传给子组件,子组件通过props 属性来接收父组件传递的数据.

2.1 props的基本使用

父组件在使用子组件时, 可以将父组件的数据绑定到使用子组件的自定义标签上,
子组件在选项中添加一个props属性来接收数据

示例代码如下:

 <div id="app">
        <father-com></father-com>
    </div>


    <!-- 定义父组件模板   -->
    <template id = "father">
        <div>
            <h2>我是父组件</h2>
            <p>
                父组件中的数据   {{msg}}
            </p>
            <!--    在父组件中使用子组件    -->
            <son-com :my-name="user.name" :my-age="user.age"></son-com>
        </div>

    </template>

    <!--    定义子组件模板-->
    <template id = "son">
        <div>
            <h3>我是子组件</h3>
            <p>我是子组件中的数据   {{msg}}</p>
            <p>
                我接收的父组件中的数据: {{myName}},{{myAge}}
            </p>
        </div>
    </template>
    <script>
        let SonCom = ({
            props:["myName","myAge"],
            template:"#son",
            data() {
                return{
                    msg:"你好,世界"
                }
            }
        })
        let FatherCom = ({
            template:"#father",
            data() {
                return {
                    msg:"hello world",
                    user:{
                        name:"why",
                        age:19,
                    }
                }
            },
            components: {
                SonCom,
            }
        })
        let vm = new Vue({
            el:"#app",
            components:{
                FatherCom,
            }
        })

在这里插入图片描述

2.2 数据传递的分类

我们都知道 js 的数据类型分为两大类,一类是 *基本数据类型 *还有一类是 引用数据类型
因此父组件向子组件传递的值也分为两类"

  1. 传值 : String Number Boolean
  2. 传引用 : Array Object

这两者之间会有不同,后面我们慢慢分析

2.2.1 传递数据注意事项

<div id="app">
  <conter :count="3"></conter>
  <conter :count="4"></conter>
</div>

<script>
  var conter = {
    props: ['count'],
    template:'<div @click="handleClick">{{count}}</div>',
    methods:{
      handleClick(){
        //  console.log(this.count)
        this.count++
      }
    }
  }
  var app = new Vue({
    el:'#app',
    components:{
      conter
    }
  })
</script>

在这里插入图片描述
其实这个时候我们发现在使用子组件传递数据时并没有使用父组件data中的数据,但仍然使用了v-bind动态绑定指令,为什么呢? 难道不用v-bind指令就不能传递数据了吗?

答案当然不是啦, 之前有讲过, 属性如果不使用v-bind指令绑定,那么属性值将是字符串,如果使用v-bind指令绑定, 属性值虽然还是引号,但是引号中确实JavaScript表达式

因此上面的示例中我们希望的是传递数据过去,那么引号中的3 只有在JavaScript表达式中才表示数字的字面量, 因此需要v-bind指令

我们也可以尝试一下,不是用v-bind的情况

<div id="app">
    <!-- 不使用v-bind 指令-->
    <conter count="3"></conter>
    <conter count="4"></conter>
</div>

<script>
    var conter = {
        props: ['count'],
        template:'<div @click="handleClick">{{count}}</div>',
        methods:{
            handleClick(){
                //  console.log(this.count)
                this.count++
            }
        }
    }
    var app = new Vue({
        el:'#app',
        components:{
            conter
        }
    })
</script>

在这里插入图片描述
通过上面两端代码,相信我们对于为什么要用 v-bind 命令已经有了一定的了解,就是说如果我们不使用指令,那么父组件传递给子组件的数据就会是字符串,
使用v-bind指令, 属性值引号中的内容将变成表达式,那么你就可以传递任何JS数据类型的数据

2.2.2 传递基本类型数据

父组件传递数据基本类型数据到子组件.
我们来看看在子组件中能否修改父组件中穿过来的数据

<div id="app">
    <father-com></father-com>
</div>
<!-- 定义父组件模板   -->
<template id = "father">
    <div>
        <h2>我是父组件</h2>
        <p>
            父组件中的数据   {{msg}}
        </p>
        <!--    在父组件中使用子组件    -->
        <son-com :my-name="user.name" :my-age="18"></son-com>
    </div>

</template>

<!--    定义子组件模板-->
<template id = "son">
    <div>
        <h3>我是子组件1</h3>
        <p>我是子组件1中的数据   {{msg}}</p>
        <p>
            我接收的父组件中的数据: {{myName}},{{myAge}}
        </p>
        <button @click=" changeName">点击切换人名</button>
    </div>
</template>
<script>
    let SonCom = ({
        props:["myName","myAge"],
        template:"#son",
        data() {
            return{
                msg:"你好,世界",
            }
        },
        //直接修改父组件传过来的值会报错(基本数据类型)
        methods:{
            changeName() {
                this.MyAge = 9
            }
        }
    })
    let FatherCom = ({
        template:"#father",
        data() {
            return {
                msg:"hello world",
                user:{
                    name:"why",
                    age:19,
                }
            }
        },
        components: {
            SonCom
        }
    })

    let vm = new Vue({
        el:"#app",
        components:{
            FatherCom,
        }
    })

父组件向子组件传递了 user 对象中的 name 为字符串, age为数字类型.在子组件中添加了一个按钮并且添加了 change 方法来改变年龄,在按按钮之前的显示结果如下:
在这里插入图片描述
按按钮之后结果如下图,虽然结果改成了9,但是会报错.
在这里插入图片描述

2.2.2 传递引用类型数据

传引用就是传递引用类型的数据,
其实我们最想关注的是,传递引用类型的数据是数据的拷贝,还是内存地址的拷贝
因为这涉及到在子组件中是否可以通过props修改父组件中的数据

<div id="app">
    <father-com></father-com>
</div>
<!-- 定义父组件模板   -->
<template id = "father">
    <div>
        <h2>我是父组件</h2>
        <p>
            父组件中的数据   {{msg}}
        </p>
        <!--    在父组件中使用子组件    -->
        <son-com
                :menu="menu"
        ></son-com>
        <second-com
                :menu="menu"
        ></second-com>
    </div>

</template>

<!--    定义子组件模板-->
<template id = "son">
    <div>
        <h3>我是子组件1</h3>
        <p>我是子组件1中的数据   {{msg}}</p>
        菜单{{menu}}
    </div>
</template>

<!--定义2号子组件-->
<template id = "secondSon">
    <div>
        <h3>我是子组件2</h3>
        <p>我是子组件2中的数据   {{msg}}</p>
        <p>
          菜单{{menu}}
        </p>
    </div>
</template>

<script>
    let SonCom = ({
        props:["menu"],
        template:"#son",
        data() {
            return{
                msg:"你好,世界"
            }
        },
    })

    let secondCom = ({
        props:['menu'],
        template:"#secondSon",
        data() {
            return {
                msg : "我就说2号组件中的数据,没错,我就是222222"
            }
        }
    })

    let FatherCom = ({
        template:"#father",
        data() {
            return {
                msg:"hello world",
                menu:{
                    name:"辣椒炒肉",
                    price:14,
                }
            }
        },
        components: {
            SonCom,secondCom
        }
    })

    let vm = new Vue({
        el:"#app",
        components:{
            FatherCom,
        }
    })
</script>

父组件向两个子组件都传递了引用类型数据 – 对象,两个组件都接受了数据,在控制台中也可以看到这两组数据是一模一样的.
在这里插入图片描述
可是在 SonCom 子组件中定义了一个按钮用于改变 SonCom 这个子组件通过 props从父组件获取过来的数据

在在原先子组件相关代码的基础上改变代码


<!--    定义子组件模板-->
<template id = "son">
    <div>
        <h3>我是子组件1</h3>
        <p>我是子组件1中的数据   {{msg}}</p>
        菜单{{menu}}
        <button @click=" changeName">点击切换名字</button>
    </div>
</template>

  let SonCom = ({
        props:["menu"],
        template:"#son",
        data() {
            return{
                msg:"你好,世界"
            }
        },
        //直接修改父组件传过来的值会报错(基本数据类型)

        // 父组件向子组件传递数据是传引用(引用数据类型), 直接修改引用地址就会报错,
        // 如果不变内存地址,只是改变引用数据的属性值,不会报错,
        // 虽然不会报错,但是不要改:
        // 原因在于
        //  1. 不符合vue的单项下行数据流的思想
        //  2.  如果一旦修改所有使用父组件这个数据的子组件都会发生改变
        methods:{
            changeName() {
                this.menu.name = "西红柿炒鸡蛋"
            }
        }
    })

点击按钮后的显示结果:
在这里插入图片描述
当在子组件 SonCom 中改变了父组件中传递过来的数据时, 另外一个子组件 SecondCom 中的数据也发生了改变,就连父组件中的数据也被改变了.

原因在于父组件向子组件传递的是引用类型的数据,也就是说是引用类型数据的内存
地址,也就是说这两个组件共享一个内存地址,当其中的一个数据发生改变的时候另外一个的数据也会随而改变,

那么还有一种方案就是直接给组件一个新的内存地址,看看会发生什么,看下面的代码:

 let SonCom = ({
        props:["menu"],
        template:"#son",
        data() {
            return{
                msg:"你好,世界"
            }
        },
        //直接修改父组件传过来的值会报错(基本数据类型)

        // 父组件向子组件传递数据是传引用(引用数据类型), 直接修改引用地址就会报错,
        // 如果不变内存地址,只是改变引用数据的属性值,不会报错,
        // 虽然不会报错,但是不要改:
        // 原因在于
        //  1. 不符合vue的单项下行数据流的思想
        //  2.  如果一旦修改所有使用父组件这个数据的子组件都会发生改变
        methods:{
            changeName() {
                //如果直接修改内存地址就会报错
                 this.menu = {
                   name:"西红柿炒鸡蛋",
                 }
            }
        }
    })

在这里插入图片描述
我们这样做尽管成功修改了 SonCom 组件中的数据且没有影响其他的子组件,但是会报错.

总结:
vue采用的是单向下行数据流,

  1. 不能(也不要)在子组件中直接修改父组件的传递过来数据
  2. 父组件向子组件传递数据如果是传值(基本数据类型)子组件直接修改就会报错
  3. 父组件向子组件传递数据是传引用(引用数据类型), 直接修改引用地址就会报错,
  4. 如果不变内存地址,只是改变引用数据的属性值,不会报错,

虽然不会报错,但是不要改:
原因有:

  1. 不符合vue的单项下行数据流的思想
  2. 如果一旦修改所有使用父组件这个数据的子组件都会发生改变

原则子组件不要(也不能)直接修改props接受的数据

2.3 单向下行数据流解释

Vue 默认是单向数据流,所谓的单向数据流,就是数据传递是单向的

既然父组件将数据传递给了子组件,那么子组件中如果对于数据进行改变就有可能影响其他使用这数据的组件,在前面传递基本数据类型那里已经有提到,如果我们在子组件中袖该父组件传递过来的基本数据类型,可以修改,但是修改之后户报错.

现在值之前代码的基础上我们在父组件中修改数据,看看会发生什么,看下面的代码:

<div id="app">
    <father-com></father-com>
</div>
<!-- 定义父组件模板   -->
<template id = "father">
    <div>
        <h2>我是父组件</h2>
        <p>
            父组件中的数据   {{msg}}

         </p>

        <p>
            user对象中的数据
            {{user.name}}
            {{user.age}}
        </p>
        <!--    在父组件中使用子组件    -->
        <son-com :my-name="user.name" :my-age="user.age"></son-com>

        <button @click="change">点击修改数据</button>
    </div>

</template>

<!--    定义子组件模板-->
<template id = "son">
    <div>
        <h3>我是子组件1</h3>
        <p>我是子组件1中的数据   {{msg}}</p>
        <p>
            我接收的父组件中的数据: {{myName}},{{myAge}}
        </p>

    </div>
</template>
<script>
    let SonCom = ({
        props:["myName","myAge"],
        template:"#son",
        data() {
            return{
                msg:"你好,世界",
            }
        },
    })
    let FatherCom = ({
        template:"#father",
        data() {
            return {
                msg:"hello world",
                user:{
                    name:"why",
                    age:19,
                }
            }
        },
        components: {
            SonCom
        },
        methods:{
            change() {
               this.user.age = 9;
               this.user.name ="彭昱畅"
            }
        }
    })

    let vm = new Vue({
            el:"#app",
            components:{
                FatherCom,
            }
    })

我们在父组件中添加方法 change ,用来改变user对象中的数据,点执行这个方法之前的结果如下:
在这里插入图片描述
但我们点击按钮之后,数据会发生如下变化:
在这里插入图片描述
因此我们会发现,当我们把父组件中的数据改变的时候,子组件中的数据也会发生改变.

但是当我们通过子组件来改变父组件中的数据的时候,父组件中的数据不会改变,还会有报错信息出现.

所以会有:

  1. 当父组件数据更新时,传递给子组件的props也会更新,
  2. 当子组件修改了props的数据, 父组件不会有任何变化(基本数据类型报错,引用类型直接替换引用地址也报错, 但是直接修改属性不报错,但是不建议这么用)

总结:

  1. 父组件向子组件可以传递基本数据类型和引用数据类型
  2. 子组件如果直接修改 props 中的父组件传过来的基本数据类型引用数据类型的内存地址 就会报错
  3. 如果直接修改的是引用数据类型里面的值虽然不会报错,但是这样做非常的不友好,因此不推荐在子组件中直接修改 `props 中的数据

这就是单行数据流,这样做的目的是为了防止子组件在无意中改变父组件的数据

3. props响应式

通过上一个例子,我们发现父组件的数据发生变化, 子组件也会随着发生变化, 也就是,父组件在使用子组件时,给子组件 prop 传递的数组无论何时发生改变, 在子组件内任何使用该 prop 的地方都会发生更新,这就是props响应式
示例:

 <div id="app">
        <father-com></father-com>
    </div>

    <template id="son">
        <div>
            我总共被点击了{{num}}</div>

    </template>

    <template id="father">
        <div>
            <son-com :num="num"></son-com>
            <button @click=" add">点击+1</button>
        </div>

    </template>

    <script>
        //无论点击多少次,只要父组件的数据一发生改变,子组件的数据也会马上跟着改变
        let SonCom = ({
            props:["num"],
            template:"#son",

        })

        let FatherCom = ({
            template:"#father",
            data(){
                return {
                    num:0,
                }
            },
            components: {
                SonCom,
            },
            methods:{
                add(){
                    this.num++;
                }
            }
        })

        let vm = new Vue({
            el:"#app",
            components: {
                FatherCom,
            }
        })
     </script>

在这里插入图片描述
不管点击按钮多少次,只要父组件中的数据发生改变,那么子组件中的数据一定会随之而改变.

4.props验证

我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。

要指定验证规则,需要用对象的形式来定义prop,而不能用字符串数组:

4.1 验证父组件传过来数据的类型

// 子组件选项
let sonComponent = {
    // props 验证数据类型
    props:{
        myName:String,
        myAge: Number
    },
    template:`#son`,
}

如果传过来的数据符合要求,则会通过,如果不符合要求,则会报错.

我故意给 myAge 传一个字符串你类型数据,看看下面的报错信息:
在这里插入图片描述

4.2 允许传递多种类型数据

如果一个数据可以接受多种数据类型,则使用数组将所有允许的类型罗列在一起

   let SonCom = ({
        //允许数据传多种类型的参数
        props:{"myName":String,
            "myAge":[Number,String],
            "menu":Object,
        },
        template:"#son",
    })

这样myAge 在接受的数据是Number 类型或String 都不会报错

注:
这两种验证数据的类型,只验证父组件传递过来数据的类型是否符合, 并不关心用户是否传数据过来, 不传也不会报错,

4.3 设置必须传递的数据

有的时候我们需要指定一些数据为必须传递的, 如果不传递就会报错, 这个时候,数据的只是一个对象

对象就是对于props传递数据的配置对象

验证的配置对象中
type: 验证数据类型
required: 验证数据是否为必须传递,true,是必须传递,不传就报错

// 子组件选项
let SonCom = {
    props:{
        myName:String,
        myAge: {
            type:Number,   // type为验证数据类型
            required: true  // required为数据是否必须传递,true是false 否
        }
    },
    template:`#son`,
}

4.4 指定默认值

如果父组件未传递数据过来,则使用默认值


配置对象中required 必传选项 和 default 默认值选项,不能同时使用

默认是就是父组件在未传递数据的时候使用, 如果你还需要父组件必须传递数据, 默认值就不没有意义了嘛

// 子组件选项
let sonComponent = {
    props:{
        myName:{
            type: String,    // 验证类型
            default: '默认姓名'   // 默认值
        },
        myAge: {
            type:Number,   // type为验证数据类型
            required: true  // required为数据是否必须传递,true是false 否
        }
    },
    template:`#son`,
}

如果传递过来的是是引用类型, 那么在定义默认值的时候必须是一个函数,函数返回引用类型的数据
为什么是一个函数就不用再说吧,和组件数据data是函数同一个意思, 保存每次传递都是第一无二的数据

示例;

let sonComponent = {
    props:{
        myName:{
            type: String,    // 验证类型
            default: '默认姓名'   // 可以赋予默认值,如果父组件没有传值,使用默认值
        },
        myAge: {
            type:Number,   // type为验证数据类型
            required: true  // required为数据是否必须传递,true是false 否
                            //  此属性表示必须传值,但是不能跟default同用
        },
        myLike:{
            type:Array,       // 限定的数据类型是引用类型的数组
            default: function(){   //如果传递过来的是一个引用类型的值,默认值是函数
                 return []   
            }
        }
    },
    template:`#son`,
}

4.5 自定义验证规则

自定义验证是一个函数,返回true则验证通过,返回false则验证不通过
示例:

let sonComponent = {
    // 子组件通过props接受数据并使用
    // 数组里放父组件中自定义属性的名字
    // props 里面使用驼峰写法
    props:{
        myName:{
            type: String,    // 验证类型
            default: '默认姓名'   // 默认值
        },
        myAge: {    
            validator:function(value){ //   自定义验证器
                return value > 16        // 返回true 验证通过, 返回false 验证不通过报错
            }
        }
    },
    template:`#son`,
}

props 会在组件实例创建之前进行校验,

所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性都还无法使用。

props特性就是父组件通过属性传值,子组件有对应的props接受,那么这个属性不会出现在网页的标签属性上

Logo

前往低代码交流专区

更多推荐