Luna Tech

Tutorials For Dummies.

Vue: Component Props

2022-03-24


0. Intro

Props | Vue.js (vuejs.org)


1. Props Declaration

Vue component 需要 explicit props declaration,这样 Vue 才知道传入的参数中哪些应该被当做 fallthrough attributes(这个概念之后会讨论)。

Example - use array syntax

export default {
  props: ['foo'],
  created() {
    // props are exposed on `this`
    console.log(this.foo)
  }
}

Example 2 - use object syntax (推荐)

key: prop name

value: constructor function of the expected type

export default {
  props: {
    title: String,
    likes: Number
  }
}

2. Prop Passing Details

Prop Name Casing

用 camelCase 来命名,这样的命名可以避免我们在使用 property key 的时候加引号,还可以直接在 template expression 里面 reference 它们,因为它们都是 valid JS identifiers。

export default {
  props: {
    greetingMessage: String
  }
}
<span>{{ greetingMessage }}</span>

虽然我们可以在 pass props to a child component 的时候用 camelCase,但惯例是用 kebab-case。在 HTML 里面写 component tag 的时候,一般用 PascalCase,因为这样易读性更强,可以区分 Vue component from native elements。

<MyComponent greeting-message="hello" />

Static vs. Dynamic Props

静态值:

<BlogPost title="My journey with Vue" />

动态值:用 v-bind 或者 :

<!-- Dynamically assign the value of a variable -->
<BlogPost :title="post.title" />

<!-- Dynamically assign the value of a complex expression -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

Passing Different Value Types

Example - Number

<!-- Even though `42` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.       -->
<BlogPost :likes="42" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :likes="post.likes" />

Example - Boolean

<!-- Including the prop with no value will imply `true`. -->
<BlogPost is-published />

<!-- Even though `false` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.          -->
<BlogPost :is-published="false" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :is-published="post.isPublished" />

Example - Array

<!-- Even though the array is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.            -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :comment-ids="post.commentIds" />

Example - Object

<!-- Even though the object is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.             -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :author="post.author" />

3. Binding Multiple Properties Using an Object

假如我们要把一个 object 的所有 properties 都传入作为 props,可以直接用 v-bind without argument 来实现。

If you want to pass all the properties of an object as props, you can use v-bind without an argument (v-bind instead of :prop-name).

Example

Template 里面的两种写法效果是一样的,第一种是传入所有 properties。

<BlogPost v-bind="post" />
<BlogPost :id="post.id" :title="post.title" />
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}

4. One-Way Data Flow

所有 props 都是 child property 和 parent 之间的 one-way-down-binding。

当 parent property 更新时,child 也会更新,但是 child 更新不会让 parent 更新。

parent -> child

为什么?

这可以避免 child 意外地改变 parent state,two-way-binding 会让你的 app data flow 难以理解。

Don’t mutate child component’s prop

每次 parent component update,child component 的所有 props 都会更新到最新的 value,这意味着你不应该尝试改变一个 child component 里面的 prop,假如你这么做的话会看到一个 console warning message - props are readonly!

Example

export default {
  props: ['foo'],
  created() {
    // ❌ warning, props are readonly!
    this.foo = 'bar'
  }
}

如何正确地「改变 prop」value

当你想要改变 prop 的时候通常有两种情况:

  1. The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards.

    • 解决方案:define local data property that uses the prop as its initial value
  2. The prop is passed in as a raw value that needs to be transformed.

    • 解决方案:define a computed property using the prop’s value

Example - local data property

export default {
  props: ['initialCounter'],
  data() {
    return {
      // counter only uses this.initialCounter as the initial value;
      // it is disconnected from future prop updates.
      counter: this.initialCounter
    }
  }
}

Example - computed property

export default {
  props: ['size'],
  computed: {
    // computed property that auto-updates when the prop changes
    normalizedSize() {
      return this.size.trim().toLowerCase()
    }
  }
}

改变 Object/Array Props

Child component 可以 mutate object/array’s nested properties,因为在 JS 里面,objects and arrays 是通过 reference 传入的,所以 Vue 也没有必要去阻止这种 mutation。

问题:会改变 parent state,但是比较难发现,因此导致 data flow 不明确。

best practice:不要在 child 里面改这些 value,除非设计本身就是让 child 和 parent tightly coupled。大部分情况下,我们都应该让 child emit an event to let the parent perform the mutation.


5. Prop Validation

Components 可以明确地写出对 props 的要求,比如 type,是否 require。

假如传入的值没有达到要求,Vue 会有警告提示。

默认情况下,所有 props 都是 optional;假如没有传入值,就是 undefined;假如有 default value,就会在 override undefined。

Note that props are validated before a component instance is created, so instance properties (e.g. data, computed, etc.) will not be available inside default or validator functions.

Example

export default {
  props: {
    // Basic type check
    //  (`null` and `undefined` values will allow any type)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true
    },
    // Number with a default value
    propD: {
      type: Number,
      default: 100
    },
    // Object with a default value
    propE: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function. The function receives the raw
      // props received by the component as the argument.
      default(rawProps) {
        // default function receives the raw props object as argument
        return { message: 'hello' }
      }
    },
    // Custom validator function
    propF: {
      validator(value) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // Function with a default value
    propG: {
      type: Function,
      // Unlike object or array default, this is not a factory function - this is a function to serve as a default value
      default() {
        return 'Default function'
      }
    }
  }
}

Runtime Type Checks

Type 可以有以下几种:

也可以用 custom class 或者 constructor function 来做 type,此时的 runtime check 就是 instanceof check。

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

export default {
  props: {
    author: Person
  }
}

6. Boolean Casting

Boolean 类型的 Props 有 special casting rules,用来模拟原生 boolean attributes。

Example

<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />

<!-- equivalent of passing :disabled="false" -->
<MyComponent />
 export default {
  props: {
    disabled: Boolean
  }
}

Example - multiple types allowed

The casting rules for Boolean will apply regardless of type appearance order.

export default {
  props: {
    disabled: [Boolean, Number]
  }
}