# Vue2教程 - 14 组件

什么是组件?

前面在进行学习的时候,已经使用了组件,在 App.vue 组件中完成的,但是在项目中只使用了这一个组件,作为整个项目的根组件。

在正常的项目中,是将 App 作为根组件的,然后在 App 中通过路由(后面学习)来控制页面的切换,实际控制的是组件的切换;页面中展示的内容,也是封装成一个个组件,利于功能的划分和代码的复用。最终构成整个前端的 SPA(Single Page Application) 单页面应用。

下面来正式学习一下组件的使用。

# 14.1 创建组件

现在我们来创建一个组件,然后在 App.vue 根组件中引入并使用这个组件。

# 1 创建组件

src 目录下创建一个 components目录,然后在 components 目录下创建一个 HomePage.vue 文件,后缀名为 .vue

HomePage.vue 文件中输入如下内容:

<!-- 1.组件模板 -->
<template>
    <div>
        <h1>HomePage组件</h1>
    </div>
</template>

<!-- 2.组件业务逻辑 -->
<script>
export default {
    name: "HomePage"    //可以省略
}
</script>

<!-- 3.编写组件样式,scoped表示样式只对当前组件生效 -->
<style scoped>

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

组件分为三个部分,在 HelloWorld 中已经讲解过了。

# 2 引入和使用组件

现在在 App.vue 页面引入上面创建的组件,使用 import 进行引入:

<template>
  <div id="root">
    <!-- 使3.用组件 -->
    <HomePage />
  </div>
</template>

<script>
// 1.引入HomePage
import HomePage from '@/components/HomePage.vue';

export default {
  name: 'App',
  components: {
    HomePage  // 2.声明使用组件
  }
}  
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 首先使用 import 引入组件,路径中的 @ 表示 src 目录;
  • 在 App 组件的 components 属性中声明使用组件,如果有多个,用 , 分隔;
  • 然后就可以使用组件了: <HomePage></HomePage>

# 3 组件中的data

前面也已经介绍了,可以在 组件中定义 datamethodsfilterscomputed 等等。

关于组件的 data 有几点说明:

  1. 组件的 data 和 Vue 实例的 data 有点不一样,Vue 实例中的 data 可以为一个对象,但是组件中的 data 必须是一个方法;
  2. 组件中的 data 除了必须为一个方法之外,这个方法内部,还必须返回一个对象才行;
  3. 组件中的 data 数据,使用方式和Vue实例中的 data 使用方式完全一样,使用 this. 就可以直接访问 data 中的数据。

为什么组件中的数据必须返回一个函数?

如果data不是一个函数,而是一个对象,那么这个对象会在所有组件实例之间共享,导致一个实例对数据的更改会影响到其他实例。通过返回一个函数,每个组件实例都可以调用这个函数来获取自己的数据副本,从而保持数据的独立性。

# 14.2 父组件向子组件传值

在子组件中无法访问父组件中定义的数据和方法。

那么父子组件如何进行数据传递呢?

父组件通过属性绑定的方式给子组件传值,子组件在 props 属性中定义接收的值。

举个栗子:

我们再定义一个子组件 ChildCom.vue 然后在 HomePage.vue 组件中引入并使用,并传递参数。

# 1 定义子组件

子组件ChildCom.vue

<template>
    <div>
        <span>父组件传递的值:{{ superMsg1 }}, {{ superMsg2 }}</span>
    </div>
</template>

<script>
export default {
    name: "ChildCom",
    props: ['superMsg1', "superMsg2"]
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
  • 在子组件 ChildCom.vue 中通过在子组件上添加 props 属性来接收,可以定义多个参数,上面 ChildCom.vue 组件可以接收父组件传递 superMsg1superMsg2 参数;
  • 需要确保传递给子组件的 props名称不与子组件内部定义的 datacomputed属性或方法冲突。
  • 组件中,data 的数据是可读可写的,props 中的数据都是只读的,子组件不可修改。

# 2 父组件传值

父组件 HomePage.vue

<!-- 1.组件模板 -->
<template>
    <div>
        <ChildCom :superMsg1="'Hello'" :superMsg2="superMsg2"></ChildCom>

        <button @click="changeMsg">改变父组件的Msg</button>
    </div>
</template>

<!-- 2.组件业务逻辑 -->
<script>
import ChildCom from '@/components/ChildCom.vue';

export default {
    name: "HomePage", 
    components: {
        ChildCom
    },
    data() {
        return {
            superMsg2: "World"
        }
    },
    methods: {
        changeMsg() {
            this.superMsg2 = "Doubi"
        }
    },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

首先引入了子组件并使用,在使用的时候通过数据绑定的方式为给子组件传值,可以使用 v-bind:superMsg1:superMsg1 的方式。

上面给 superMsg1 传递的是一个字符串,然后将 HomePage 组件中的 superMsg2 传递给 ChildCom 组件的 superMsg2 ,所以通过点击按钮,修改 HomePage 组件中的 superMsg2 的值,那么传递给 ChildCom 组件的 superMsg2 也会同步修改。

也就是说:父组件传递给子组件的值是无法在子组件进行修改的,但是可以在父组件中修改。

# 14.3 子组件向父组件传值

子组件向父组件传值是通过方法回调的方式实现的,即父组件向子组件传递函数的引用,子组件调用传递的函数,将数据作为函数的参数来实现数据的传递。

父组件向子组件传递函数:

<ChildCom @childClick="childChangeMsg"></ChildCom>
1

childChangeMsg 函数传递给子组件,使用事件绑定的方式,绑定的名称是 childClick ;可以使用多个名称绑定多个函数,那么子组件就可以调用父组件的多个函数。

子组件调动父组件函数:

父组件传递给子组件的函数,不能直接调用,需要通过 this.$emit('函数名称') 来调用父组件传递的函数的。

this.$emit('childClick', arg)
1

参数的传递 this.$emit() 函数的第一参数是函数绑定的名称,后面的参数可以传递参数,父组件的函数的参数可以直接获取到传递的参数,参数可以多个。


举个栗子:

下面的例子中,父组件将函数传递给子组件,通过点击子组件的按钮,来触发父组件传递的函数,将子组件自己的私有数据传递给父组件。

# 1 定义父组件

父组件 HomePage.vue

<template>
    <div class="home">
        <div>{{ msg }}</div>

        <!-- 3.使用子组件 -->
        <ChildCom @childClick="childChangeMsg"></ChildCom>
    </div>
</template>

<script>
// 1.引入子组件
import ChildCom from '@/components/ChildCom'
export default {
    name: 'HomePage',
    // 2.注册子组件
    components: {
        ChildCom
    },
    data: function () {
        return {
            msg: 0
        }
    },
    methods: {
        // 通过子组件调用父组件的方法,修改父组件中的数据
        childChangeMsg(childData) {
            this.msg += childData;
        }
    },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

父组件在使用子组件的时候通过 @childClick="childChangeMsg" 将函数 childChangeMsg 绑定到指令 childClick 上,在子组件中就可以通过 childClick 调用父组件的函数了。

# 2 定义子组件

子组件 ChildCome.vue

点击按钮后,通过调用父组件方法,传递数据给父组件。

<template>
    <div>
        <button @click="doClick">这是子组件中的按钮</button>
    </div>
</template>

<script>
export default {
    data: function () {
        return {
            data: 1,
        };
    },
    methods: {
        doClick() {
            // 调用父组件中的方法,并传递参数
            this.$emit('childClick', this.data)
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

参数可以传递多个,也可以传递对象。

# 14.4 标签的ref属性

如果想获取元素和组件的 DOM,可以使用标签的 ref 来实现。

ref 可以添加在普通的 HTML 标签上,也可以添加在组件标签上:

  • 添加在普通的HTML标签上,获取的是 DOM 节点;
  • 添加在 Vue 组件标签上,获取的是组件实例。

如果在以前,我们会使用 document.getElementById() 的方式来获取到 DOM 元素,但在 Vue 中不推荐。

首先定义子组件 ChildCom.vue 如下:

<template>
    <div>
        {{ msg }}
    </div>
</template>

<script>
export default {
    data: function () {
        return {
            msg: "Hello Doubi",
        };
    },
    methods: {
        changeMsg() {
            this.msg = "Hello World";
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

然后在父组件中引入子组件,并使用 ref 来获取子组件的属性和调用子组件的方法。

HomePage.vue

在下面的代码中,同时给普通的标签和子组件添加 ref 属性,可以分别获取到普通组件的 DOM 和子组件的实例。

<template>
    <div class="home">
        <span id="my-span" ref="mySpan">逗比笔记</span>
        <ChildCom ref="childCom"></ChildCom>

        <button @click="testRef">测试ref</button>
    </div>
</template>

<script>
import ChildCom from '@/components/ChildCom'
export default {
    name: 'HomePage',
    components: {
        ChildCom
    },
    methods: {
        testRef() {
            // 不要使用元素js操作DOM
            console.log(document.getElementById('my-span').innerText)

            // 获取mySpan标签中的内容
            console.log(this.$refs.mySpan.innerText)

            // 可以直接获取子组件的属性
            console.log(this.$refs.childCom.msg)
            // 可以直接调用子组件的方法
            this.$refs.childCom.changeMsg();
        }
    },

}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

可以在父组件中通过 this.$refs.ref属性名称 来调用子组件的属性和方法。

# 14.5 组件的切换

这里的切换,是某一个页面中一个部件的切换,在 Vue 中,页面也是组件,但是页面之间的切换,是通过后面的路由来完成的,路由后面再讲解。

例如在登录页面,有两个按钮登录和注册,当点击登录按钮,显示输入登录信息的输入框,点击注册,在同样的位置显示填写注册的信息,其实这是一个登录组件和注册组件的切换,那么如何实现组件的切换呢?

# 1 通过v-if实现

通过在Vue实例中定义一个flag,在组件上通过v-if来获取flag的值来判断组件要不要显示,当点击登录或注册按钮的时候,修改flag的值。

定义两个组件

LoginCom.vue

<template>
    <div id="root">
        登录
    </div>
</template>

<script>
export default {
}
</script>

<style scoped>
#root {
    width: 200px;
    height: 200px;
    background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Register.vue

<template>
    <div id="root">
        注册
    </div>
</template>

<script>
export default {
}
</script>

<style scoped>
#root {
    width: 200px;
    height: 200px;
    background-color: yellowgreen;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

实现切换

在Home.vue中引入两个组件,通过定义 flag 属性,使用 v-if 来实现隐藏和显示。

<template>
    <div class="home">
      <!-- 点击的时候,修改flag的值 -->
      <a href="" @click.prevent="flag=!flag">切换</a>
  
      <!-- 通过flag的值来判断显示哪个组件 -->
      <LoginCom v-if="flag"></LoginCom>
      <RegisterCom v-else></RegisterCom>
    </div>
  </template>
  
  <script>
  import LoginCom from '@/components/LoginCom'
  import RegisterCom from '@/components/RegisterCom'
  export default {
    name: 'HomePage',
    data: function() {
      return {
        flag: true
      }
    },
    components: {
        LoginCom,RegisterCom
    }
  }
  </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

效果如下:

# 2 使用component标签

使用 <component> 标签来实现切换。

<component :is="componentName"></component>
1

:is 属性指定的 componentName 是组件的名称,是一个字符串,只要这个值是哪个组件的名称,则在这个标签的位置就显示哪个组件。

这样我们在 data 中定义一个变量用来保存显示的组件的名称,当点击登录注册按钮的时候,修改这个变量的值就可以了。

Home.vue代码:

<template>
    <div class="home">
        <!-- 点击的时候,修改componentName的值 -->
        <a href="" @click.prevent="componentName = 'LoginCom'">登录</a>
        <a href="" @click.prevent="componentName = 'RegisterCom'">注册</a>

        <!-- component标签 是一个占位符, :is属性,可以用来指定要展示的组件的名称 -->
        <component :is="componentName"></component>
    </div>
</template>

<script>
import LoginCom from '@/components/LoginCom'
import RegisterCom from '@/components/RegisterCom'
export default {
    name: 'HomePage',
    data: function () {
        return {
            componentName: 'LoginCom' // 当前 component 中的 :is 绑定的组件的名称
        }
    },
    components: {
        LoginCom, RegisterCom
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

当点击两个按钮的时候,通过修改 componentName 的值,使用 <component> 实现了组件的切换。

# 14.6 组件切换的动画

组件的切换很简单,只需要将 <component> 标签使用 <transition> 标签包裹,然后在编写动画过渡即可。

<transition> 上可以设置切换的模式,out-in 表示第一组件出去以后,第二个组件才能进来,不会同时看到两个组件。

HomeVue.vue

<template>
    <div class="home">
        <!-- 点击的时候,修改componentName的值 -->
        <a href="" @click.prevent="componentName = 'LoginCom'">登录</a>
        <a href="" @click.prevent="componentName = 'RegisterCom'">注册</a>

        <!-- component标签 是一个占位符, :is属性,可以用来指定要展示的组件的名称 -->
        <!-- 通过 mode 属性,设置组件切换时候的 模式 -->
        <transition mode="out-in">
            <component :is="componentName"></component>
        </transition>
    </div>
</template>

<script>
import LoginCom from '@/components/LoginCom'
import RegisterCom from '@/components/RegisterCom'
export default {
    name: 'HomePage',
    data: function () {
        return {
            componentName: 'LoginCom' // 当前 component 中的 :is 绑定的组件的名称
        }
    },
    components: {
        LoginCom, RegisterCom
    }
}
</script>

<style scoped>
    .v-enter,
    .v-leave-to {
        opacity: 0;
        transform: translateX(150px);
    }

    .v-enter-active,
    .v-leave-active {
        transition: all 0.5s ease;
    }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

mode 的默认值是 mode="in-out":同时开始进入和离开的过渡,但在实际的动画效果中,可能会看到新旧元素的重叠。