# 祖孫之間傳遞(props & $attrs) ###### tags: `vue` ## 使用 props 傳遞資料 假設今天有三個組件,依序為: - 最外層(祖):component-a - 中間層(父):component-b - 最內層(子):component-c 然後阿祖有一包資料要傳送給孫子 這時會想到用 props 的方式如下: 1. a 組件的資料(msga) 要能夠供 b 組件(為方便簡稱 a, b, c 組件)使用,則需要在 b 組件 配置 props 的功能,因此先在 b 組件配置一個 props 的 options^[組件內的 data, components, methods... 稱作 options] 2. 因為配置了 props,我們在 a 組件內使用 b 組件時可以加上 msgb 屬性並且動態綁定(加上`:`),綁定的資料是 msga,如此一來,b 組件便可以使用 a 組件的資料。 何以證明? 看下方代碼,我們在 b 組件的模板內使用 `{{}}` 包著的 msgb 可以正常顯示出來 <p class="notice">注意:a 組件只能放 `{{msga}}`,不能放 `{{msgb}}`,因為 msgb 只能在 b 組件內使用,這是因為作用域的關係,解決此方法便要透過 v-slot 的作用域插槽來解決</p> 3. 還沒完,剛剛只傳遞資料到 b 組件而已,現在同樣的方式在 c 組件配置一個 props 為 msgc,回到 b 組件模板的地方,動態綁定 msgc 到 msgb,使得 c 組件得以使用 b 組件的數據(實際上 b 組件的數據就是 props 的內容) ```htmlmixed <div id="app"> A{{msga}} <component-b :msgb="msga"> </component-b> </div> ``` ```javascript let vm = new Vue({ el: '#app', data: { msga: '100' }, components: { 'ComponentB': { props: ['msgb'], template: `<div>B{{msgb}}<component-c :msgc="msgb"></component-c></div>`, components: { 'ComponentC': { props: ['msgc'], template: '<div>C{{msgc}}</div>' } } }, } }) ``` ## 使用 $attrs 傳遞資料 上述透過 props 的傳遞方式通常是單純地父傳子組件的時候適合使用,但當組件的層級一多,就開始有些礙手礙腳的。像上述例子中,b 組件的作用僅僅是個中間橋樑,卻要配置 props,那有沒有更好的方式?有的,$attrs 就解決需要配置一堆 props 的困擾。 先簡單說一下 $attrs 是什麼?在多層組件資料傳遞的情況下,為什麼比 props 好用。 先假設最底層的 e 組件想要接收到最外層 a 組件的資料(這邊的 a, e 僅僅舉例,目前並沒有這樣的組件),無論中間隔了幾層,要五層也可以。今天在別的組件上使用 e 組件時,只要在 e 組件標籤上加上 `v-bind="$attrs"`,便可以接收到 e 組件以上的其他組件的資料^[注意這個資料必須要是綁定在標籤上的,後面例子會慢慢解釋],但須注意接收到的資料會是 props 以外的屬性,直接透過畫面上顯示的東西來解釋 ```htmlmixed <div id="app"> <div style="padding:50px;"> <childcom :name="name" :age="age" :sex="sex"> </childcom> </div> </div> ``` ```javascript let app = new Vue({ el: "#app", props: [], data() { return { name: "张三", age: "30", sex: "男" }; }, components: { childcom: { template: `<div> <div>{{name}}</div> <grandcom v-bind="$attrs"> </grandcom> </div>`, props: ["name"], components: { grandcom: { template: `<div>{{$attrs}}</div>` } } } } }); ``` ``` 張三 {"age":"30","sex"="男"} ``` 在 grandcom 這個組件裡可以直接使用 $attrs,並且顯示在畫面上的,我們先看到 `張三` 這是從哪來的? 剛好順便複習一下 props,為什麼明明是最外層組件的資料,childcom 組件卻可以使用 name 這個資料?這裡先打住,澄清一下什麼叫做「使用」。使用的意思是在該組件的模板template 內使用,以 name 這個資料為例,name不管從何而來,也不需要去管以下兩個組件的層級關係,使用的意思為: ```htmlmixed // component X <template> <div> // 在這邊引入變數 {{name}} </div> </template> ``` 而非: ```htmlmixed // component Y <template> <div> <component-x :name="name"></component-x> </div> </template> ``` 回到先前例子,為何 childcom 可以使用 name?答案正是因為在 childcom 組件有配置 props 去接收外層組件的 name 資料。 當然這邊的重點是 $attrs,也因此我們接著看到下面顯示的 `{ "age": "30", "sex": "男" }` 奇怪?怎麼少了 name,很簡單,因為先前說過了,$attrs 可以訪問父組件的資料除了有被 props 配置過的之外。正因為在 childcom 組件這邊已經先配置過 name,也因此 name 不會被顯示出來。講到這邊,先記得以下的條件,此兩個條件會在下面更進階的例子中解釋清楚: 1. 父組件(不管哪層)要有 data 數據 2. 此 data 數據要被動態綁定到組件標籤上,例如說:`<component-a :sex="sex" :name="name"></component-a>` 3. 不被 props 配置過 達到以上條件的資料是可以被子組件訪問的,透過 `$attrs` 這個物件 直接將上面的例子改造一下,變成總共有四層的組件,透過這四層來解釋一下。詳細例子在 [這裏](https://codesandbox.io/embed/modest-bose-yr1re?fontsize=14&hidenavigation=1&theme=dark) 我們直接看顯示的結果,一行一行說明: ``` 組件A: 組件B:{ "bName": "組件A", "bAge": "90", "bCalled": "阿祖" } 組件C:{ "cName": "組件B", "cAge": "70", "cCalled": "阿公", "bName": "組件A", "bAge": "90", "bCalled": "阿祖" } 組件D:{ "dName": "組件C", "dAge": "50", "dCalled": "爸爸", "cName": "組件B", "cAge": "70", "cCalled": "阿公", "bName": "組件A", "bAge": "90", "bCalled": "阿祖" } 測次 props: 12 ``` 第一行的組件A就是在組件A裡面的資料 aName,這沒啥好說,如下: ```javascript // 組件A data(){ return { aName:'組件A' } } ``` 接下來每行的組件開頭不需要特別解釋,ㄧ樣都是各自組件內的資料直接透過 `{{}}` 顯示在視圖,這是為了方便解釋每一行是哪個組件而已 <p class="notice">需注意的是第二行組件B後面那串東西是透過使用 `{{$attrs}}` 顯示出來的(不要去管屬性與名字有沒有對上!只需注意為什麼這行,也就是此組件可以使用這些資料)</p> 我們從內層的組件D開始解釋,組件D本身擁有以下三筆資料,但它們都不是重點,重點是要怎麼拿到 A, B, C 組件的資料?以及該怎麼樣在 A, B, C 組件去做配置才能供 D 使用 ```javascript data() { return { dName: "組件D", dAge: "30", dCalled: "兒子" }; } ``` 你會發現很神奇的事情發生,當我們在組件D的模板直接使用 `{{$attrs}}`,便會看到以下的東西產生 ``` 組件D:{ "dName": "組件C", "dAge": "50", "dCalled": "爸爸" } ``` 在子組件可以透過 $attrs 直接去使用父組件的資料,不過前提是,在父組件使用子組件模板時,子組件標籤需要加上屬性。 所以說正因為在組件D內綁定上組件C的資料,我們才可以在組件D直接去透過 $attrs 訪問組件C的資料 ```htmlmixed <component-d :dName="cName" :dAge="cAge" :dCalled="cCalled" /> ``` 那細心的你可能會發現,為什麼這邊組件D只拿到這三筆資料?明明在更上面顯示的是 ``` 組件D:{ "dName": "組件C", "dAge": "50", "dCalled": "爸爸", "cName": "組件B", "cAge": "70", "cCalled": "阿公", "bName": "組件A", "bAge": "90", "bCalled": "阿祖" } ``` 別急我正要講,所以說假如組件D不想只拿到組件C的資料,而是很貪心地A, B, C都想拿到,該怎麼做?首先,我們可以先在組件D模板上加上 `v-bind="$attrs"` ```htmlmixed <component-d :cNameProps="cName" :dAge="cAge" :dCalled="cCalled" v-bind="$attrs" /> ``` 這樣一來,我們連組件C可以使用的 $attrs 都可以拿到了。也因此假如今天有三層組件,中間那層只要設置 v-bind="$attrs" 最裡面的組件就可以拿到最外面的組件!這樣是不是方便很多。 例子出處: 1. vue中$attrs你会用吗?[連結](https://juejin.im/post/5c77c7f4f265da2dc37b428b) 2. vm.$attrs 【Vue 2.4.0新增inheritAttrs,attrs详解】[連結](https://www.jianshu.com/p/ce8ca875c337) <style> .notice { border-left: 5px solid red; padding-left: 5px; } img{ border: 1px solid grey; width:50%; } </style>