Vue组件化编程

Vue组件化编程

组件的定义:用来实现局部(特定)功能效果的代码集合(html/css/js/image)

为什么使用组件:一个界面的功能很复杂

作用:复用代码,简化项目编码,提高运行效率

组件分为非单文件组件和单文件组件,在开发中一般使用单文件组件

非单文件组件

1.组件的基本用法

组件的使用步骤有三步:定义组件、注册组件、使用组件

<body>
<div id="root">
  <school></school>
  <hr/>
  <student></student>
</div>
<script>
  //创建组件
  const school = Vue.extend({
    template:`
    <div>
    <h2>学校名:{{schoolName}}</h2>
    <h2>学校地址:{{address}}</h2>
    </div>
    `,
    data(){
      return{
        schoolName:"sdgsxy",
        address:"yantai"
      }
    }
  })
  const student = Vue.extend({
    template:`
    <div>
    <h2>学生名:{{studentName}}</h2>
    <h2>学生年龄:{{age}}</h2>
    </div>
    `,
    data(){
      return{
        studentName:"sdgsxy",
        age:18
      }
    }
  })
​
  new Vue({
    el:"#root",
    components:{
      school:school,
      student:student
    }
  })
</script>
</body>

1.定义组件

使用Vue.extend(options) 创建组件,其中 options 和 new Vue 时传入的options 几乎一样,区别点:

  • 但是不能写 el配置,因为最终所有组件都要经过一个vm的管理,由vm中的el决定服务于哪个容器

  • data 必须写成函数,为了避免组件被复用时,数据存在引用关系

  • 需要使用 template 配置组件结构

2.注册组件

在vm中配置 components 对象,用键值对的方式注册组件,值就是定义组件时的组件名,键是使用组件时用的名字

3.使用组件

通过注册组件时 定义的名字 作为标签

 

在注册组件时,还可以进行全局注册

//Vue.component('school',school)

 

注意点:

  • 关于组件名

    • 一个单词组成

      • 首字母小写 (school)

      • 首字母大写(School)

    • 多个单词组成

      • kabab-case命名(my-school)

      • CamelCase 命名 (MySchool) 需要脚手架支持

    组件名尽可能回避HTML中已有的元素名称,例如 h2、h3等

    可以使用name配置项指定组件在vue开发者工具中的名字

  • 关于组件标签

    • 第一种写法 <school></school>

    • 第二种写法 <school/>

      没有使用脚手架的话,第二种写法会导致后续组件无法渲染

  • 定义组件时可以简写

    const school = Vue.extend(options) ==> const school = options

 

2.组件的嵌套

<body>
<div id="root">
  <app></app>
  <hr/>
</div>
<script>
  //创建组件
  const student = Vue.extend({
      template:`
    <div>
    <h2>学生名:{{studentName}}</h2>
    <h2>学生年龄:{{age}}</h2>
    </div>
    `,
      data(){
          return{
              studentName:"sdgsxy",
              age:18
          }
      }
  })
  const school = Vue.extend({
    template:`
    <div>
    <h2>学校名:{{schoolName}}</h2>
    <h2>学校地址:{{address}}</h2>
      <student></student>
    </div>
    `,
    data(){
      return{
        schoolName:"sdgsxy",
        address:"yantai"
      }
    },
    components:{
        student
    }
  })
  const hello = Vue.extend({
      template:`
        <div>
            <h1>欢迎访问</h1>
        </div>
      `
  })
  const app = Vue.extend({
      template:`
        <div>
        <school></school>
        <hello></hello>
        </div>
      `,
      components:{
          school,
          hello
      }
  })
  new Vue({
    el:"#root",
    components:{
      app
    }
  })
</script>
</body>

组件嵌套就是把子组件的注册和使用 拿到父组件中

在实际开发中一般在 vm中只注册一个 app组件,其他组件都是它的子组件

 

3.VueComponent

  • school组件本质是一个名为 VueComponent的构造函数,且不是程序员定义的,时Vue.extend生成的

  • 我们只需要写<school></school> ,vue 解析时会帮我们创建school组件的实例对象

    即Vue帮我们执行的 new VueComponent(options)

  • 需要注意的是每次调用 Vue.extend 返回的都是一个全新的VueComponent

  • 关于this的指向

    • 组件中

      • data、method、watch、computed中的函数,他们的this均是VueCompontent实例对象

    • new Vue(options) 中

      • data、method、watch、computed中的函数,他们的this均是Vue实例对象

  • VueComponent 的实例对象,以后简称vc,vue实例对象简称vm

 

Vue和VueComponent的关系

VueComponent和Vue的关系:

VueComponent 的原型对象 的 原型对象 就是 Vue的原型对象

为什么要有这个关系?

为了让组件实例对象可以访问到Vue原型上的方法和属性

 

单文件组件

单文件组件是一个个的 vue文件,浏览器不能直接处理 vue文件,需要借助 webpack或脚手架

在vue文件中,主要写三个标签

  • template 里面写组件的结构

  • script 里面写组件交互相关代码

  • style 组件的样式

需要注意 template 不参与渲染,所以在 该标签中需要有一个根标签

<template>
    <div class="demo">
        <h2>学校名称:{{schoolName}}</h2>
        <h2>地址:{{address}}</h2>
    </div>
</template>
​
<script>
    const school = Vue.extend({
        data(){
            return{
                schoolName:"sdgsxy",
                address:"yantai"
            }
        }
    })
</script>
​
<style>
  .demo{
    background-color: orange;
  }
</style>

三个标签写完后,需要对 script 标签的内容进行 暴露,然后别的文件才能引入该文件使用

使用 默认暴露,并且可以 省略 Vue.extend

<script>
    export default{
        name:'School',
        data(){
            return{
                schoolName:"sdgsxy",
                address:"yantai"
            }
        }
    }
</script>

定义好自己的组件后 一定要写一个 App.vue 来引入自己的组件

<template>
    <div>
        <!-- 使用组件 -->
        <School/>
        <Student/>
    </div>
​
</template>
​
<script>
    //引入组件
    import School from './School.vue'
    import Student from './Student.vue'
   
    export default {
        name:'App',
        //注册引入的组件
        components:{
            School,
            Student
        }
    }
</script>

 

有了三个组件 App.vue、School.vue、Student.vue,需要用vm去使用他们,所以还需要一个 js文件去创建vm

import App from './App.vue'
​
new Vue({
    el:'#root',
    template:`<App/>`,
    components:{
        App
    }
})

有了 vm 还缺一个 容器 root,所以还需要创建一个 html文件作为容器

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>测试单文件组件</title>
    
</head>
<body>
    <div id="#root"></div>
    <!-- 引入vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <!-- 引入 main.js -->
    <script type="text/javascript" src="./main.js"></script>
</body>
</html>

 

关于vue脚手架

需要安装node.js

执行 npm install -g @vue/cli 安装最新的脚手架

创建一个vue项目

  • vue create xxxx(xxxx是项目名)

  • npm run serve 启动项目,脚手架版本为2.9.6的话命令是 npm run dev

 

分析 vue项目

  • babel.config.js ES6转 ES5 的配置文件

  • package.json 配置包名、版本、依赖等,常用命令也写在该文件中

  • package-lock.json 包版本控制文件

  • src

    • main.js 网页的入口,执行完 npm run serve 后直接执行main.js

    • App.vue 所有组件的父组件

    • assets 该目录存放网页的静态资源

    • components 该目录存放所有子组件

  • public

    • favicon.ico 网页页签图标

    • index.html 网页的界面

render函数

通过vue-cli构建的项目中的main.js 和 自己写的main.js 的区别

import Vue from 'vue'      
import App from './App.vue'          

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
import App from './App.vue'

new Vue({
    el:'#root',
    template:`<App/>`,
    components:{
        App
    }
})

可以很明显地看到 这两个文件对于 App组件的注册以及模板的构建采取了两种不同的方式

如果把我们写main.js 的方式 放到该项目中去运行,会报如下错误

image-20240624184328604

错误:你正在使用一个 运行版本 的 vue,没有模板解析器。也就是说引入的vue 是一个残缺的vue

两条解决方案:

  • 使用rander 函数

  • 引入完整版vue

项目中 引入vue使用的语句是 import Vue from 'vue' ,它引入的是 node_modules 目录下的vue

image-20240624185301974

因为没有指定具体的文件,所以会去package.json 文件中去找到底引入哪个文件

image-20240624185430193

因为使用的是vue 模块化引入 所以到底引入哪个文件是由 module 来控制的,由此可见 引入的是 dist/vue.runtime.esm.js 它就是 残缺版的vue

完整版 vue 是在 dist下的 vue.js,这是第二种解决方案

第一种解决方案是使用rander函数,他的完整版写法如下

new Vue({
  //render: h => h(App),
  // template:`<App/>`,
  // components:{
  //   App
  // }
  render(createElement){
      return createElement('h1',"你好啊")
  },
  //template:'<h1>你好啊</h1>'
}).$mount('#app')

render接收一个参数,这个参数是 函数,这个函数接收的参数是 界面元素,这里是 标签名和内容

把render 简写之后

render:q => q('h1',"你好啊")

由于 h1是html 的内置元素,所以需要传两个参数,如果参数是组件那么直接传入就行

 

那么为什么要设计和引入 残缺版的vue呢?

Vue 中包含 Vue的核心 + 模板解析器,如果Vue不创造精简版Vue,那么webpack打包后生成的文件里就包含模板解析器。这时它出现在这就不太合适了

 

使用vue.config.js 可以对脚手架进行个性化配置

 

ref属性

  • 作用:被用来给元素或子组件注册引用信息

  • 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象

  • 使用方式:

    • 打标识:<h1 ref="xxx"></h1> <School ref="xxx"></School>

    • 获取:this.$refs.xxx

<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点击输出</button>
    <School ref="sch"></School>
  </div>
</template>

<script>
import School from "./components/School.vue";
export default {
  name:'App',
  // eslint-disable-next-line vue/no-unused-components
  components:{School},
  data(){
    return{
      msg:"你好欢迎"
    }
  },
  methods:{
    showDOM(){
      console.log(this.$refs.title)
      console.log(this.$refs.btn)
      console.log(this.$refs.sch)
    }
  }
}
</script>

image-20240624201645877

输出的三个分别是DOM、DOM、组件实例对象

 

props 配置项

之前向页面展示数据都是把数据写到 data函数中如果想要更改数据,就要修改data里面的数据。但我们知道 组件的一大特点就是能够复用,如果别人想要复用你的组件,但是想展示的数据不同。那么该如何解决呢?

这时就可以用props配置项来接收参数,传递参数的地方就是 使用该组件的地方

<template>
  <div>
    <h1>{{msg}}</h1>
    <Student name="李四" age="80"></Student>
  </div>
</template>

传递方在组件使用时 通过属性传递参数 ,这些属性也可以用v-bind 绑定

<template>
  <div>
    <h2>学生姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="addAge">点击增加年龄</button>
  </div>
</template>
<script>
export default {
    /* eslint-disable */
    name:"Student",
    data(){
        return{
            //name:'张三',
            //age:18,
        }  
    },
    methods:{
        addAge(){
            this.age++
        }
    },
    props:['name','age']
}
</script>

接收方 通过 props 接收参数,需要注意的是 在data中不能再把数据写死

props是一个数组,里面的值就是 传入的参数名,顺序无所谓

props:['name','age'] 这种方式 是 简单接收,还可以对传入的参数进行限制

props:{
	name:String,
	age:Number
}

写成对象的形式,限制传入参数的类型

还可以限制的更详细一些

props:{
    name:{
        type:String,
        required:true
    },
    age:{
        type:Number,
        default:18
    }
}

其中 type 是对类型的限制,required限制必须传入,default设置默认值

 

注意点: 接收到的props 是不能修改的,如果业务需求要改,可以在data里设置一个属性,并把接收到的参数赋值给他,然后修改data中的值就可以了

data中的属性和 props 中的属性 如果相同,props的优先级更高

data(){
    return{
        //name:'张三',
        //age:18,
        myAge: this.age
    }  
},
methods:{
    addAge(){
    	this.myAge++
    }
},

 

mixin 混入

功能:把多个组件共用的配置提取成一个混入对象

在js文件中定义 混入,里面可以包含 组件的配置项 如data、methods等

export const hunhe = {
    data(){
        return{
            x:100,
            y:200
        }
    },
    methods:{
        showName(){
            alert(this.name)
        }
    }
}

在组件中使用时,先引入js文件,再通过配置项mixins 引用定义的混入

<script>
import { hunhe } from '../mixin'

export default {
    /* eslint-disable */
    name:"Student",
    data(){
        return{
            name:'张三',
            age: 18
        }  
    },
    mixins:[hunhe]
}
</script>

mixins配置项是一个 数组 可以引入多个混入

如果混入中的配置项和组件中的配置项冲突了,比如都在data定义了一个x,那么x的值是组件中定义的值

如果是钩子函数冲突,那么这两个钩子函数都会执行

上面那种引入是局部引入,还有全局引入的方式

在main.js 中 先引入 混入,在调用Vue的mixin方法,传入混入即可

这时所有的组件中都有混入的元素了

 

插件

功能:增强vue

本质:包含install方法的一个对象,install的第一个参数是Vue,后面的参数是使用者传递的数据

定义:在 js 文件中,里面可以添加全局过滤器、添加全局指令、配置全局混入、添加实例方法

export default{
    install(Vue){
        //定义全局过滤器
        Vue.filter('mySlice',function(value){
            return value.slice(0,4)
        })

        //定义全局指令
        Vue.directive('fbind',{
            bind(element, binding){
                element.value = binding.value
            },
            inserted(element, binding){
                element.focus()
            },
            update(element, binding){
                element.value = binding.value
            }
        })

        //给Vue原型添加一个方法
        Vue.prototype.hello = ()=>{
            alert(11111)
        }
    }
}

使用:在main.js 中 引入该文件

调用Vue.use() 使用该插件

 

scoped 样式

作用:让样式在局部生效,防止冲突

写法:<style scoped>

 

组件化编码流程

1.实现静态组件:按功能点拆分,命名不要和html冲突

2.实现动态组件:考虑好数据的存放位置

  • 一个组件在用:放在自身组件即可

  • 一些组件在用:放在他们共同的父组件

3.实现交互

 

props适用于:

  • 父组件向子组件传递,通过在子组件的标签中传递

  • 子组件向父组件传递,需要父组件先传给子组件一个函数,子组件调用这个函数,传入父组件需要的值

v-model 绑定的值不能是 props传过来的值,因为props中的值不可以修改

 

webStorage

1.存储内容大小一般支持5MB左右,视浏览器而定

2.浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性实现本地存储机制

3.相关API

  • xxxxStorage.setItem('key','value) 设置键值对进行存储

  • xxxxStorage.getItem('key') 传入键名,返回键名对应的值

  • xxxxStorage.deleteItem('key') 传入键名,删除该项

  • xxxxStorage.clear() 清楚所有数据

 

4.sessionStorage 存储的内容会随着浏览器窗口关闭而消失

localStorage 需要手动清楚才会消失

 

 

自定义事件

之前从子组件向父组件传递数据时是通过 props,父组件先向子组件传递一个函数,子组件调用这个函数,并传递数据

利用自定义事件也可以实现子组件向父组件传递数据

父组件在 子组件标签处绑定一个自定义事件,用 v-on,值可以是一个函数,当子组件触发这个事件时,就会调用这个函数

在子组件中触发这个自定义事件使用 this.$emit,第一个参数是自定义事件的名字,后面的参数是要传的值

<template>
  <div>
    <h1>{{msg}}</h1>
    <School @getSomething="getSchoolName"></School>
    <hr/>
    <Student></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
    name:'App',
    components:{Student,School},
    data(){
        return{
            msg:"欢迎!!!"    
        } 
    },
    methods:{
      getSchoolName(name){
        console.log('学校名:' + name)
      }
    }
}
</script>

这里的自定义事件是 getSomething,触发该事件后执行 getSchoolName 函数

<template>
  <div>
    <h2>学校姓名:{{name}}</h2>
    <h2>地址:{{address}}</h2>
    <button @click="senSchoolName">点击发送学校名称</button>
  </div>
</template>

<script>

export default {
    /* eslint-disable */
    name:"School",
    data(){
        return{
            name:'sdgsyx',
            address:"yantai"
        }  
    },
    methods:{
      senSchoolName(){
        this.$emit('getSomething',this.name)
      }
    }
}
</script>

 

这是自定义事件的第一种写法,除此之外还有第二种

给子组件 绑定 ref属性,然后在钩子函数mounted中,定义自定义事件

<template>
	...
    <Student ref="student"></Student>
  	...
</template>

<script>
...
export default {
   	...
    methods:{
     	...
      getStudentName(name){
        console.log('学生名:' + name)
      },
    },
    mounted(){
      this.$refs.student.$on('getStudent',this.getStudentName)
    }
}
</script>

在mounted函数中。先用 this.$refs.student 获取到子组件的组件实例对象,然后调用 $on,第一个参数是自定义事件的名字,第二个是回调函数

子组件中还是用 this.$emit('getStudent',this.name) 去触发自定义事件

 

第二种方式比第一种方式更加灵活,可以进行延时绑定

组件上也可以绑定原生DOM事件,需要用native修饰符

 

自定义事件解绑

调用 this.$off() 参数是 自定义事件名

传入一个自定义事件名是解绑一个自定义事件

传入一个数组是解绑多个自定义事件

什么都不传就是把所有自定义事件都解绑

当组件实例对象被销毁后,组件绑定的自定义事件也就被销毁了

 

全局事件总线

1.是一种组件间通信的方式,适用于任意组件间通信

2.安装全局事件总线

在main.js中给 vue的原型对象上绑定一个属性,这个属性能够被所有的组件看到,并且它有绑定和解绑的函数($on、$off、$emit),那么这个属性的值就是vue的实例对象。

绑定这个属性的时机就是在创建vue实例中,调用 beforeCreate 函数,确保该属性能够在模板被渲染之前被绑定

new Vue({
    el:"#app",
    render:h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    },
})

在要接收的组件中,绑定一个自定义事件,调用 $on,在beforeDestroy中解绑

mounted() {
  this.$bus.$on('sendEvent',(data)=>{
    console.log("我是School,我收到了:" + data)
  })
},
beforeDestroy() {
	this.$bus.$off('sendEvent')
}

在要发送的组件中触发该自定义事件,调用 $emit

methods:{
  sendStudentName(){
    this.$bus.$emit('sendEvent',this.name)
  }
}

 

 

消息订阅与发布(pubsub)

1.一种组件间通信的方式,适用于任意组件间通信

2.使用步骤

  • 安装pubsub,它是一个js库 npm i pubsub-js

  • 引入 import pubsub from 'pubsub-js'

  • 在接收消息方,订阅消息

    methods(){
    	demo(msgName,data){...}
    },
    mounted(){
    	this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
    },
    beforeDestroy(){
        pubsub.unsubscribe(this.pid)
    }

    注意:在回调函数中,第一个参数是消息名,第二个是接收的数据,最好在beforeDestroy去取消订阅

  • 在发送消息方,发布消息

    methods(){
    	send(){
            pubsub.publish("xxx",data)
        }
    },

     

nextTick

作用:在下一次DOM 结束后执行指定的回调

handleUpdate(){
    this.showInput = true
    this.$nextTick(function(){
      this.$refs.input.focus()
    })     
},

在handleUpdate 函数中,当数据改变后,vue并不会立即去重新解析模板,而是会继续执行函数,等到函数执行完后才去重新解析模板,如果不加 $nextTick, 让input获取焦点的时候,input输入框还没有被渲染到模板上,所以会没有效果

使用 $nextTick,vue解析完模板后,再执行里面的回调函数

 

Vue 封装的过渡与动画

1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名

2.

3.写法:

  • 准备好样式

    • 元素进入时的样式:

      • v-enter:进入的起点

      • v-enter-active:进入过程中

      • v-enter-to:进入的终点

    • 元素离开时的样式:

      • v-leave:离开的起点

      • v-leave-active:离开过程中

      • v-leave-to:离开的终点

  • 使用 <transition> 包裹要过渡的元素,并配置name属性

    <transition name="hello">
    	<h1 v-show="isShow">
            你好!
        </h1>
    </transition>
  • 备注:如果有多个元素需要过渡,则需要使用 <transition-group> 且每个元素都要指定key值

posted @ 2024-07-01 19:20  GrowthRoad  阅读(17)  评论(0编辑  收藏  举报