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
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 的方式 放到该项目中去运行,会报如下错误
错误:你正在使用一个 运行版本 的 vue,没有模板解析器。也就是说引入的vue 是一个残缺的vue
两条解决方案:
-
使用rander 函数
-
引入完整版vue
项目中 引入vue使用的语句是 import Vue from 'vue' ,它引入的是 node_modules 目录下的vue
因为没有指定具体的文件,所以会去package.json 文件中去找到底引入哪个文件
因为使用的是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>
输出的三个分别是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值