vue组件化开发

定义一个组件

构建步骤中

//子组件
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">clicked {{ count }} times.</button>
</template>

不使用构建步骤中

//子组件
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      clicked {{ count }} times.
    </button>`
}

一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义

全局注册

我们可以使用 Vue 应用实例app.component() 方法,让组件在当前 Vue 应用中全局可用。

  1. 首先,创建一个名为 MyButton.vue 的组件,代码如下:

<!-- content in MyButton.vue -->
<template>
    <button class="my-button" @click="count++">
      You clicked me {{ count }} times.
    </button>
</template>

<script>
  export default {
    data() {
      return {
        count: 0
      }
    }
  }
</script>

<style scoped>
  .my-button {
    background-color: blue;
    color: #fff;
    padding: 10px 20px;
  }
</style>
  
  1. main.js 中进行全局注册:

// content in main.js
import { createApp } from 'vue'
import App from './App.vue'
import MyButton from './components/MyButton.vue'

const app = createApp(App)

app.component('MyButton', MyButton)

app.mount('#app')
  1. 最后,在 App.vue 中使用组件:

<!-- content in  App.vue-->
<template>
  <div class="app">
    <h1>我是全局注册</h1>
    <MyButton></MyButton>
<!-- <my-button></my-button> -->
  </div>
</template>

为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyButton 为名注册的组件,在模板中可以通过 <MyButton><my-button> 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。

局部注册

局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。

局部注册需要使用 components 选项:

  1. 组件实现保持不变

<!-- content in MyButton.vue -->
<template>
    <button class="my-button" @click="count++">
      You clicked me {{ count }} times.
    </button>
</template>

<script>
  export default {
    data() {
      return {
        count: 0
      }
    }
  }
</script>

<style scoped>
  .my-button {
    background-color: blue;
    color: #fff;
    padding: 10px 20px;
  }
</style>
  1. MyButton.vue 中的组件名称从全局注册中删除。为了在模板中使用该组件,我们需要在 App.vue 中进行局部注册

// content in main.js
import { createApp } from 'vue'
import App from './App.vue'
// import MyButton from './components/MyButton.vue'

const app = createApp(App)

// app.component('MyButton', MyButton)

app.mount('#app')
  1. App.vue 中导入组件:

<!-- content in  App.vue-->
<template>
  <div class="app">
    <h1>我是局部注册</h1>
    <my-button></my-button>
  </div>
</template>
<script>
import MyButton from './components/MyButton.vue'

export default {
  components: {
    // 在这里进行局部注册
    'my-button': MyButton
  }
}
</script>

请注意:局部注册的组件在后代组件中并可用。在这个例子中,my-button 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

父组件往子组件中传数据:传递 props

在 Vue 中,props 是一种用于父组件向子组件传递数据的方式。具体来说,父组件可以通过 props 向子组件传递任何数据,包括字符串、数字、布尔值、对象、数组等等。

props 是一个对象,其中包含了子组件需要接收的属性名称和属性类型。子组件在使用 props 之前,必须先声明这些属性。

<!-- 子组件 MyInput.vue -->
<template>
  <div>
    <button>父组件传递的整数类型数据: {{ id }}</button>
    <button>父组件传递的字符串类型数据: {{ MenuTitle }}</button>
    <button>父组件传递的数组类型数据: {{ list }}</button>
    <button>父组件传递的对象类型数据: {{ obj }}</button>
  </div>
</template>
  
  <script>
export default {
  props: {
    id: {
      type: Number,
      required: true,
    },
    MenuTitle: {
      type: String,
      required: true,
    },
    list: {
      type: Array,
      required: true,
    },
    obj: {
      type: Object,
      required: true,
    },
  },
};
</script>
<!-- 父组件 App.vue -->
<template>
  <div>
    <h1>下面是使用子组件,并传递了多个类型的值给子组件</h1>
    <my-input
      :id="post.id"
      :MenuTitle="post.MenuTitle"
      :list="post.list"
      :obj="post.obj"
    ></my-input>
  </div>
</template>

<script>
import MyInput from "./components/MyInput.vue";

export default {
  components: {
    "my-input": MyInput,
  },
  data() {
    return {
      post: {
        id: 1,
        MenuTitle: "My Journey with Vue",
        list: ["item1", "item2", "item3"],
        obj: {
          name: "张三",
          age: 18,
          sex: "男",
        },
      },
    };
  },
};
</script>
  • 所有 prop 默认都是可选的,除非声明了 required: true

子组件往父组件中传数据:$emit

在 Vue 中,子组件可以通过 $emit 方法向父组件传递数据。具体步骤如下:

<!-- 子组件 ChildComponent.vue -->
<template>
  <button @click="sendDataToParent">点击我向父组件传递数据</button>
</template>

<script>
export default {
  methods: {
    sendDataToParent() {
      this.$emit('child-click', '这是从子组件传递的数据');
    }
  }
}
</script>
<!-- 父组件 App.vue -->
<template>
  <div>
    <ChildComponent @child-click="handleChildClick"></ChildComponent>
    <p>从子组件传递的数据:{{ childData }}</p>
  </div>
</template>

<script>
import ChildComponent from "./components/ChildComponent.vue";

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      childData: ''
    }
  },
  methods: {
    handleChildClick($event) {
      this.childData = $event;
    }
  }
};
</script>

这里子组件中定义了一个 sendDataToParent 方法,在该方法中通过 $emit 触发了一个名为 child-click 的自定义事件,并将字符串 '这是从子组件传递的数据' 作为参数传递给该事件。父组件中使用了 <ChildComponent> 标签来引入子组件,并通过 @child-click="handleChildClick" 语法监听了子组件触发的 child-click 事件。当子组件触发该事件时,父组件中的 handleChildClick 方法就会被调用,并将子组件传递过来的数据赋值给 childData 数据项。在父组件的模板中,使用了 Mustache 语法 {{ childData }} 来显示从子组件中接收到的数据。

  • $emit 是 Vue 实例中的一个方法,用于触发当前实例上的事件。通常用于子组件向父组件传递消息。

  • 当子组件内部发生了某个事件,可以使用 $emit 来触发一个自定义事件,并且可以传递一些数据给父组件。

  • 父组件可以监听子组件上触发的事件,并在相应事件发生时做出反应,从而实现子组件向父组件传递数据和通信。

插槽

插槽是 Vue.js 中一种重要的组件通信方式,它可以让子组件在父组件中动态地插入内容,增强组件的灵活性和可复用性。Vue.js 中的插槽可以分为两种:具名插槽和默认插槽。

本质:插槽就是一种占位符,用来标记子组件的某些区域可以被父组件插入任意内容。在子组件内部,插槽通常通过 <slot> 标签来定义

具名插槽

具名插槽是通过 v-slot 指令来定义的,它允许我们给插槽起一个名称,并将插槽的内容作为子组件的一部分传递进去。具名插槽可以让子组件根据父组件传递的不同数据,动态地修改其展示的内容,从而实现更高的灵活性和可复用性。

<!-- 子组件 MyButton.vue -->
<template>
  <button><slot name="text">Submit</slot></button>
</template>
<!-- 父组件 App.vue -->
<template>
  <div>
    <my-button>
      <!-- 给名为 text 的插槽传递数据 -->
      <template v-slot:text>提交表单</template>
      <!--<template #text>提交表单</template>-->
    </my-button>
  </div>
</template>

<script>
  import MyButton from './components/MyButton.vue'

  export default {
    components: {
      MyButton
    }
  }
</script>

在上述代码中,我们定义了一个名为 MyButton 的子组件,它包含了一个名为 text 的具名插槽。插槽的默认内容是 Submit,如果父组件需要插入其他的内容,可以通过具名插槽来动态设置。

在父组件中,我们通过 v-slot 指令和 name 属性来给具名插槽传递数据。在这个例子中,我们将文本 提交表单 传递到名为 text 的插槽中,最终渲染出来的按钮文本就是 提交表单

需要注意的是,具名插槽也可以有默认内容,例如在 MyButton 组件中的插槽默认内容是 Submit。如果父组件没有为该插槽传递数据,则会使用默认内容进行渲染。

Vue.js 支持使用 # 符号来替代 v-slot 的语法。

默认插槽

<!-- 子组件 MyCard.vue -->
<template>
    <div class="card">
      <slot></slot>
    </div>
  </template>
  
  <script>
  export default {
    name: 'my-card',
    props: {
      title: String
    }
  }
  </script>
  
<!-- 父组件 App.vue -->
<template>
  <div>
    <my-card title="My Title">
      <p>This is the main content</p>
      <button>Submit</button>
    </my-card>
  </div>
</template>
<script>
import MyCard from "./components/MyCard.vue";
export default {
  components: {
    "my-card":MyCard
  },
};
</script>

我们定义了一个名为 my-card 的组件,并在组件内部使用了一个默认插槽。默认情况下,组件内部所有未具名的子元素都会被渲染到该插槽中。

my-card 组件的插槽会将 <p>This is the main content</p><button>Submit</button> 渲染出来,从而使组件变得更加灵活,并支持用户自定义内容。

需要注意的是,当一个组件同时包含具名插槽和默认插槽时,未命名的子元素会被渲染到默认插槽中。如果要在组件中同时使用多个默认插槽,可以通过为每个默认插槽添加 name 属性来实现。

插槽作用域

<!-- 子组件 MyList.vue -->
<template>
    <div class="list">
      <h2>{{ title }}</h2>
      <ul v-if="items.length > 0">
        <li v-for="(item, index) in items" :key="index">
          <slot name="item" :item="item" :index="index">
            {{ item }}
          </slot>
        </li>
      </ul>
      <p v-else>{{ emptyText }}</p>
    </div>
  </template>
  
  <script>
  export default {
    name: "MyList",
    props: {
      items: {
        type: Array,
        default: () => []
      },
      title: String,
      emptyText: {
        type: String,
        default: "No data available."
      }
    }
  };
  </script>
  

在上面的代码中,我们为 <my-list> 组件添加了一个 name 选项,以便在插槽中使用 v-slot 指令。由于我们想要在插槽中访问 itemindex 属性,因此我们将它们作为插槽作用域变量进行传递,使用 :item="item":index="index" 的形式。在插槽内部,我们可以通过 $slotProps 对象来获取这些属性。例如:

<!-- 父组件 App.vue -->
<div>
    <my-list :items="['apple', 'banana', 'orange']">
      <template v-slot:item="props">
        {{ props.index + 1 }}. {{ props.item }}
      </template>
    </my-list>
</div>
<script>
import MyList from "./components/MyList.vue";
 
export default {
  components: {
    "my-list":MyList
  },
};
</script>

在这个例子中,我们定义了一个名为 item 的插槽,并使用 v-slot:item 来指定该插槽的名称。然后,我们可以通过 props.indexprops.item 来访问 itemindex 属性,并在模板中使用它们来渲染每个列表项的内容。这样,我们就可以将 <my-list> 组件作为一个可复用的模块,任意地配置和定制它的显示效果和行为。

在父组件的模板中,我们可以通过在子组件标签上添加 items 属性来向子组件传递数据

我们使用 slot 元素来定义一个 item 插槽,并向插槽中传递两个属性值:itemindex。这些属性值将在父组件中使用 v-slot 指令时,被作用域插槽变量 props 所接收。

能够访问子组件中数据的插槽,称为作用域插槽