Vue组件

2,792 阅读5分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

介绍

组件系统是 Vue 的一个重要概念,Vue允许我们将代码拆分独立成一些将小型的可复用模块。这些模块就被成为组件,使用这些组件就可以构建一个大型项目。在Vue中组件分为全局组件局部组件

组件的创建

全局组件

注册在Vue构造函数中的组件,在Vue实例任何地方任何组件中都可以使用

语法: Vue.component('组件名', {组件的配置对象})

<div id="app">
        <gec-title></gec-title>
</div>

<script>
        //全局组件,使用Vue构造函数提供的component API添加在构造函数中,在Vue实例对象的任何地方都可以使用.\
        Vue.component('gec-title', {
            // 这里的配置选项与Vue实例对象的配置选项(除了没有el以外)基本相同
            // template 模板配置选项,Vue实例配置选项中也有此选项
            // 当前组件/vue实例渲染在页面中的DOM模板
            template: `
            <div>
                <h2>{{12 + 8}}</h2>
                我是组件gec-title的模板
            </div>
            `
        })

        new Vue({
            el: '#app'
        })
</script>

局部组件

通过components配置选项注册在Vue实例对象或其他子组件内部,只能在注册的父组件内部使用。下面的例子中组件'child-a'只能在'component-a'中使用不能在Vue实例中使用

语法: Vue配置选项和组件配置选项都支持components属性 components: { 组件名: { 组件配置选项 } }

<div id="app">
    <gec-title></gec-title>
    <component-a></component-a>
</div>

<script>
    //全局组件,使用Vue构造函数提供的component API添加在构造函数中,在Vue实例对象的任何地方都可以使用.\
    Vue.component('gec-title', {
        // 这里的配置选项与Vue实例对象的配置选项(除了没有el以外)基本相同
        // template 模板配置选项,Vue实例配置选项中也有此选项
        // 当前组件/vue实例渲染在页面中的DOM模板
        template: `
        <div>
            <h2>{{12 + 8}}</h2>
            我是组件gec-title的模板
        </div>
        `
    })

    new Vue({
        el: '#app',
        components: {
            // 在Vue实例中注册的局部组件 'component-a',只能在Vue的实例对象中使用
            'component-a': {
                template: `
                <div>
                    <h3>我是组件A</h3>
                    <child-a></child-a>
                    <gec-title/>
                </div>
                `,
                components: {
                    // 在组件'component-a'中注册的局部组件,该组件只能在组件'component-a'中使用
                    'child-a': {
                        template: '<h2>我是组件A的子组件childA</h2>'
                    }

                }
            }

        }
    })
</script>

注意: template属性指定DOM模板结构中必须有且仅有一个根DOM元素!

组件的data配置选项

概念: 组件设计初衷就是将哪些独立的可复用的代码块封装起来,因为对象是引用数据类型如果直接将组件的data属性设置为对象的话。同一个组件在复用时会导致多个组件同时读写同一个对象严重的影响了组件可复用性和独立性。为了解决这个问题Vue明确规定组件的data不可以是个对象,而是一个返回data对象的工厂模式函数。

语法:

// 普通函数
  Vue.component('gec-title', {
        template: `
        <div>
            <h2>{{name}}</h2>
        </div>
        `,
        data() {
            return {
                name: '小明',
                age: 18
            }
        }
    })
        
 // 箭头函数
 Vue.component('gec-title', {
        template: `
        <div>
            <h2>{{name}}</h2>
        </div>
        `,
        data: () => ({
                name: '小明',
                age: 18
            })
    })

单项数据流

概念: 在Vue中组件之间是单项数据流的。单项数据流规定子组件不可以直接访问父组件的数据,只能通过props属性让父组件把数据传递给子组件。并且子组件不可以直接修改父组件传递给子组件的数据。

props的使用

定义组件可以通过特殊的配置选项props给自身设置自定义属性,父组件就可通过props属性传值将父组件的数据传递给子组件,因为Vue是单项数据流子组件不可以修改props(props是只读的

注意:组件的data属性与props属性不能同名

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
    <h2>{{age}}</h2>
    <my-label v-bind:a="age" b="hello" :c="age >= 18?'已成年':'未成年'"/>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            age: 15
        },
        components: {
            'my-label': {
                // 给当前my-label设置了三个自定义属性a b c
                // 组件组件自身就可以通过 this.props.a 
                props: ['a','b','c'],
                data: () => ({
                    name: 'my-label'
                }),
                template: `
                <div> 
                    <p>a:{{a}}</p>
                    <p>b:{{b}}</p>
                    <p>c:{{c}}</p>
                    <child-a v-bind:name="name" :rootage="a"></child-a>
                </div>    
                `,
                components: {
                    'child-a': {
                        props: ['name','rootage'],
                        template: '<div>我是childA 父组件的name为{{name}} rootAge{{rootage}}</div>'
                    }
                }
            }
        }
    })
</script><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
    <h2>{{age}}</h2>
    <my-label v-bind:a="age" b="hello" :c="age >= 18?'已成年':'未成年'"/>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            age: 15
        },
        components: {
            'my-label': {
                // 给当前my-label设置了三个自定义属性a b c
                // 组件组件自身就可以通过 this.props.a 
                props: ['a','b','c'],
                data: () => ({
                    name: 'my-label'
                }),
                template: `
                <div> 
                    <p>a:{{a}}</p>
                    <p>b:{{b}}</p>
                    <p>c:{{c}}</p>
                    <child-a v-bind:name="name" :rootage="a"></child-a>
                </div>    
                `,
                components: {
                    'child-a': {
                        props: ['name','rootage'],
                        template: '<div>我是childA 父组件的name为{{name}} rootAge{{rootage}}</div>'
                    }
                }
            }
        }
    })
</script>

反向传值

由于之前已经写过一遍文章是介绍反向传值的,所以这里就不多说,详情请点击

Vue脚手架安装使用

在系统变量中安装Vue脚手架工具vue-cli

#全局安装vue-cli环境配置
npm install -g @vue/cli
#全局安装完毕后,以后 vue create 项目名称 搭建项目
vue create project_name   

执行创建vue项目指令后返回一个vue项目配置询问,先直接使用默认vue 2.0 模板

? Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint) #使用默认vue 2.0 模板
  Default (Vue 3 Preview) ([Vue 3] babel, eslint) #使用默认vue 3.0 模板
  Manually select features      #自定义模板

安装完毕后

#启动测试用服务开发指令
npm run serve
#项目开发完毕打包指令
npm run build

Vue-cli项目结构

  webpack-demo
  |- /public    // 公共目录,这个目录中的文件不会被webpack打包而是作为一个静态目录
                // 放置在 public 目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过 webpack 的处理。
      |- index.html // vue中 html模板文件
  |- /src  // 整个项目代码开发目录
      |- main.js // 项目的入口文件
  |- babel.config.js  // webpack babel-loader配置文件 
  |- package.json // 项目的配置描述文件
  |- README.md // 项目的readme文件
+|- vue.config.js // 可自定义Vue webpack相关配置的文件

public 文件夹

任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。

注意我们推荐将资源作为你的模块依赖图的一部分导入,这样它们会通过 webpack 的处理并获得如下好处:

  • 脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
  • 文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
  • 最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本。 public 目录提供的是一个应急手段,当你通过绝对路径引用它时,留意应用将会部署到哪里。如果你的应用没有部署在域名的根部,那么你需要为你的 URL 配置 publicPath 前缀:
  • public/index.html或其它通过 html-webpack-plugin 用作模板的 HTML 文件中,你需要通过 <%= BASE_URL %> 设置链接前缀:
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <link rel="stylesheet" href="<%= BASE_URL %>css/style.css">
  • 在js文件中 使用process.env.BASE_URL作为pubulic文件的前缀
<template>
  <div id="app">
    // 直接引入静态目录中的文件
    <img alt="Vue logo" :src="`${publicPath}imgs/01.jpg`">
    // 相对路径的引入会导致webpack对该文件进行打包
    <img alt="Vue logo" src="../public/imgs/01.jpg">

  </div>
</template>

<script>

export default {
  name: 'App',
  data() {
    return {
       // 获取公共目录路径
      publicPath: process.env.BASE_URL
    }
  }
}
</script>

public|Path配置实在项目的根目录下vue.config.js中设置publicPath选项就好了

module.exports = {
    publicPath: process.env.NODE_ENV === 'production'
  ? '/production-sub-path/' //真实开发的话,如果你的项目存放在公司域名二级路径下 只需要将 /production-sub-path/改为 /公司二级路径/就可以了
  : '/'
}

webpack 相关

简单的配置方式就是在Vue项目的跟目录下创建vue.config.js文件.这个通过配置这个文件中module.exports公开的对象实现对Vue Webpack进行修改.

  • 指定项目静态资源模块的相对路径
  • 指定打包后文件的目录(默认dist文件)
  • assets文件的目录
  • 多页面应用开发
  • css模块化 loader等
  • 给项目添加Webpack plugin
  • 配置当前项目的开发测试服务器
// vue.config.js
module.exports = {
    publicPath: process.env.NODE_ENV === 'production'
        ? '/production-sub-path/' //真实开发的话,如果你的项目存放在公司域名二级路径下 只需要将 /production-sub-path/改为 /公司二级路径/就可以了
        : '/',
    devServer: { // 服务器代理,当请求了代理设置的路径时 会自动跳转到指定服务器上,解决跨域问题
        proxy: {
            "/search": {
                target: 'http://musicapi.leanapp.cn/',
                changeOrigin: true
                // 当你请求 /search?123123 时 会代理到 'http://musicapi.leanapp.cn/search?123123'
            }
        }
    }
}

了解Vue-cli src目录结构

在src文件中main.js是整个项目的入口文件,也是实例化Vue对象的地方

因为vue-cli是模块化开发,所以整个项目不适用<script>标签引入Vue支持而是使用模块化依赖模式通过import引入Vue对象

import Vue from 'vue'
import App from './App.vue' // 引入单文件App组件

Vue.config.productionTip = false
// 实例化Vue
new Vue({
  render: h => h(App)  
  // render是template字符串模板的替代方案,render是一个函数函数接收一个参数(createElement)
  // 这个参数可以将组件生成为一个Vue DOM节点渲染在页面上
  // 所以上面这句化等价于 template: '<App></App>' 
}).$mount('#app')

// 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。
// 这时可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

认识单文件组件

我们观察下面的代码,在Vue实例中添加了局部组件App,如果说除了Vue实例以外其他组件可能也需要注册App局部组件的话。我们推荐将App抽离出来提供给其他组件复用

new Vue({
    el: '#app',
    components: {
        App: {
            props: ['name','age'],
            data() {
                return {
                    address: 'bj'
                }
            },
            template: `
            <div>
                <h2>用户:{{name}} 年龄:{{age}}</h2>
                <p>地址:{{address}}</p>   
            </div>
            `
        }
    }
})

抽离后

const App = {
    props: ['name','age'],
    data() {
        return {
            address: 'bj'
        }
    },
    template: `
    <div>
        <h2>用户:{{name}} 年龄:{{age}}</h2>
        <p>地址:{{address}}</p>   
    </div>
    `
}

new Vue({
    el: '#app',
    components: {
        App
    }
})

我们在开发中推荐使用模块化开发,建议将每个可复用的组件单独封装成一个js模块,通过import引入其他文件中复用。这样的好处是便于维护更新让项目的结构更明确。

// 将App组件封装成一个js模块
// App.js
export default {
    props: ['name','age'],
    data() {
        return {
            address: 'bj'
        }
    },
    template: `
    <div>
        <h2>用户:{{name}} 年龄:{{age}}</h2>
        <p>地址:{{address}}</p>   
    </div>
    `
}
// 其他组件或实例注册App组件
import App from './App'

new Vue({
    el: '#app',
    components: {
        App
    }
})

Vue为了简化template字符串模板的开发(使用字符串写HTML语法js无法格式化代码,编译器无法对模板进行补全和检查),Vue提供了一个.vue文件简化了js文件创建单文件组件时template字符串模板开发不便。.vue文件单独的将template选项抽离出来以HTML的形式进行开发。

// 上面的App.js就可以转化为
// App.vue
// template 被抽离出来变成了一个独立的标签,内部本来使用字符串模板代码变成HTML语法
<template>
  <div>
    <h2>用户:{{name}} 年龄:{{age}}</h2>
    <p>地址:{{address}}</p>
  </div>
</template>

<script>
export default {
  props: ["name", "age"],
  data() {
    return {
      address: "bj",
    };
  },
}
</script>

注意

  1. .vue文件不仅支持template标签指定组件的模板样式和script 公开当前模板配置选项,还支持style标签内置的设置当前组件的样式,而且style标签支持使用sass\less\stylus预编译语言
// template内部遵循XML语法规则,所有单一型标签后面一定要跟一个/,
// 并且template标签内部有且仅有一个根元素

<template>
  <div class="demo">
      我是Demo,在vue文件中 
      <input/>
  </div>
</template>

<script>
export default {};
</script>

/*
style有两个可选属性 
    lang="scss" 内部使用sass语法,项目要额外安装 sass-loader  
    lang="less" 内部使用less语法,项目要额外安装 less-loader  
    lang="stylus" 内部使用less语法,项目要额外安装 stylus-loader 
    不设置该属性则使用css
    scoped 样式私有化,如果设置了该属性,style标签内的所有样式只会对组件自身有效
*/

<style scoped>
.demo {
    color: green;
}
</style>
  1. 建议使用.vue创建组件时,组件名与文件名一致并且使用每个单词首字母大写的命名方法例子: HelloWorld.vue、DemoComponent.vue

Props验证

  • 概念: 在开发中我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这一模式会在开发中帮开发人员捕获大量的异常。
  • 语法: props可以是一个数组,数组中的每一项都是当前组件的props属性名。props属性还是是一个对象为每个指定的props属性指定其验证规则以及默认值
props: {
        // 当前组件的propsA属性必须是Number数据类型或者为空(可忽略)
        propA: Number, 
        // propB与propA等价
        propB: {
            type: Number
        },
        // prop属性可以设置为必要属性,渲染该组件时必须给该属性传值并且不可为空
        propC: {
            type: Number,
            required: true 
        },
        // prop属性支持默认值,当没有给该prop传递属性是,该属性则会使用默认值
        // 注意: 默认值优先级小于required
        propD: {
            type:Number,
            default: 15
        },
        propE: {
          type: Object,
          // 对象或数组默认值必须从一个工厂函数获取
          default: function () {
            return { message: 'hello' }
          }
        }
    }

Props验证规则

// 通过构造函数指定prop数据类型
 props: {
        // prop为指定数据类型只需要设置该属性type值为对应数据类型的构造函数
        propA: Number,
        propB: Boolean,
        propC: String,
        propD: Array,
        propE: Object,
        propF: Symbol,
        propG: RegExp,
        propH: Date,
        propI: Cat, // 可以指定任何构造函数作为prop的type,当前prop接收到的值必须是当前指定构造函数的实例对象
        propJ: Function
        // prop验证支持 多个可能的类型
        propK: [String, Number], 
        // 自定义验证规则,不满足验证条件是return false
        propL: {
          validator(val) {// val 传入到 propL属性的值
            if (typeof val === "string") {
              if (/fuck/gi.test(val)) {
                console.error("组件ComponentA中 属性 PropL包含敏感词汇!");
              } else {
                return true;
              }
            } else {
              console.error("数据必须是字符串");
            }
            return false;
          }
        }
    }

动态组件 & keep-alive

官网介绍

概念: Vue 提供了一个标签component,该标签可以使用 is attribute 来切换不同的组件:

<div id="app">
    <component :is="`my-${name}`"></component>

    <button @click="name ='a'">a</button>
    <button @click="name ='b'">b</button>
</div>

<script>
     new Vue({
         el: '#app',
         data: {
             name: 'a'
         },
         components: {
             'my-a': {
                 template: '<h2>我的组件A</h2>'
             },
             'my-b': {
                 template: '<h2 @click="num++">组件B{{num}}</h2>',
                 data() {
                     return {num: 7}
                 }
             },
         }
     })   

</script>
  • keep-alive: 当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。
<div id="app">
    <keep-alive>
        <component :is="`my-${name}`"></component>
    </keep-alive>

    <button @click="name ='a'">a</button>
    <button @click="name ='b'">b</button>
</div>

<script>
     new Vue({
         el: '#app',
         data: {
             name: 'a'
         },
         components: {
             'my-a': {
                 template: '<h2>我的组件A</h2>'
             },
             'my-b': {
                 template: '<h2 @click="num++">组件B{{num}}</h2>',
                 data() {
                     return {num: 7}
                 }
             },
         }
     })   

</script>