Vue组件化开发

154 阅读8分钟

1. 什么是组件

  • 传统方式开发
    • 一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)
  • 组件化开发
    • 每一个组件都有独立的 js,独立的 css,这些独立的 js 和 css 只供当前组件使用,不存在纵横交错。 更加便于维护
    • 代码复用性增强。组件不仅让 js css 复用了,HTML 代码片段也复用了(因为要使用组件直接引入 组件即可)
  • 什么是组件?
    • 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用
    • 模块:一个大的 js 文件按照模块化拆分规则进行拆分,生成多个 js 文件,每一个 js 文件叫做模块。凡 是采用模块方式开发的应用都可以称为模块化应用。
    • 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件 是可以包括模块的

2. 组件创建、注册和使用

<div id="app">
  <h1>{{msg}}</h1>  
  <!-- 3. 使用组件 -->
  <userlogin></userlogin>
  <userlist></userlist>
  <userlogin></userlogin>
</div>
 const myComponent = Vue.extend({
            template : `
            <ul>
                <li v-for="(user,index) of users" :key="user.id">
                    {{index}},{{user.name}}
                </li>
            </ul>
            `,
            data(){
                return {
                    users : [
                        {id:'001',name:'jack'},
                        {id:'002',name:'lucy'},
                        {id:'003',name:'james'}
                    ]
                }
            }
        })

        // 全局注册
        Vue.component('userlogin', userLoginComponent)
        // 2. 注册组件(局部注册)
            components : {
                // userlist是组件的名字。myComponent只是一个变量名。
                userlist : myComponent,
            }
        })
</body>
</html>

1.组件使用三步走

  • 第一步:创建组件
    • Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})
    • 区别:
      • 1.创建Veu组件时,配置项中不能使用el配置项(但需要template配置项来配置模板语句)
      • 2.配置项中daya不能使用对象形式,必须使用function
  • 第二步:注册组件
    • 局部注册:
      • 在配置项中使用components,语法格式:
           components : {
           // key:value
             组件的名字 : 组件对象
          }
      
    • 全局注册:Vue.compont("key",value)
    • 使用组件: 在页面中需要使用组建的位置:

2.使用组件小细节

  1. 在Vue当中是可以使用自闭合标签的,但是前提必须在脚手架环境中使用。
  2. 在创建组件的时候Vue.extend()可以省略,直接写:{}但是底层实际上还是会调用的,在注册组件的时候会调用。
  3. 组件的名字:
    • 第一种:全部小写
    • 第二种:首字母大写,后面都是小写
    • 第三种:kebab-case命名法(串式命名法。例如:user-login)
    • 第四种:CamelCase命名法(驼峰式命名法。例如:UserLogin),但是这种方式只允许在脚手架环境中使用。
  • 不要使用HTML内置的标签名作为组件的名字。
  • 在创建组件的时候,通过配置项配置一个name,这个name不是组件的名字,是设置Vue开发者工具中显示的组件的名字。

3.组件嵌套

    <div id="root"></div>
    <script>
        // 创建Y1组件
        const y1 = {
            template : `
                <div>
                    <h3>Y1组件</h3>
                </div>
            `
        }
        // 创建X1组件
        const x1 = {
            template : `
                <div>
                    <h3>X1组件</h3>
                </div>
            `
        }
        // 创建Y组件
        const y = {
            template : `
                <div>
                    <h2>Y组件</h2>
                    <y1></y1>
                </div>
            `,
            components : {y1}
        }
        // 创建X组件
        const x = {
            template : `
                <div>
                    <h2>X组件</h2>
                    <x1></x1>
                </div>
            `,
            components : {x1}
        }
        // 创建app组件
        const app = {
            template : `
                <div>
                    <h1>App组件</h1>
                    <x></x>
                    <y></y>
                </div>
            `,
            // 注册X组件
            components : {x,y}
        }
        // vm
        const vm = new Vue({
            el : '#root',
            template : `
                <app></app>
            `,
            // 注册app组件
            components : {app}
        })
    </script>

4.vm与vc区别与联系

1.this

  • new Vue({})配置项this:Vue实例(vm)
  • Vue.extend({})配置项this:VueComponent实例(vc)
  • vm与vc有大量的相同属性. 比如:生命周期钩子、methods、watch

2. vm === vc?

  • 差不多,但不完全相等
  • 列如:
    • vm上有el,vc上没有
    • vc的data必须是一个函数;而vm的data可以是对象,也可以是函数 总结:vm上有的vc上不一定有,vc上有的vm上一定有

3, Vue.extend()方法做了什么?

  • 每一次的extend调用返回的都是一个全新的VueComponent函数
  • Vue.extend()部分核心源码:

image.png

  • 注意:每一次都会返回用户全新的VueComponet构造函数 是全新的
  • 什么时候会调用构造函数来实列化VueComponent对象呢?

image.png

  • Vue 在解析时会创建一个 VueComponent 实例,也就是:new VueComponent()

4.通过vc可以访问Vue原型对象上的属性

  • 通过 vc 可以访问 Vue 原型对象上的属性:
Vue.prototype.counter = 100
console.log(vc.counter) // 100
  • 这样设计的好处?
    • 可以提高代码复用效率.Vue原型对象上有很多方法.
    • 例如:mount(),对于组件VueComponent来说就不需要额外提供了,直接使用vc调用mount(),对于组件VueComponent来说就不需要额外提供了,直接使用vc调用mount(),代码得到了复用
  • 实现机制?
VueComponent.prototype.__proto__ = Vue.protot

5.原型对象复习

  • prototype:显示原型对象,用法:函数.prototype,例如:Vue.prototype
  • _proto_:隐式原型对象,用法:实例.proto,例如:vm.proto
  • 无论是通过 prototype 还是__proto__,获取的对象都是同一个,它是一个共享的对象,称为:XX 的原型对象
  • 如果通过 Vue.prototype 获取的对象就叫做:Vue 的原型对象。
  • 如果通过 User.prototype 获取的对象就叫做:User
  • 图解:

image.png

6.原理剖析

VueComponent.prototype.__proto__ = Vue.protot

image.png

  • 这样设计的好处
    • 最终结果:Vue、vm、VueComponent、vc 都共享了 Vue 的原型对象(并且这个 Vue 的原型 对象只有一个)

5.单文件组件

1.什么是单文件组件?

  • 一个文件对应一个组件
  • 单文件组件的名字通常是:x.vue,这是 Vue 框架规定的,只有 Vue 框架能够认识,浏览器无法直接打开运行。需要 Vue 框架进行编译,将 x.vue 最终编译为浏览器能识别的 html
  • 单文件组件的文件命名规范和组件名的命名规范相同:
  1. 全部小写:userlist
  2. 首字母大写,后面全部小写:Userlist
  3. kebab-case 命名法:user-list
  4. CamelCase 命名法:UserList(我们使用这种方式,和 Vue 开发者工具呼应。)

2.x.vue文件内容包括三块:

  1. 结构:<template>HTML代码</template>
  2. 交互: <script>Js代码</script>
  3. 样式: <style>css代码</style>

3.export和import,ES6模块化语法

  • 使用export导出(暴露)组件,在需要使用组件都x.vue中使用import导入组件
    • 默认导入和导出
      • export default{}
      • import 任意名称 from '模块标识符'
    • 按需导入和导出
      • export{a,b}
      • import {a,b} from '模块标识符'
    • 分别导出
      • export var name = ‘zhangsan’
      • export function sayHi(){}

4. vue代码提示:vetur 插件

6. props配置

  • 使用props配置可以接受其他组件传过来都数据,让组件都数据变为动态数据,共有三种接收方式:
  • 简单接受
props : ['name','age','sex']
  • 接收时添加类型限制
props : { name : String age : Number sex : String }
  • 接收时添加类型限制,必要性限制,默认值
props : { 
name : {type : Number, required : true }, 
age : { type : Number, default : 10 },
sex : { type : String, default : ‘男’ }
}
  • 其他组件怎么将数据传递过来?
<User name="jack" age="20" sex="男"></User>
  • 注意:
    • 不要乱接收,接收的一定是其它组件提供的
    • props 接收到的数据不能修改。(修改之后会报错,但页面会刷新。)可以找个中间变量来解决

7.从父组件获取子组件

1.ref作用

  • ref被用来给元素或者子组件注册引用信息
  • 引用信息会被注册在父组件都$refs对象上
  • 如果DOM元素上使用,引向都就是DOM元素
  • 如果使用在子组件上,引用就指向组件实例

2.ref的使用

  • 在组件上使用ref属性进行标识:
<User ref="userJack"> </User>
  • 在程序中使用$refs来获取子组件:
this.$refs.userJack
  • 访问子组件的属性
this.$refs.userJack.name
  • 访问子组件都子组件属性:
this.$refs.userJack.$refs.name
  • ref 也可以使用在普通的 HTML 标签上,这样获取的就是这个 DOM 元素:
<input type="text" ref="test">
this.$refs.test

8.mixins配置(混入)

image.png

image.png

image.png

  • 运行结果

image.png

  • 该 代码中有Vip.vueUser.vue代码中都有相同都methods,怎么将代码进行复用?
  • 使用mixins配置进行混入.
  • 第一步:提取
  • 单独定义一个 mixin.js(一般和 main.js 在同级目录),代码如下:

image.png

  • 第二步:引入并使用

image.png

  • 以上演示的是methdos混入。如果已经有了一个a方法,那么再混入一个a方法会怎么样?

image.png

image.png

  • 通过该代码可知:如果冲突了,会执行组件自身的,不会执行混入的 。(这是原则:混入的意思就是不破坏)
  • 对于生命周期钩子函数来说,混入时,会采用叠加方式:

image.png

image.png

  • 执行结果:

image.png

  • 对于生命周期钩子来说,都有的话,采用叠加,先执行混入,再执行本身的
  • 以上混入属于局部混入,只能混入到指定组件中
  • 全局混入:

image.png

  • 执行结果

image.png

  • 一共四个组件,所以输入四次 mixin mounted

9. plugins配置(插件)

1. 插件定义

  • 给Vue做功能增强的
  • 怎么定义插件?以下上定义插件并暴露插件。插件是一个对象,对象中必须有install方法,这个方法会被自动调用

image.png

  • 插件一般都放在一个plugins.js文件中
  • 导入插件并使用插件:

image.png

2.插件对象的install方法两个参数

  • 第一个参数:Vue构造函数
  • 第二个参数:插件使用者传递的数据

10.局部样式scope

  • 默认情况下,在vue组件中定义的样式最终会汇总到一块,如果样式名一致,会导致冲突,冲突发生后,以后来加载的组件样式为准。怎么解决这个问题?

image.png

  • 另外 vue 组件的 style 样式支持多种样式语言,例如:css、less、sass 等。如何选择使用呢?

image.png

  • 使用less注意安装less-loader:npm i less-loader
  • App跟组件中的样式style不建议添加scoped

11. 组件自定义事件

1.关于内置事件都实现步骤

  • 第一步:提供事件源(以下这个按钮就是一个事件源)
  • 第二步:给事件绑定事件
    • v-on:事件名 或者 @事件名
  • 第三步:编写回调函数,将回调函数和事件进行绑定
  • 第四步:等待事件的触发,只要事件触发,则执行回调函数

2.关于组件的自定义事件,实现步骤:

  • 第一步:提供事件源(这个事件源上一个组件)
  • 第二步:给组件绑定事件
    • v-on:事件名 或者 @事件名
  • 第三步:编写回调函数,将回调函数和事件进行绑定
  • 第四步:等待事件的触发,只要事件触发,则执行回调函数
    • 对于组件自定义事件来说,要想让事件发生,需要去执行一段代码
    • 这段代码负责去触发这个事件,让这个事件发生
      • 这段代码写在哪里?
        • 事件绑定在A组件上,则触发这个事件都代码要在A组件中编写

3.解绑事件

  • 哪个组件绑定就找哪个组件解绑:
methods : { 
// 这种方式只能解绑一个事件。
unbinding(){ this.$off(‘event1’)  
// 这种方式解绑多个事件。 
this.$off([‘event1’, ‘event2’]) 
this.$off() // 解绑所有事件。 
    }
}
  • 注意;vm和vc销毁的时候,所有组件以及子组件当中的事件会全部解绑

4.总结:到目前为止,父子组件之间如何通信

  • 父 --> 子:props
  • 子 --> 父:
    • 第一种:在父中定义一个方法,将方法传递给子,然后在子中调用父传过来的方法,这样给父传数据。(这种方式以后很少使用)
    • 第二种:使用组件自定义事件的方式
      • App组件是父组件
      • User组件上子组件
      • 子组件向父组件传递数据(User给App组件传数据)
        • 在父组件绑定事件
        • 在子组件触发事件
      • 父绑定,子触发

4.对于事件都once修饰符来说,组件都自定义事件也是可以使用的

  • App部分代码
<div>
       <button @click.once="hello">内置事件的实现步骤</button>

        <!-- 给User组件绑定一个自定义的事件 -->
        <!-- <User v-on:event1.once="doSome"></User> -->
        <User v-on:event1="doSome" @event2="doOther"></User>

        <!-- 简写形式 -->
        <!-- <User @event1.once="doSome"></User> -->
        <User @event1="doSome" @event2="doOther"></User>

        <!-- 准备一个组件 -->
        <User ref="user"></User>

    </div>
// 组件自定义事件在mounted()函数触发
   mounted() {
            // 给ref="user"的组件绑定event1事件,并且给event1事件绑定一个回调函数:doSome
            this.$refs.user.$on('event1', this.doSome)

            // 如果回调函数是普通函数:函数体当中的this是User组件实例。不是App组件实例。
            /* this.$refs.user.$on('event1', function(){
                console.log(this)
            }) */

            // 如果回调函数是箭头函数:那么函数体当中的this就是App组件实例。
            /* this.$refs.user.$on('event1', () => {
                console.log(this)
            }) */

            this.$refs.user.$on('event2', this.doOther)
            // 保证事件只触发一次。
            //this.$refs.user.$once('event1', this.doSome)
        },
           methods: {
            hello(){
                console.log('hello vue!')
            },
            /* doSome(name, age, gender){
                console.log(name, age, gender)
            } */
            // ES6的语法,...params这个params可以看做是一个数组。以数组的形式接收多个参数。
            doSome(name, ...params){
                console.log(name, params)
            },
            doOther(){
                console.log('do other!')
            }
        },
  • User部分代码
<div>
        <button @click="triggerEvent1">触发event1事件</button>
        <button @click="triggerEvent2">触发event2事件</button>
        <button @click="unbinding">解绑事件</button>
        <button @click="goodbye">再见</button>
    </div>
methods: {
            triggerEvent1(){
                // 编写触发event1事件的代码
                // this是当前的组件实例:vc
                // 触发事件的同时,可以给事件绑定的回调函数传数据
                this.$emit('event1', this.name, this.age, this.gender)
            },
            triggerEvent2(){
                this.$emit('event2')
            },
            goodbye(){
                this.$destroy()
            },
            // 解绑事件
            unbinding(){
                // 仅仅解绑this指向的这个组件实例上的event1事件。
                //this.$off('event1')
                //this.$off(['event1', 'event2'])
                this.$off()
            }
        },
    }

12. 全局事件总线

1.原理

  • 给项目中所有组件找一个共享的vc对象。把这个共享的对象vc叫全局事件总线。所有的事件都可以绑定到这个共享对象上。
  • 所有组件都通过这个全局事件总线对象来传递数据。这种方式可以完美都完成兄弟组件传达数据.
  • 这样的共享对象必须具备两个特征:
    • 1.能够让所有的vc共享
    • 2.共享对象上有$on``$off``emit等方法
  • 第一种解决方案:
在 main.js 文件中:
// 获取 VueComponent 构造函数 c
onst VueComponentConstructor = Vue.extend({})
// 创建 vc const vc = new VueComponentConstructor() 
// 让所有的 vc 都能够使用这个
vc Vue.prototype.$bus = vc
  • 第二种解决方案:
在 main.js 文件中: 
new Vue({ el : '#app',
render : h => h(App), 
beforeCreate(){
Vue.prototype.$bus = this
    }
})
  • 永远记住的是:A组件向B组件传数据,在B组件中绑定事件(接).应该在A组件中触发事件(传)

image.png

  • 数据发送方:触发事件
methods : { triggerEvent(){ this.$bus.$emit(‘eventx’, 传数据) } }
  • 数据接收方:绑定事件
mounted(){ this.$bus.$on(‘eventx’, this.doSome) }
  • 养成好习惯:组件实例被销毁前,将绑定在$bus上事件解绑
beforeDestroy(){ this.$bus.off('eventx')}