# Vue - 「 Props in , emit out 」 - 父與子 Component 之間如何傳遞資料 元件內的 data 使用 function 的寫法,是為了獨立每個元件的資料,使元件們的資料不會互相影響 ~~,所以使用了**閉包**的特性~~,我們必須使用「 Props in , emit out 」的方式達到 Component 之間的資料傳遞 ![](https://i.imgur.com/BfAk9N8.png) <h4 style="text-align: center;">簡單來說</h4> <h4 style="text-align: center;">Props in - 父層將資料傳遞給子層</h4> <h4 style="text-align: center;">Emit out - 子層將資料傳遞給父層</h4> <br> ## Props in - 父層將資料傳遞給子層 ### Props ![](https://i.imgur.com/NDKRIbh.png) - 用來儲存父層傳遞 value 的 key - `props`的值可以是**陣列**或是**物件** - 陣列 - 底下的資料用**字串**存入 - `props: ['props1', 'props2', 'props3', ...]` - 物件 - 底下的資料用 **key-value** 存入,值為 Object 且有以下幾種屬性可以設定: - 屬性 `type` : 具**資料型態的驗證**功能,若 props in 的資料不符則會跳出警告 ```jsx= props: { // 型態類別無需用引號包成字串,首字要大寫 'prpos-string':{type:String}, 'props-number':{type:Number}, 'props-boolean':{type:Boolean}, 'props-array':{type:Array}, 'props-object':{type:Object}, 'props-date':{type:Date}, 'props-function':{type:Function}, 'props-symbol':{type:Symbol}, // 型態的驗證可以不只一種 'props-string-or-number':[String, Number], } ``` - 屬性 `default` : 當父層沒有 props in 給子層該筆資料時,可以預先**設定預設值** ```jsx= props: { // 就算父層沒有 props in,還是有預設值可以顯示 'prpos-has-default':{default:'defaultValue'}, } ``` - 屬性 `required` 設定為**必要 props in 的資料**,其值為 **Boolean** 若父層沒有 props in 該筆資料,則會跳出警告 ```jsx= props: { 'prpos-required':{required:true}, } ``` - 屬性 `validator` **自訂驗證規則**,若不符合驗證規則,則跳出警告 ```jsx= props: { 'prpos-validator':{validator: value => value > 10}, } ``` :::warning 元件初始化時,**`props` 的初始順序會優先於** `data` 、 `computed` ...屬性,所以 `default` 或 `validator` 無法取得實體內的資料 ( this. ) ::: ![](https://i.imgur.com/dIilBg6.png) ==parentComponent.vue== ```jsx= <template> // 綁定父層要 props in 的資料 <ChildComponent :childProp='parentData'></ChildComponent> </template> <script> import ChildComponent from '相對路徑/ChildComponent'; export default { data(){ return { parentData:'父層要傳過去的資料' } }, components:{ ChildComponent } } </script> ``` ==ChildComponent.vue== ```jsx= <template> <div>{{childProp}}</div> </template> <script> export default { // 設定子層接收父層 props in 的承載 props: ['childProp'] } </script> ``` :::warning :warning: 注意: **HTML 不分大小寫** 以 HTML 作為模板的時,**駝峰式(Camel Case)** 寫法可以換成 **連字號 (kebab-case)** ex. childProp ---> child-prop ::: [CodeSandBox - Props in 範例](https://codesandbox.io/s/props-in-o9eqb?file=/src/components/SubComponent.vue) ### 若父層在 props in 時沒有使用 v-bind:,則會傳入**純文字字串** ![](https://i.imgur.com/7K4ZONT.png) ==parentComponent.vue== ```jsx= <div>{{parentData}}</div> <ChildComponent childProp='parentData'></ChildComponent> ``` ```jsx= data(){ return { parentData:"Parent's message!" } } ``` ==ChildComponent.vue== ```jsx= <template> // 得到的 childProp 會是字串 'parentData' ,而不是 "Parent's message!" <div>{{childProp}}</div> </template> <script> export default { // 設定子層接收父層 props in 的承載 props: ['childProp'] } </script> ``` ### 用 Props in 的方式,動態設定子層的 Class 可以透過 props in 的方式,視父層的使用情況,給予複用的子層們不同的 Class ![Props in Class](https://i.imgur.com/bEcG4E8.png) ==parentComponent.vue== ```jsx= <BackgroundColor :parentClass="propsInClass.pink" /> <BackgroundColor :parentClass="propsInClass.blue" /> ``` ```jsx= data(){ return{ propsInClass:{ pink:'pink', blue:'blue' } } } ``` ```css= .pink { background-color: DeepPink; } .blue { background-color: DeepSkyBlue; } ``` ==ChildComponent.vue== ```jsx= <div :class="parentClass" class="other-class"></div> ``` ```jsx= export default { props: ["parentClass"], }; ``` [CodeSandBox - Props in Class 範例](https://codesandbox.io/s/props-in-class-3s0ts?file=/src/components/BackgroundColor.vue:93-138) ### 用 Object 來 props in - 直接傳入物件 ==parentComponent.vue== ```jsx= <ChildComponent :childJimmy="jimmy"></ChildComponent> ``` ```jsx= data() { return { jimmy: { name: "Jimmy", id: 55688, gender: "male", age: 26, like: ["surfing", "diving", "climbing"], }, }; }, ``` ==ChildComponent.vue== ```jsx= <p>{{childJimmy.name}}</p> <p>{{childJimmy.id}}</p> <p>{{childJimmy.gender}}</p> <p>{{childJimmy.age}}</p> <p>{{childJimmy.like}}</p> ``` ```jsx= export default { props: ["childJimmy"], }; ``` - 也可以**解構**,傳入各自所需的資料 ==parentComponent.vue== ```jsx= <ChildComponent :name="jimmy.name" :id="jimmy.id" :gender="jimmy.gender" :age="bindOther" :like="bindOther" ></ChildComponent> ``` ```jsx= data() { return { jimmy: { name: "Jimmy", id: 55688, gender: "male", age: 26, like: ["surfing", "diving", "climbing"], }, bindOther: "bindOther" }; }, ``` ==ChildComponent.vue== ```jsx= <p>{{name}}</p> <p>{{id}}</p> <p>{{gender}}</p> <p>{{age}}</p> <p>{{like}}</p> ``` ```jsx= export default { props: ["name", "id", "gender", "age", "like"] }; ``` [CodeSandBox - Props in Object 解構範例](https://codesandbox.io/s/props-in-object-kfyb6?file=/src/App.vue:1154-1185) ### ~~用 Object 來 props in - with v-for --待續.........~~ <!-- - 當 **Props in 的資料為 Object** 時,需要特別注意 JavaScript 的 Object 是 pass by reference,若直接 props in 的話,會造成子層可以直接修改父層的資料,這違反了 vue 的"單向資料流",並且可能會造成資料的污染,應該先將物件屬性**解構成原始型別 (Primitive)** 後再將資料傳遞出去 父層 ```jsx= <template> <ChildComponent :name="jimmy.name" :id="jimmy.id" :gender="jimmy.gender" :age="jimmy.age" :like="jimmy.like" ></ChildComponent> </template> <script> import ChildComponent from '相對路徑/ChildComponent'; export default { data(){ return { jimmy: { name: "Jimmy", id: 55688, gender: "male", age: 26, like: ["surfing", "diving", "climbing"] }, } }, components:{ ChildComponent } } </script> ``` 子層 ( ChildComponent.vue ) ```jsx= <template> <p>name:{{ name }}</p> <p>id:{{ id }}</p> <p>gender:{{ gender }}</p> <p>age:{{ age }}</p> <p>like:<span v-for="item in like" :key="item">{{ item }}</span> </p> </template> <script> export default { props: ["name", "id", "gender", "age", "like"], } </script> ``` [CodeSandBox - Props in Object 範例](https://codesandbox.io/s/props-in-object-tzl55) - 使用 `v-for` Props in Object 時,也可以使用指令 `v-bind` 自動解構 :::warning ~~若在子層需要對 props in 的資料做 v-model 雙向綁定的話,需要再 data 複製一份 props in 的資料,或是使用 computed(這樣父層更新資料子層才會跟這更新)~~ ::: --> ### 在 props in 的資料使用 v-model - **子層最好不要直接在 props in 的資料使用 v-model** ,這樣有可能會 改變/汙染 到父層的資料 - 若真的**需要在子層 props in 的資料使用 v-model**,則可以在子層**用 data 做 props in 的副本**,副本將不會隨著父層改變而更新,可以讓子層獨立 管理/處理 props in 的副本 ==ChildComponent.vue== ```jsx= export default { props: ["parentPropsIn"], data(){ return{ // 資料只有在初始化時更新 childData:this.parentPropsIn } } }; ``` - :no_entry: 上述的情況**千萬不要用 computed 做資料的副本**,雖然執行結果的"畫面"跟原本使用 `v-bind:` 一樣,資料會隨著父層改變而更新,且子層資料改變時,不會 改變/汙染 到父層的資料,但當你使用 `v-model` 的特性,在子層修改資料並做雙向綁定的同時,你可能會遇到下列兩種情況: - 當 computed 沒有 setter 並且 getter 依賴 props in 的資料時,你會收警告 [Vue warn]: Write operation failed: computed property "....." is readonly. ![](https://i.imgur.com/PTwTjwM.png) ![](https://i.imgur.com/QX4gh8w.png) - 在 getter 依賴 props in 的資料,並且有設定 setter 並且依賴 data 的資料時,當修改 v-model 的 computed,由於 data 的改變觸發生命週期的updated,getter 又從新 return props in 的值 ![computer setter getter and updated](https://2.bp.blogspot.com/-dewaJmzwLYg/WRHB89i_URI/AAAAAAAAyxY/L0clpu50eYY3aHiklwT4r1PD_OTQfh3UQCLcB/s1600/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7%2B2017-05-09%2B%25E4%25B8%258B%25E5%258D%25889.19.23.png) > [那些關於 Vue 的小細節 - Computed 中 getter 和 setter 觸發的時間點](https://pjchender.blogspot.com/2017/05/vue-computed-getter-setter.html) ==ChildComponent.vue== ```jsx= export default { props: ["parentPropsIn"], computed:{ childData(){ // 資料跟著父層資料更新而更新 return this.parentPropsIn; } } }; ``` - getter 與 setter 都依賴 data 時,效果與在 data 做 props in 副本一樣,但是根本多此一舉 :::warning 若子層需要在改變資料時,將資料傳回父層,可以使用 Emit out / Event out ,也就是 `v-on:` 搭配 `$emit` 來觸發事件並將資料傳給父層 ::: --------------------------------------- ## Emit out / Event out - 子層將資料傳遞給父層 ![](https://i.imgur.com/8BCahFu.png) ### `$emit(自定義事件,[參數1, 參數2, ...])` - 用來**觸發當前實例上的事件**,附加參數都會傳給監聽器 callback - 參數 - 自定義事件:欲觸發的事件名稱,用**連字號 (kebab-case)** 較不易出錯 - [參數1, 參數2, ...]:觸發事件時帶入 event handler 的參數 ### `$event` 是 vue 用來監聽事件的特殊變數 - 使用 `$event.target.value` 便可以取得事件當下 DOM 節點的 value ### `v-on:監聽 DOM 事件 = "$emit( 自定義事件, $event.target.value )"` - 當子層的 `v-on:監聽 DOM 事件` 觸發時,會在父層觸發 `自定義事件` 並且帶入子層觸發事件當下 DOM 節點的 value - `$event.target.value` 作為 父層 event handler 的參數 ![](https://i.imgur.com/ZgBRCN8.png) ==ChildComponent.vue== ```jsx= <input type="text" v-on:input="$emit('emit-trigger', $event.target.value)" /> ``` ==parentComponent.vue== ```jsx= <ChildComponent v-on:emit-trigger="parentGet"></ChildComponent> <p>Data from ChildComponent : {{parentData}}</p> ``` ```jsx= data() { return { parentData: "", }; }, methods: { parentGet(eventTargetValue) { this.parentData = eventTargetValue; }, }, ``` [CodeSandBox - Emit out 範例](https://codesandbox.io/s/emit-out-event-out-rcp8l) :::warning **父層** `ChildComponent` 標籤中,自定義事件 (`emit-trigger`) 的 **Event Handler** (`parentGet`) **不需要**像其他 Event Handler 一樣加**小括號 ( )** ::: ### 子層也可以用 event handler 的方式處理資料 ==ChildComponent.vue== ```jsx= <input type="text" v-on:input="sandToParent($event)" /> ``` ```jsx= methods: { sandToParent(event) { this.$emit("emit-trigger", event.target.value); }, }, ``` :::warning 子層 `v-on:` 的 **Event Handler** `sandToParent($event)`甚至**可以不用小刮號 ($event)** 傳入 $event,因為 Event Listener 在註冊 Event Handler 的時候,會把**事件物件(Event Object)** 預設為 **Event Handler 的第一個參數** ```jsx= <input type="text" v-on:input="sandToParent" /> ``` ```jsx= methods: { sandToParent(event) { this.$emit("emit-trigger", event.target.value); }, }, ``` ::: ==parentComponent.vue== ```jsx= <ChildComponent v-on:emit-trigger="parentGet"></ChildComponent> <p>Data from ChildComponent : {{parentData}}</p> ``` ```jsx= data() { return { parentData: "", }; }, methods: { parentGet(eventTargetValue) { this.parentData = eventTargetValue; }, }, ``` [CodeSandBox - Emit out - Event Handler 範例](https://codesandbox.io/s/emit-out-event-out-event-handler-12w0x?file=/src/components/ChildComponent.vue) <br><br><br><br><br> --------------------------------- ### **`v-model`** 其實是結合 `v-bind:` 與 `v-on:` 語法糖 以 input 為例: ```jsx= <input v-model='something' /> // 等價於 <input v-bind:value='something' v-on:input='value=$event.target.value' /> ``` <br><br><br><br><br> 參考如下: > https://ithelp.ithome.com.tw/articles/10199044 > https://ithelp.ithome.com.tw/articles/10223518 > https://book.vue.tw/CH2/2-2-communications.html > [隱藏在 Handler 中的event](https://ithelp.ithome.com.tw/articles/10192015#:~:text=%E9%9F%B3%E6%A8%82%E8%AB%8B%E4%B8%8B-,%E9%9A%B1%E8%97%8F%E5%9C%A8%20Handler%20%E4%B8%AD%E7%9A%84%20%22event%22,-%E7%95%B6%E7%9B%A3%E8%81%BD%E7%9A%84) ###### tags: `Vue` <style> h2{ border:none !important; } </style>