学过Vue的都知道Vue等MVVM框架相对于传统的JS库比如Jquery最大的区别在于数据驱动视图,重点在于数据,拿到数据后将数据通过模板{{}}语法或者v-html展示在页面上。
我们也都知道在Vue父子组件可以通过Props实现父组件传递到子组件。
在项目开发中,我们会遇到这种需求,页面初始化时,父组件通过接口拿到需要数据,然后拿到的数据通过props传递给子组件。在子组件会有些业务上的操作来改变接受的props值

注意Vue中子组件不能直接更改props值,这样会报错。

父组件需要拿到字组件改变后的值作为接口请求参数的值。
为了实现这种需求,我们一般会在data中定义某个属性,这个属性引用props的某个值。然后监听该数据,当该数据发生变化时,向父级组件传递自定义事件和改变后的值。

// 子组件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>局部组件的使用</title>
</head>
<body>
    <div id="app">

        <h1>在有template选项时,#app里的内容不展示</h1>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">

// 全局组件在声明时已经挂在到全局,可以直接使用
Vue.component('Parent', {
    template: `
        <div>
            <p>我是父组件</p>
            <Child :childDataA="msg"/>
          
        </div>
    `,
    data() {
        return {
            msg: '传递给子组件的数据'
        }
    },
    methods: {
        childHandler(val) {
            console.log(val)
        }
    }
})

Vue.component('Child', {
    template: `
        <div>
            <p>我是子组件</p>
            {{ childDataA }}
            <input type="text" v-model="childDataA" @input="changeValue">
        </div>
    `,
    // 指定props属性的类型时,会对传入的参数进行类型检查,如果不符合就会报错
    props: {
        childDataA: {
            type: String,
            default: ''
        },
        childDataB: {
            type: Object,
            default: null
        }
    },
    data() {
        return {
            msgA: this.childDataA,
            msgB: this.childDataB
        }
    },
    methods: {
        changeValue() {
            this.$emit('childHandler', this.msg)
        }
    }
})

// 声明局部组件App
const App = {
    template: `
        <div>
            <Parent />
        </div>
    `
}
new Vue({
    el: '#app',
    data() {
        return {

        }
    },
    // 挂在子组件
    components: {
        App
    },
    //使用子组件
    template: '<App/>'
})
</script>
</html>

在上面的代码中定义了子组件Child和父组件Parent,子组件的input框通过v-model绑定接受的props的childDataA,页面初始化如下
在这里插入图片描述

当在文本框输入其他值时
在这里插入图片描述
会提醒你避免直接更改props属性,而是基于props基础上定义data或者计算属性来操作。

接下来我们看另外一种情况。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>局部组件的使用</title>
</head>
<body>
    <div id="app">

        <h1>在有template选项时,#app里的内容不展示</h1>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">

// 全局组件在声明时已经挂在到全局,可以直接使用
Vue.component('Parent', {
    template: `
        <div>
            <p>我是父组件</p>
            <Child :childDataA="msg" :childDataB="msgB"/>
          
        </div>
    `,
    data() {
        return {
            msg: '传递给子组件的数据',
            msgB: {
                name: '我是name属性'
            }
        }
    },
    methods: {
       
    },
    watch: {
        msg(val) {
            console.log(val)
        },
        msgB: {
            deep: true,
            handler: function(newVal, oldVal) {
                console.log(newVal, oldVal)
            }
        }
    }
})

Vue.component('Child', {
    template: `
        <div>
            <p>我是子组件</p>
            {{ childDataA }}
            <input type="text" v-model="msgA">
            <input type="text" v-model="msgB.name">
        </div>
    `,
    // 指定props属性的类型时,会对传入的参数进行类型检查,如果不符合就会报错
    props: {
        childDataA: {
            type: String,
            default: ''
        },
        childDataB: {
            type: Object,
            default: null
        }
    },
    data() {
        return {
            msgA: this.childDataA,
            msgB: this.childDataB
        }
    },
    methods: {
        
    },
    mounted() {
        console.log(`msgA数据类型是${typeof this.msgA}`)
        console.log(this.childDataA === this.msgA)

        console.log(`msgB数据类型是${typeof this.msgB}`)
        console.log(this.childDataB === this.msgB)

    }
})

// 声明局部组件App
const App = {
    template: `
        <div>
            <Parent />
        </div>
    `
}
new Vue({
    el: '#app',
    data() {
        return {

        }
    },
    // 挂在子组件
    components: {
        App
    },
    //使用子组件
    template: '<App/>'
})
</script>
</html>

页面
在这里插入图片描述

可以看到无论原始类型msgA和引用类型值msgB都和接受的props值时严格相等的。

分别改变两个文本框的值

在这里插入图片描述

只有45行打印出改变后的name值,也就是说data选项的msgA引用props的childDataA,childDataA是一个原始类型,msgA改变并不会导致childDataA发生变化。也就是父组件的msg不会发生改变。而msgB引用props的childDataB,childDataA是一个引用类型,msgB改变导致childDataB发生变化。也就是父组件的data选型中的msgB发生变化。

不用深究Vue源码是如何具体实现的,在子组件的mounted阶段可以看到两个值childDataA=== msgA,childDataB=== msgB。从这里我们可以得值,父组件的msgB和子组件的props中的childDataB以及data中的msgB都是的引用都是相同的,也就是引用同一个对象,其中一个属性值发生变化时,都会发生变化。而原始类型不会。
在这里插入图片描述

所以这里其实延伸到JS中的原始类型和引用类型相等的比较。
原始类型只要值相等即可严格相等(字符串编制值也要相等)
引用类型的比较是引用的比较,必须要求内存地址相同。如果两个对象属性即属性值完全相同,但引用不同(地址不同),那这两个对象是不严格相等的。

var a = 1
b = a
b // 1
b = 2
b // 2
a // 1

var objA = {name: 'A'}
var objB = objA
objB //{name: 'A'}
objB.name = 'B
objA.name // 'B'

上面说了这么多,有什么用呢。其实我们可以得到以下几点启发

在实际业务开发中,如果子组件接受的props属性值改变后,父组件data选项中的值也需要知道值发生变化,当存在多个这样的props属性时,可以定义我一个对象,这样可以避免多次在组件定义并在父组件接受自定义事件并作逻辑处理,手动将父组件data中的多个属性的值改成自定义事件接受的值。

子组件的props建议使用对象来定义,而不是数组,通过对象定义可以对接受的类型进行校验。

无论是Jq,还是Vue都是建立在原生JS的基础上,所以理解熟悉原生JS特别重要。

Logo

前往低代码交流专区

更多推荐