---
# System prepended metadata

title: 祖孫之間傳遞（props & $attrs）
tags: [vue]

---

# 祖孫之間傳遞（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>