温故知新 Vue 3: Lesson 9

45 阅读6分钟

温故知新 Vue 3: Lesson 9

Components In-Depth

Component Registration

Registration

// With kebab-case
app.component("my-component-name", {
  /* ... */
});

// With PascalCase
app.component("MyComponentName", {
  /* ... */
});

The component's name is the first argument of app.component, When using a component directly in the DOM, we strongly recommend following the W3C rules for custom tag names:

1.All lowercase 2.Contains a hyphen

第一个参数是组件的名字. 当在 html 里面直接使用时, 名字应该全部使用小写, 多个单词使用-分隔

kebab-case, you must also use kebab-case when referencing its custom element, such as in <my-component-name>.

名字使用 kebab-case 时, 使用时也只能写成 kebab-case

PascalCase, you can use either case when referencing its custom element. That means both <my-component-name> and <MyComponentName> are acceptable.

名字使用 PascalCase 时, 使用时可以写成 kebab-case, 也可以写成 PascalCase.

Global Registration

<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>
const app = Vue.createApp({});
app.component("component-a", {
  /* ... */
});
app.component("component-b", {
  /* ... */
});
app.component("component-c", {
  /* ... */
});
app.mount("#app");

These components are globally registered for the application. That means they can be used in the template of any component instance within this application.

Vue.createApp()生成的 app instance 上注册的方式, 为全局注册. 应用的任意地方都可以使用这些已经注册的组件.

Local Registration

globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download.

全局注册的组件在编译之后, 即便没有用到仍然会存在于编译后的代码里, 会增加代码的体积. 这时候可以选择本地注册组件的方式.

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}

const app = Vue.createApp({
  components: {
   ComponentA,
   ComponentB
  })
})

For each property in the components object, the key will be the name of the custom element, while the value will contain the options object for the component.

components 对应的 object 里, key 是组件的名字, value 是组件

locally registered components are not also available in subcomponents.

本地注册的组件只允许在注册的地方使用, 如果别的地方也要使用, 也必须通过注册的方式. 比如组件 B 要使用组件 A, 在组件 B 里面注册组件 A.

const ComponentB = {
  components: {
    'component-a': ComponentA
  }

Module Systems

If you're using a module system, such as with Babel and Webpack. In these cases, we recommend creating a components directory, with each component in its own file.

如果你使用了 Module System. 那么建议你把组件放在 components 文件夹里面

-components
---ComponentA.vue
---ComponentB.vue
index.vue
// index.vue
import ComponentA from "./components/ComponentA";
import ComponentB from "./components/ComponentB";

export default {
  components: {
    ComponentA,
    ComponentB,
  },
  // ...
};

Props

Prop Types

// props listed as an array of strings:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

// list props as an object
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

Passing Static or Dynamic Props

<!-- Passing a String -->
<blog-post title="My journey with Vue"></blog-post>

<!-- Passing a Number-->
<blog-post :likes="42"></blog-post>

<!-- Passing a Boolean -->
<blog-post :is-published="false"></blog-post>

<!-- Passing an Array -->
<blog-post :comment-ids="[234, 266, 273]"></blog-post>

<!-- Passing an Object -->
<blog-post
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- Passing Dynamic Props -->
<blog-post :title="post.title"></blog-post>

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).

如果你想把一个 Object 的所有 property 都当做 props 传进去, 你可以直接使用 v-bind:obj

post: {
  id: 1,
  title: 'My Journey with Vue'
}
<!-- Passing all the Properties of an Object -->
<blog-post v-bind="post"></blog-post>

<!-- Will be equivalent to: -->
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>

One-Way Data Flow

when the parent property updates, it will flow down to the child, but not the other way around.

当 parent 数据更新时, 通过 props 流向 child, 但不能反过来.

This prevents child components from accidentally mutating the parent's state.

这有效防止 child 意外的更改 parent 的数据

This means you should not attempt to mutate a prop inside a child component.

意味着你不应该在 child 里面直接更改 prop

There are usually two cases where it's tempting to mutate a prop:

以下是两种 child 里面确实需要更改 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.

prop 用来传递初始值, child 里面使用 data property 来接收这个 prop 的初始值,以后直接更改这个 property 就好了.

props: ['initialCounter'],
data() {
  return {
    counter: this.initialCounter
  }
}

2.The prop is passed in as a raw value that needs to be transformed.

prop 作为一个需要转换后才能使用的值. 使用 computed property.

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state.

在 js 里面, object 和 array 是通过 reference 引用传递的, 因而如果在 child 里面直接修改 object 或 array, 是会直接影响 parent 的数据的.

Prop Validation

基本数据类型校验, 比如数字, 字符串等等

 props: {
    // Basic type check (`null` and `undefined` values will pass any type validation)
    propA: Number,
  }

也可以是一个 array, 表示多种可能的数据类型

 props: {
    // Multiple possible types
    propB: [String, Number],
  }

可以是一个 object, required property 表示是否必传

 props: {
     // Required string
    propC: {
      type: String,
      required: true
    },
  }

default 可以设置默认值, prop 没有时会使用这个默认值

 props: {
// Number with a default value
    propD: {
      type: Number,
      default: 100
    },
  }

default 也可以是一个函数, 返回默认值

 props: {
    // Object with a default value
    propE: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function
      default: function() {
        return { message: 'hello' }
      }
    },

validator 可以对 prop 进行校验, 返回 bool

 props: {
  // Custom validator function
    propF: {
      validator: function(value) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    },

type 为 Function, default 是一个默认函数, 但不是 factory function. 也就是说 default 是它本身, 而不是它的返回值.

 props: {
// 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: function() {
        return 'Default function'
      }
    }

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.

props 在 instance created 之前被校验, 因此这个时候 default 或 validator 不能里面访问 data 和 computed.

type checks

type 如果是 String, Number, Boolean, Array, Object, Date, Function, Symbol 中的一种, 使用 typeof 校验.

如果是 custom constructor function. 使用 instanceof 校验.

vue-next 源码

// componentProps.ts
function assertType(value: unknown, type: PropConstructor): AssertionResult {
  let valid;
  const expectedType = getType(type);
  if (isSimpleType(expectedType)) {
    const t = typeof value;
    valid = t === expectedType.toLowerCase();
    // for primitive wrapper objects
    if (!valid && t === "object") {
      valid = value instanceof type;
    }
  } else if (expectedType === "Object") {
    valid = isObject(value);
  } else if (expectedType === "Array") {
    valid = isArray(value);
  } else {
    valid = value instanceof type;
  }
  return {
    valid,
    expectedType,
  };
}

最后, 如果在html里面直接使用props, 避免使用 camelCase, 应该使用 kebab-case

Non-Prop Attributes

A component non-prop attribute is an attribute or event listener that is passed to a component, but does not have a corresponding property defined in props or emits.

non-prop attribute 是传递给组件的 attribute, 但是不在 props 或 emits 中.

Attribute Inheritance

When a component returns a single root node, non-prop attributes will automatically be added to the root node's attributes.

对于只有 single root element 的组件, non-prop attribute 会自动添加到 这个 single root element 上.

<!-- Date-picker component with a non-prop attribute -->
<date-picker data-status="activated"></date-picker>

<!-- Rendered date-picker component -->
<div class="date-picker" data-status="activated">
  <input type="datetime" />
</div>

This might be helpful when we have an HTML element with change event as a root element of date-picker.

change event listener is passed from the parent component to the child and it will be triggered on native <select> change event.

当这个 custom component 的 single root element 上有个 change 事件时, event listener 从 parent component 传递给这个 custom component. 会非常有用. event 可以直接在这个 root element 上触发.

<div id="date-picker" class="demo">
  <date-picker @change="showChange"></date-picker>
</div>
app.component("date-picker", {
  template: `
    <select>
      <option value="1">Yesterday</option>
      <option value="2">Today</option>
      <option value="3">Tomorrow</option>
    </select>
  `,
});

Disabling Attribute Inheritance

If you do not want a component to automatically inherit attributes, you can set inheritAttrs: false in the component's options.

如果你不想让组件自动继承 attribute, 你可以设置 inheritAttrs: false

The common scenario for disabling an attribute inheritance is when attributes need to be applied to other elements besides the root node.

比如我们想让 root element 里面的 其它 element使用这个 attrs. 而不是 root element.

in the event we need to apply all non-prop attributes to the input element rather than the root div element, this can be accomplished by using the v-bind shortcut.

除了设置 inheritAttrs: false之外, 我们在需要继承的元素上使用 v-bind="$attrs"

app.component("date-picker", {
  inheritAttrs: false,
  template: `
    <div class="date-picker">
      <input type="datetime" v-bind="$attrs" />
    </div>
  `,
});
<!-- Date-picker component with a non-prop attribute -->
<date-picker data-status="activated"></date-picker>

<!-- Rendered date-picker component -->
<div class="date-picker">
  <input type="datetime" data-status="activated" />
</div>

Attribute Inheritance on Multiple Root Nodes

Unlike single root node components, components with multiple root nodes do not have an automatic attribute fallthrough behavior.

如果是 multiple root node 的组件. attribute 并不会自动传递. 这时候需要手动指定需要继承 attribute 的 root node.

<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
// This will raise a warning
app.component("custom-layout", {
  template: `
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  `,
});

// No warnings, $attrs are passed to <main> element
app.component("custom-layout", {
  template: `
    <header>...</header>
    <main v-bind="$attrs">...</main>
    <footer>...</footer>
  `,
});