1. 程式人生 > >關於Vue子元件data選項某個屬性引用子元件props定義的屬性的幾點思考

關於Vue子元件data選項某個屬性引用子元件props定義的屬性的幾點思考

學過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特別重要。