[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


<<:  (32)试着学 Hexo-番外篇之更新 Hexo

>>:  如果要架设一个HTTPs File Server, 推荐用什麽软件呢?

Day 2 - API 文件导览、 Postman 测试取得 Nonce

在进行串接前,首先需要有定义串接的规格,例如:串接的协定 (HTTP、或走 FTP 档案交换等等)、...

硬体安全模组 (HSM) 的身份验证最不相关-职责分离(SOD)

如今,“秘密”(secret)是认证的基础。我们通常使用密码(您知道的东西)、令牌中的加密密钥(您拥...

如何简单快捷找到被误删的日历事件

突然发现日历事件被误删?我们曾丢失/误删过 iPhone 的行事历档案。那麽我们如何才能从 iPho...

[Day18] Esp32用STA mode + Relay - (程序码讲解)

1.前言 今天就不废话拉,直接进入主题(大家应该都去报复性出游了吧)。 2.html 其实HTML目...

[day25]Vue实作-历史交易查询画面

在昨天的铁人贴文中制作了交易建立的画面,之前也有提到,透过批次,会於日档批次中,定期抓取历史缴费纪录...