全局组件与局部组件

官方文档
Vue.createApp()函数的参数传递的是根组件的属性

使用app.component()注册全局组件

  1. const app = Vue.createApp({...})
  2. app.component('my-component-name', {
  3. /* ... */
  4. })

全局组件的组件名一般小写,用连字符-分隔
全局注册的组件的可以在任意组件中使用,且会一直存在
组件具备可复用性,组件数据具有独占性

可以通过普通的javascript对象声明局部组件

  1. const ComponentA = {
  2. data(){},
  3. methods:{},
  4. template: ''
  5. }
  6. const ComponentB = {
  7. /* ... */
  8. }
  9. const app = Vue.createApp({
  10. components: {
  11. 'component-a': ComponentA,
  12. 'component-b': ComponentB
  13. }
  14. })

局部组件名称用驼峰式,首字母大写
局部组件需要在components属性中声明才能使用

props传值和props验证

一般情况下,props以数组形式列出,props还可以以对象形式列出,用于对props进行验证
props传值
props验证
2个要注意的点:

  1. 如果一次传递多个props,可以用v-bind绑定一个对象传递, 这个对象的所有属性对将作为单独的props

    1. post: {
    2. id: 1,
    3. title: 'My Journey with Vue'
    4. }
    1. <blog-post v-bind="post"></blog-post>

    等价于

    1. <blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
  2. props大小写

传递props时要用kebab-case分隔形式名称

  1. <blog-post post-title="hello!"></blog-post>

接收props时要用驼峰式

  1. const app = Vue.createApp({})
  2. app.component('blog-post', {
  3. // camelCase in JavaScript
  4. props: ['postTitle'],
  5. template: '<h3>{{ postTitle }}</h3>'
  6. })

单向数据流

子组件可以使用父组件的数据,但不能直接修改父组件的数据

Non-prop attribute

官方文档
父组件给子组件传值,子组件未通过props接收,那么这个值将自动添加根节点的attribute中

  1. const app = Vue.createApp({
  2. template: `
  3. <my-component class="hello" style="color:red;"/>
  4. `
  5. })
  6. app.component('my-component',{
  7. template: `<div>
  8. <p>hello world!</p>
  9. <input type="text">
  10. <button>点我</button>
  11. </div>`
  12. })

image.png
子组件可以通过this.$attrs获取到传递的值

  1. app.component('my-component',{
  2. created(){
  3. console.log(this.$attrs)
  4. },
  5. // ...
  6. })

如果想要根元素内的其他元素继承传递的属性,使用v-bind="$attrs"即可

  1. app.component('my-component',{
  2. template: `<div>
  3. <p>hello world!</p>
  4. <input type="text" v-bind="$attrs">
  5. <button>点我</button>
  6. </div>`
  7. })

如果你希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。

  1. app.component('my-component',{
  2. inheritAttrs: false,
  3. template: `<div>
  4. <p>hello world!</p>
  5. <input type="text" v-bind="$attrs">
  6. <button>点我</button>
  7. </div>`
  8. })

父子组件通过事件通信

子组件可以通过$emit向父组件抛出事件

  1. <script>
  2. const app = Vue.createApp({
  3. data(){
  4. return {
  5. count: 1
  6. }
  7. },
  8. methods:{
  9. handleAddOne(value){
  10. this.count = value
  11. }
  12. },
  13. template: `
  14. <my-component :count="count" @add-one="handleAddOne"/>
  15. `
  16. })
  17. app.component('my-component',{
  18. props:['count'],
  19. methods:{
  20. handleBtnClick(){
  21. this.$emit('addOne', this.count + 1)
  22. }
  23. },
  24. template: `<div>
  25. {{count}}
  26. <button @click="handleBtnClick">+1</button>
  27. </div>`
  28. })
  29. app.mount("#root")
  30. </script>

Vue3中取消了.sync修饰符,取而代之的是v-model

  1. <script>
  2. const app = Vue.createApp({
  3. data(){
  4. return {
  5. count: 1
  6. }
  7. },
  8. template: `
  9. <my-component :count="count" @update:count="count = $event"/>
  10. `
  11. })
  12. app.component('my-component',{
  13. props:['count'],
  14. methods:{
  15. handleBtnClick(){
  16. this.$emit('update:count', this.count + 1)
  17. }
  18. },
  19. template: `<div>
  20. {{count}}
  21. <button @click="handleBtnClick">+1</button>
  22. </div>`
  23. })
  24. app.mount("#root")
  25. </script>

上面的代码在vue2等价于使用.sync修饰符

  1. <my-component :count.sync="count" />

但是在vue3中要使用v-model, 且props的名称默认写成modelValue

  1. <script>
  2. const app = Vue.createApp({
  3. data(){
  4. return {
  5. count: 1
  6. }
  7. },
  8. template: `
  9. <my-component v-model="count"/>
  10. `
  11. })
  12. app.component('my-component',{
  13. props:['modelValue'],
  14. methods:{
  15. handleBtnClick(){
  16. this.$emit('update:modelValue', this.modelValue + 1)
  17. }
  18. },
  19. template: `<div>
  20. {{modelValue}}
  21. <button @click="handleBtnClick">+1</button>
  22. </div>`
  23. })
  24. app.mount("#root")
  25. </script>

如果要自定义props的名称,则要写成v-model:xxx,这样允许绑定多个v-model

  1. <ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
  2. <!-- 是以下的简写: -->
  3. <ChildComponent
  4. :title="pageTitle"
  5. @update:title="pageTitle = $event"
  6. :content="pageContent"
  7. @update:content="pageContent = $event"
  8. />

v-model自定义修饰符

官方文档

  1. const app = Vue.createApp({
  2. data(){
  3. return {
  4. count: 'a'
  5. }
  6. },
  7. template: `
  8. <my-component v-model.capitalize="count"/>
  9. `
  10. })
  11. app.component('my-component',{
  12. props:{
  13. modelValue: String,
  14. modelModifiers: {
  15. default: ()=>({})
  16. }
  17. },
  18. methods:{
  19. handleBtnClick(){
  20. let value = this.modelValue + 'b'
  21. if(this.modelModifiers.capitalize){
  22. value = value.charAt(0).toUpperCase() + value.slice(1)
  23. }
  24. this.$emit('update:modelValue', value)
  25. }
  26. },
  27. template: `<div>
  28. {{modelValue}}
  29. <button @click="handleBtnClick">+b</button>
  30. </div>`
  31. })

通过modelModifiers接收修饰符,如果修饰符xxx存在,this.modelModifiers.xxx则为true
对于带参数的 v-model 绑定,生成的 prop 名称将为 arg + “Modifiers”:

  1. <my-component v-model:description.capitalize="myText"></my-component>
  1. app.component('my-component', {
  2. props: ['description', 'descriptionModifiers'],
  3. emits: ['update:description'],
  4. template: `
  5. <input type="text"
  6. :value="description"
  7. @input="$emit('update:description', $event.target.value)">
  8. `,
  9. created() {
  10. console.log(this.descriptionModifiers) // { capitalize: true }
  11. }
  12. })

插槽slot

官方文档
插槽用于父子组件间传递DOM,是内容分发的出口

动态组件和异步组件

官方文档
动态组件: 通过component标签和is属性绑定组件名,实现动态切换
使用keep-alive标签记住组件切换时的状态

  1. const app = Vue.createApp({
  2. data(){
  3. return {currentItem: 'input-item'}
  4. },
  5. methods: {
  6. handleClick(){
  7. if(this.currentItem === 'input-item'){
  8. this.currentItem = 'common-item'
  9. }else{
  10. this.currentItem = 'input-item'
  11. }
  12. }
  13. },
  14. template:`
  15. <keep-alive>
  16. <component :is="currentItem" />
  17. </keep-alive>
  18. <button @click="handleClick">切换</button>
  19. `
  20. })

异步组件:以异步方式返回组件

  1. app.component('async-common-item', Vue.defineAsyncComponent(()=>{
  2. return new Promise((resolve, reject) => {
  3. setTimeout(()=>{
  4. resolve({
  5. template: `<div>this is an async component</div>`
  6. })
  7. },4000)
  8. })
  9. }))

v-once

让组件只渲染一次,即使数据发生变化也不再渲染

  1. app.component('terms-of-service', {
  2. template: `
  3. <div v-once>
  4. <h1>Terms of Service</h1>
  5. ... a lot of static content ...
  6. </div>
  7. `
  8. })

ref

在渲染之后,获取到DOM节点

  1. const app = Vue.createApp({
  2. mounted(){
  3. console.log(this.$refs.divRef)
  4. },
  5. template: `
  6. <div ref="divRef">hello</div>
  7. `
  8. })

或者用于获取子组件的引用,以便调用子组件的方法(慎用)

  1. const app = Vue.createApp({
  2. mounted(){
  3. this.$refs.child.sayHello()
  4. },
  5. template: `
  6. <my-component ref="child"></my-component>
  7. `
  8. })
  9. app.component('my-component',{
  10. methods:{
  11. sayHello(){
  12. console.log('hello')
  13. }
  14. },
  15. template: `<div>hello</div>`
  16. })

provide/inject

官方文档
如果子组件嵌套很深,避免用props逐层传递数据,可以使用provide/inject

  1. const app = Vue.createApp({
  2. provide:{
  3. name: 'jack'
  4. },
  5. template: `
  6. <son></son>
  7. `
  8. })
  9. app.component('son',{
  10. template: `<grandson></grandson>`
  11. })
  12. app.component('grandson',{
  13. inject:['name'],
  14. template: `<div>{{this.name}}</div>`
  15. })

如果要传递父组件的data或其他属性,要改用函数形式返回对象

  1. const app = Vue.createApp({
  2. data(){
  3. return {
  4. name: 'jack'
  5. }
  6. },
  7. provide(){
  8. return {
  9. username: this.name
  10. }
  11. },
  12. template: `
  13. <son></son>
  14. `
  15. })
  16. app.component('son',{
  17. template: `<grandson></grandson>`
  18. })
  19. app.component('grandson',{
  20. inject:['username'],
  21. template: `<div>{{this.username}}</div>`
  22. })