iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

0
自我挑戰組

跟 VueJS 認識的30天系列 第 12

[DAY12]跟 Vue.js 認識的30天 - Vue 模組資料傳遞(`props`)

props 的命名及使用

HTML attribute 是大小寫不敏感的,所以必須要注意 prop 的命名跟使用。

命名

可以使用 PascalCase (首字母大寫) 或是 camelCase (駝峰命名法)的命名方法。

  • PascalCase : PostTitleCartItemTodoItem ,每個單字的開頭都是大寫。

  • camelCase : postTitlecartItemtodoItem ,除了第一個單字以外,其餘單字的開頭都是大小。

使用

雖然 prop 的命名是使用 PascalCase (首字母大寫) 或是 camelCase (駝峰命名法),但是在 HTML 中使用時必須使用 kebab-case (短橫線分隔)且應該為小寫。

像是 PostTitleCartItemTodoItem 等,在 HTML 中使用時就會變成 post-titlecart-itemtodo-item

範例

<div id="vm">
<!--post-title 跟 post-content 都是props -->
  <blog-post post-title="Blog1" post-content="I\'m content1"></blog-post>
</div>

<script>
Vue.component("blog-post", {
  props: ["PostTitle", "postContent"],
  template: `<div>
    <h3>{{ PostTitle }}</h3>
    <div>{{ postContent }}</div>
  </div>`
});
</script>

傳遞 props 值的方法

傳遞字串

<blog-post post-title="Blog1" post-content="I\'m content1" post-complete="true" post-total-num="500" post="{title:'Blog1'}"></blog-post>

只要是直接傳遞(靜態傳遞)的都是字串,所以 prop 接收的值 Blog1I\'m content1true500{...}等等都是字串,而非是數字、布林值、陣列、物件等型別。

傳遞數字、布林值、陣列、物件

要如何傳遞數字、布林值、陣列、物件等值給 prop 接收,這時候就要借助 Vue 的指令 v-bind

<blog-post post-title="動態傳遞" post-content="I\'m content1" v-bind:post-complete="true" v-bind:post-total-num="500" v-bind:post="{title:'動態傳遞'}"></blog-post>

透過 v-bind (可用縮寫 : )來讓 Vue 知道後面的值的型別不是字串,而是數字、布林值、陣列或物件等。

也可以通過給予變數來獲得數字、布林值、陣列或物件等型別。

<blog-post :post-title="postTitle" :post-content="postContent" :post-complete="postComplete" :post-total-num="postTotalNum" :post="post"></blog-post>

<script>
const vm = new Vue({
  el: "#vm",
  data: {
    postTitle: "動態傳遞",
    postContent: "I'm content",
    postComplete: true,
    postTotalNum: 500,
    post: { title: "動態傳遞" }
  }
});
</script>

單向數據流

props 是為了接收從父模組傳遞進來的資料,而這些資料是單向綁定的,也就是說父模組資料的更新會影響子模組裡的 prop ,但子模組裡的 prop 值的改變並不能影響父模組。

測試:

<prop-change :counter=counter></prop-change>
<br/>
<span>外 {{counter}}</span>
<button type="button" @click="changeOuterCounter">改變外面數字</button>

<script>
Vue.component("prop-change", {
  props: ["counter"],
  template: `<div>
    <span>component內的  {{counter}}</span>
    <button type="button" @click="changeInnerCounter">改變component數字</button>
  </div>`,
  methods: {
    changeInnerCounter() {
      this.counter += 2;
    }
  }
});

const vm = new Vue({
  el: "#vm",
  data: {
    counter: 1
  },
  methods: {
    changeOuterCounter() {
      this.counter += 1;
    }
  }
});
</script>

從上面的測試可以得知:

  • 外面(父模組)的資料 counter 改變會影響子模組 propcounter 值的改變。

  • 子模組 propcounter 值的改變僅影響內部 counter 值。

  • 不論子模組 propcounter 值是否有變動,只要父模組資料 counter 改變時,子模組 propcounter 值一定會連動。

改變子模組內 props 的值,使用以下幾種方法:

  • data 內創建一個值

    賦予 dataprop 初始值相同的值,且之後也是針對該 data 內的值做操作,並且不會再受到該 prop 的影響了。

    Vue.component("one-way-data", {
      props: ["counter"],
      template: `<div>
        <span>component內的  {{newCounter}}</span>
        <button type="button" @click="changeNewCounter">改變component數字</button>
      </div>`,
      data () {
        return {
          newCounter: this.counter
        }
      },
      methods:{
        changeNewCounter(){
          this.newCounter+=10
        }
      }
    });
    
  • 使用 computed 來做 prop 值的轉換

    透過 computed 取得 prop 值做複雜運算後的結果。

    Vue.component("one-way-computed", {
      props: ["counter"],
      template: `<div>
        <span>component內的  {{newCounter}}</span>
        <button type="button">改變component數字</button>
      </div>`,
      computed: {
        newCounter(){
          return this.counter+100
        }
      }
    });
    

物件型別的 prop 的傳遞

有關於物件或陣列的傳遞,在子模組中更改值是會影響父模組的喔!因為物件或陣列的傳遞是透過傳址(by reference)的方式,所以資料不管是在父模組還是子模組修改都會互相影響。

可以透過在 data 中創建 JSON.parse(JSON.stringify(物件)) 的值,來使得內層跟外層的物件位址不同,而不會相互影響,但必須要注意到,使用 JSON.parse(JSON.stringify(物件)) 所取得的 prop 值(物件),僅會取得初始的prop 值,之後不管 prop 值怎麼改變都不會再影響在 data 中新創建的值。

{
  props:['post'],
  data(){
    return {
      samePost:this.post,//跟props的post會相互影響
      differentPost: JSON.parse(JSON.stringify(this.post))//取得props的post的初始值後,就跟props的post的位址不同而不會相互影響
    }
  }
}

props 的進階使用

陣列型別

之前看到的 props 都是陣列型別,陣列型態傳遞進來的 props 值不會有任何限制,傳甚麼進來就接收甚麼。

props: ['PostTitle', 'postContent', 'postAuthor']

物件型別及驗證

物件型別的 props 可以幫助我們指定每個 prop 的型別(type)、默認值(default)、是否必填(required)或驗證是否成功(validator)。

範例

props: {
  validationCounter: {
    type: Number,
    default: 2,
    required: false,
    validator(value) {
      return value >= 0;
    }
  },
  post: {
    type: [Object,Array],//多種型別的可能
    required: true
  }
}

prop 的物件屬性提醒:

  • type : 可以是 StringNumberBooleanArrayObjectDateFunctionSymbol、自定義建構函式或上述類型組成的陣列(該 prop 值可以擁有多種可能性)。

    props:{
      validationCounter:{
        type: Number
      }
    }
    

    validationCounter 傳進來的值一定要是數字。

  • default : 如果該 prop 沒有任何值被傳遞進來,就會使用 default 的值作為預設值。

    props:{
      validationCounter:{
        default: 2
      }
    }
    

    validationCounter 如果沒有任何值被傳進來,那麼 validationCounter 的值就會是預設值數字 2

  • required : 是否為必填項,如果是 true 則該 prop 沒有任何值被傳遞進來的話,就會出現錯誤提示。

    props:{
      validationCounter:{
        required: true
      }
    }
    

    沒有傳值進去的話,會出現如下方的錯誤提示。

    https://ithelp.ithome.com.tw/upload/images/20201031/20127553R9ZM80soOc.png

  • validator : 自定義的驗證函式,會將該 prop 的值當成唯一的引數帶入驗證該函式進行驗證。

    props:{
      validationCounter:{
        validator(value) {
          console.log(this)//window
          return value >= 2;
        }
      }
    }
    

    會將 validationCounter 的值作為引數帶入 validator 參數進行驗證。

    驗證結果失敗的話,會出現如下方的提示。

    https://ithelp.ithome.com.tw/upload/images/20201031/20127553Xbqx995dNI.png

特別注意

以上所使用的驗證方法,會是在該模組被創建以前就先進行驗證,所以不能再 defaultvalidator 中使用模組的 datacomputed 等屬性(因為模組還沒被創建,所以會找不到 datacomputed 等屬性)。

有關 Vue 實例的創建可以參考DAY02 | 跟 Vue.js 認識的30天 - Vue 實體的生命週期(Lifecycle Hooks)及模板語法(Template Syntax)

更多 props 的驗證可以參考Vue.js - props

沒被模組定義的 Attribute

沒被模組定義的 Attribute,指的是在模組標籤上出現的 HTML Attribute ,卻沒在模組內的 props 註冊,出現這樣的 HTML Attribute 的話,會出現以下幾種可能性:

  • 被添加到模板的根元素

    <non-prop-attribute data-ride="carousel"></non-prop-attribute>
    <script>
    Vue.component("non-prop-attribute", {
      template: `<div id="carouselExampleSlidesOnly" class="carousel">
      <div class="carousel-inner">
        <div class="carousel-item active">
          <img src="https://images.unsplash.com/photo-1593642531955-b62e17bdaa9c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=600&q=60" class="d-block w-25">
        </div>
      </div>
    </div>` 
    });
    </script>
    

    模組標籤的 data-ride="carousel" 會被加入到根元素 <div id="carouselExampleSlidesOnly" class="carousel">裡,在開發者工具可發現 DOM 會變成 <div id="carouselExampleSlidesOnly" class="carousel" data-ride="carousel">

  • classstyle 與根元素的 classstyle相結合

    <non-prop-attribute data-ride="carousel" class="slide"></non-prop-attribute>
    <script>
    Vue.component("non-prop-attribute", {
      template: `<div id="carouselExampleSlidesOnly" class="carousel">
        <div class="carousel-inner">
          <div class="carousel-item active">
            <img src="https://images.unsplash.com/photo-1593642531955-b62e17bdaa9c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=600&q=60" class="d-block w-25">
          </div>
        </div>
      </div>` 
    });
    </script>
    

    模組標籤的 class="slide" 會與 <div id="carouselExampleSlidesOnly" class="carousel"> 中的 class="carousel" 結合,變成 <div id="carouselExampleSlidesOnly" class="carousel slide">

  • 取代根元素原有的 Attribute

    <non-prop-attribute-input type="date"></non-prop-attribute-input>
    <script>
    Vue.component("non-prop-attribute-input", {
      template: `<input type="text" class="form-control w-25" placeholder="我是input">`
    });
    </script>
    

    模組標籤的 type="date" 會取代掉根元素中相同的屬性,所以 <input type="text" class="form-control w-25" placeholder="我是input"> 中的 type="text" 會被 type="date" 取代,變成 <input type="date" class="form-control w-25" placeholder="我是input">

https://ithelp.ithome.com.tw/upload/images/20201031/20127553cKf5nCcP0z.png

https://ithelp.ithome.com.tw/upload/images/20201031/20127553ABl7ByRq7R.png

https://ithelp.ithome.com.tw/upload/images/20201031/201275536myi4TaF5S.png

透過 inheritAttrs: false 取消根元素繼承未被定義的 prop

<prop-inheritattrs label="我是label" placeholder="我是inheritAttrs後的input" required></prop-inheritattrs>
<script>
Vue.component("prop-inheritattrs", {
  inheritAttrs: false,
  props: ["label"],
  template: `<label>
      {{ label }}
      <input type="text" class="form-control">
    </label>`
});
</script>

被定義過的 prop label 不會受到任何影響,但是會發現未被定義的 HTML Attribute placeholderrequired 已經不會添加到根元素了。

加入 inheritAttrs: false 後:

  • 未被定義的 HTML Attribute 除了不會被添加到根元素之外,也不會擁有取代的功能了,如上方(取代根元素原有的 Attribute)的例子, type="text" 不會被模組標籤上的 type="date" 所取代。

  • classstyle 不會受到影響,仍會被合併到根元素的 classstyle 上。

使用 inheritAttrs: false 跟 Vue 屬性 $attrs ,改變未被定義的 HTML Attribute的添加位置

<prop-inheritattrs label="我是label" placeholder="我是inheritAttrs後的input" required class="custom-control-label"></prop-inheritattrs>
<script>
Vue.component("prop-inheritattrs", {
  inheritAttrs: false,
  props: ["label"],
  template: `<label>
      {{ label }}
      <input type="text" v-bind="$attrs" class="form-control">
    </label>`
});
</script>

加入 inheritAttrs: false 後, placeholder="我是inheritAttrs後的input" required 的確不會被添加到根元素了,但透過 Vue 指令 v-bind 跟屬性 $attrs 就可以將這些 Attribute 加入到 template 中的其他位置,如上方的 <input> 。但模組標籤中的 classstyle 還是會合併或添加到根元素的 classstyle 中。

https://ithelp.ithome.com.tw/upload/images/20201031/20127553E7efMSf2KJ.png

Demo:[DAY12]跟 Vue.js 認識的30天 - Vue 模組資料傳遞(props)

參考資料:

Vue.js - props

Vue.js - props


上一篇
[DAY11]跟 Vue.js 認識的30天 - Vue 的模組註冊(`component`)
下一篇
[DAY13] 跟 Vue.js 認識的30天 - Vue 模組自定義事件(Custom Events)
系列文
跟 VueJS 認識的30天21

尚未有邦友留言

立即登入留言