文章目录
- Vue 组件化编程 - .vue文件
- 非单文件组件
- 组件的注意点
- 组件嵌套
- Vue实例对象和VueComponent实例对象
- Js对象原型与原型链
- Vue与VueComponent的重要内置关系
- 应用单文件组件构建
- Vue脚手架 - vue.cli
- 项目文件结构
- 组件相关高级属性
- 引用名 - ref
- 数据接入 - props
- 混入 - mixin
- 插槽 - slot
- 默认插槽
- 具名插槽
- 插件
- 存储
- 浏览器存储 - Window.sessionStorage与 Window.localStorage
- Vuex - 共享状态管理
- Vuex工作原理
- 搭建Vuex环境
- vuex - store使用案例
- vuex开发者工具
- getters
- 4个map方法
- vuex模块化 - namespace
- 组件事件
- 触发自定义事件 - emit()
- 解绑自定义事件 - off() / destroy()
- 自定义事件注意事项 - this指向与native
- 全局事件总线 - 实现任意组件间通信
- 消息订阅与发布模型 - 基于pubsub实现
- 事件触发更新 - nextTick
- 过度与动画
- 配置代理解决跨域问题
- 什么是跨域
- 出现跨域的原因
- Vue对于跨域问题的解决 - 配置代理
- 方式一
- 方式二
Vue 组件化编程 - .vue文件
组件的定义——实现应用中局部功能代码和资源的集合。
非单文件组件
一个组件文件中由多个其他组件构成,称为非单文件组件。
Vue使用组件的三大步骤:
- 1、定义组件(创建组件)
- 2、注册组件(Vue的
components
局部注册或者使用Vue.component(‘组件名’, 组件))全局注册。 - 3、使用组件(写组件标签)
1、创建组件
创建组件时,需要注意:
- 不需要写
el
标签:el标签是将Vue实例与DOM元素绑定的,当你创建组件时,你并不知道需要将其用在哪里,因此不需要写el
标签。 - 组件的
data
属性需要写成函数式的:因为一个组件可能在多个地方使用,如果写为对象式的,各个组件之间的数据变化会相互影响。 - 使用
Vue.extend({})
创建组件,在template
属性中,使用反引号包裹HTML标签。
2、注册组件
局部注册:在Vue实例的components
属性中,标注上构建的组件。
使用`Vue.componet(‘组件名’, 组件);可以注册全局组件,即所有的Vue实例,尽管在components属性中没声明该组件,也可直接使用。
3、使用组件,写组件标签。
组件的注意点
组件嵌套
组件可以嵌套使用。注册给谁的组件,就可以在它的template
属性中应用这个组件标签。
Vue实例对象和VueComponent实例对象
以定义的school组件为例:
- 1、当我们使用school标签时,本质上是调用了
VueComponent
构造函数,该构造函数是Vue.extend
生成的。 - 2、当使用school标签时,Vue框架解析时会帮助创建school组件的实例对象,方式就是执行
new VueComponent(options)
- 3、特别注意,每次调用
Vue.extend
,返回的都有一个全新的VueComponent对象。 - 4、关于
this
指向,组件配置中:data函数、methods中的函数,watch、computed函数,它们的this均是VueComponent实例对象
。而在new Vue(options)配置中,data函数、methods中的函数,watch、computed函数,它们的this均是Vue实例对象
- 5、根组件Vue实例对象会有
$children
属性,其中就是VueComponent实例对象
数组。 - 6、Vue实例对象和VueComponent实例对象在创建时接收的options选项,除了Vue实例可以使用
el
绑定具体的DOM标签外,其余都一样。
Js对象原型与原型链
JavaScript 是一种基于原型的语言 (prototype-based language),每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。
每个Js对象都会有一个名为__proto__
的内部属性,该属性指向当前对象的原型对象。在Js中,当我们访问一个对象的某个属性时,如果该对象没有这个属性,Js语法引擎就会沿着该对象的原型链(在对象的__proto__
上找)向上查找,直到找到该属性为止。在查找对象属性时,通过__proto__
查找原型的属性这个过程,就用到原型链。
当然在Js对象中,根原型为Object,而Object的__proto__
是为null的。
原型属性又可分为显示原型属性
和隐式原型属性
。
- 显示原型属性访问:
构造函数名.prototype
- 隐式原型属性访问:
实例对象名.__proto__
- 由这两种方式访问到的原型对象是等价的,即指向的是同一个原型对象。
构造函数名.prototype
===实例对象名.__proto__
- 由原型链的属性访问方式可知,如果给原型对象添加一个属性x = 99,那么可以由实例对象d的原型对象属性访问它,即
d.__proto__.x
,实际上对于__proto__
的访问是Js执行引擎自动完成的,因此d.__proto__.x
访问的方式,可以等价于使用d.x
Vue与VueComponent的重要内置关系
VueComponent(Vue组件)的原型对象的原型对象是Vue的原型对象
应用单文件组件构建
1、构建一个单文件组件
一个单.vue文件,基本结构如下;
定义好单文件组件是为了提供给 别的文件使用,因此需要使用export
将该文件进行暴露。
export default {
……
}
实际上是一种简写,等价于:
const vc = Vue.extend(
{
……
}
)
export vc
2、入口Vue文件:App.vue
一般而言,Vue项目的根组件为App.vue,这里使用其他的组件。
组件化编程三步走:
- 导入
- 注册
- 使用标签
3、入口js文件:main.js
构建的根组件App.vue并没有使用,在main.js中构建App.vue的实例对象,使用el
属性为其绑定DOM对象。
4、入口HTML文件:index.html
Vue脚手架 - vue.cli
项目文件结构
组件相关高级属性
引用名 - ref
ref属性被用来给元素或者子组件注册引用信息,类似id,但是id只能绑定DOM元素标签,而ref还可以绑定vue组件,相当于给组件或DOM元素标签起一个别名,便于在程序中引用,程序中可以通过this.$refs.xxx
获取到Vue组件或DOM元素标签。
数据接入 - props
组件中的数据,在复用时可能各自不同,props属性用于给组件传递数据。
基本使用方法:
step1:在组件的props属性配置数据项的名称接收。
step2:在使用组件标签时传入数据项的数据值。
实际上props数据接收,有三种不同的方式:
- 简单接收
- 限制类型
- 限制类型、必要性、并指定默认值
注意:props中指定数据项名称可能会与Vue组件中的data属性中的数据项名称产生冲突,从优先级上,是props优先级会更高一点。
一般而言,props承接的数据,我们希望将其转为Vue组件的data数据项的数据,那么根据优先级关系,可以将props属性传给data属性。
将props承接的数据传给data,另一方面是由于组件在使用过程中数据可能会更新,而props属性项数据是不允许更改的,只有组件自身的data属性项可更改
混入 - mixin
mixin:混入,可以把多个组件的共用的options配置,抽取出一个混入对象mixin,节约配置。
使用方式:
step1:定义混入:
例如:
{
data() : {……}
methods: {……}
……
}
上述这些公共配置,写成一个js代码文件:mixin.js,然后暴露。
step2:使用混入,例如:
- 全局混入:Vue.mixin(xxx)
- 局部混入:使用mixins属性,mixins: [‘xxx’]
关于mixins属性需要注意:
- 1、如果混入mixin中定义的data属性项名与组件中的已经有的data名重名,则只保留组件已经有的data名。
- 2、mounted()等生命周期方法中的操作,则会与组件已有的内容合并。
插槽 - slot
插槽的提出是为了实现合成组件,假定一个组件中大部分内容是固定的,仅有几个地方是需要变化更改的,我们就可以用插槽来代替这部分需要变化的,然后根据需要写相应的插槽组件
Slot的通俗理解是“占坑”,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中位置),当插槽也就是坑<slot name= ” mySlot ” >有命名时,组件标签中使用属性slot= ” mySlot ” 的元素就会替换该对应位置内容;
默认插槽
下边这个需求,大致上页面都是标题为某个内容的分类,内容呈现上有一些不同,这些不同就可以使用插槽进行抽象占位,那么整个页面就可以使用一个组件来复用。
step1:在每个分类内容组件Catetory中编写相应的DOM结构。
step2:Catetory中编写相应的DOM结构会放在Catetory的slot
占位处,相当于将对应的DOM进行了替换。
具名插槽
当一个父组件需要组装多个动态组件时,就需要多个插槽,使用具名插槽用于区分。
需求:在内容底部,还有一部分超链接。那么就相当于是两部分动态内容,需要安置两个插槽。
所谓具名插槽就是在使用插值挖坑占位时,给坑起个名字,好方便后续根据名字来填坑。
step1:挖坑时,使用name属性,给插槽起名。
step2:填坑时,使用slot
属性,绑定name,进行结构替换。
注意:下边对于slot的绑定使用了两种方式:
- 1、美食与游戏组件都是使用
slot = "name"
进行绑定 - 2、电影里边,使用template标签 +
v-slot:name
进行了绑定。
上边两种方式都可以,只不过 v-slot:name
必须与template结合使用,template这一层标签在渲染时是不存的,仅作间隔占位用。
插件
插件的功能是用于对Vue进行增强。
使用步骤:
step1:
定义plugins.js文件,定义install函数,表示是一个可安装的插件,在其中一般可以定义用于增强Vue的:
- 1、全局过滤器
- 2、全局自定义指令
- 3、全局混入
- 4、Vue原型方法
step2:在main.js中,导入plugins.js并使用Vue.use()
。
step3:在Vue中使用定义的增强功能,比如上边在plugins.js中定义了Vue的原型方法hello
,那么后边就可直接使用了。
存储
浏览器存储 - Window.sessionStorage与 Window.localStorage
Vuex - 共享状态管理
Vuex是专门在Vue中实现集中式状态(数据)管理的Vue插件,对Vue应用中
多个组件的共享状态进行集中式的管理(读/写)
,也是一种组件间通信的方式,且适用于任意组件间的通信。
实际上,组件间通信也可以使用全局事件总线或者消息订阅 / 发布模型来实现。
全局事件总线与消息订阅,对于共享状态的处理不足之处在于组件之间的读和写,分别需要配套的绑定与触发,即一套配对的绑定与触发,只能完成数据的在组件之间的单向传递。
使用Vuex就是为了解决共享数据双向传递的问题。
共享数据状态存入Vuex中集中管理,Vuex组件与其他组件间是双向数据传递的。
Vuex工作原理
Vuex的工作原理如下图所示:
几个核心概念:
- State:一个对象,提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,与data类似。
- Actions:一个对象,组件dispatch(派遣)时间动作类型与参数,当然参数也可以来自后端的api,封装为一个action,在Actions对象上就会出现一个属性键值对,属性名为动作名,属性值为该动作对应的函数回调。
- Mutations:一个对象,mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。Mutations看起来与Actions极其相似,以致于Actions看起来多余,实际上,Actions存在的目的就是为了拆分事件回调动作的构成步骤,因为该动作回调的参数可能来自于后端接口,需要分步构建,甚至可以在Action中再次通过dispatch函数,构建action。如果不需要后端介入动作构成或者分步构建,VueComponents也是可以直接绑定动作到Mutations上的,即可以在组件中直接使用commit,跳过actions!!!
上边都是在讲概念,实际开发时,上边三个对象都是vuex中store的属性对象,由store来管理
搭建Vuex环境
step1:安装vuex
如果Vue2,需要安装vuex3
npm i vuex@3
如果Vue3,直接默认安装vuex4
npm i vuex
step2:vuex本质上是一个插件,需要在main.js中引入并use。
step3:构建store
官方推荐的写法,在脚手架的src下,新建store文件夹,在该文件夹下,新建index.js文件,构建Store对象,配置核心的actions、mutations、state。
step4:在main.js构建Vue实例时,将store选项加入:
vuex - store使用案例
template:
store:
配置actions函数和mutations函数。
actions中使用commit,将动作传递到mutations,而mutations中的函数,会真正引起state的改名
组件中通过dispatch()函数,发起action。
在vuex工作原理部分也讲过,vue组件可以直接commit,跳过Action直接调用Mutations:
vuex开发者工具
vue的devtool是兼容vuex开发者工具的,不需要额外再下载插件。
开发者工具这里方便监测共享数据状态的变化。
getters
当state中的数据需要经过加工后再使用时,可以使用getters加工,state与getters之间的关系就类似于data与computed
定义在store/index.js
:
使用:
getters与state一样,是$store
的一个属性。
4个map方法
共享的state和getters常常需要在别的组件中进行数据展示。
为了方便页面上的编写,多个state或者getters,它们共有的公共前缀$store.state.
或$state.getters.
,可以放在组件的计算属性中:
计算属性方便了页面template的编写,但是这些计算属性的生成能不能更简单一些呢?这时,就需要vuex提供的map方法了。
- 1、mapState方法:用于帮助我们映射
state
中的数据为计算属性 - 2、mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性
借助这两个map方法可以很方便的帮助我们生成计算属性:
上图中mapState与mapGetters其实本质上是一样的,都是从$store.state.
或$state.getters.
取出对应名字的state或者getters属性,然后将其转为计算属性。
注意:
(1)…语法。
… + 对象,可以将对象中的每一组key-value展开。
let obj2 = {x:100, y:200}
let obj = {
a:1,
...obj2, // ...展开对象属性
b:2
}
上边写法obj等价:
let obj = {
a:1,
x:100,
y:200,
b:2
}
(2)mapState(),会将输入的每一对k-v,转为对应的函数,因此mapState({he:'sum', xuexiao:'school', xueke: 'subject'})
输出的是{he() {}, xuexiao() {}, subject() {}}
再使用...
将对象展开,就自然成为了计算属性中的方法。
(3)当计算属性名与state或者getters中的属性同名时,可以使用数组写法,更加简洁。
- 3、mapActions方法,用于帮助我们生成与
actions
对话的方法,即包含:$store.dispatch(xxx)
的函数 - 4、mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即包含:$store.commit(xxx)
的函数
需要注意的是:mapActions与mapMutations使用时,若需要传递参数,在template模板中绑定方法时就将参数传递好,否则默认的参数是事件对象
vuex模块化 - namespace
实际开发中,针对不同的业务,有不同的共享数据状态需要管理,使用模块化 + namespace命名空间的方式,可以让代码更加维护,业务数据分类更加明确,且各个业务之间的数据名称也不会产生冲突。
step1:
在store文件夹下,根据业务类型,编写相应的vuex相关js文件,并暴露,在index.js中创建store时进行模块化管理。
在编写单个store的module配置时,除了actions、mutations、state、getters等的配置,一定要添加namespaced:true,这个属性默认为false,只有为true时,store中对于该module里边的配置才是可见的,否则在store中只能看到module名。
在index.js引入刚刚配置的store模块,在创建Store实例时,使用modules属性,指定模块名和实例。
step2:
在组件中具体使用时,需要注意模块化的写法。
不借助map方法的模块化使用写法:需要注意state和其他类型引入的不同。
注意:模块名 + ‘/’ + 属性名,不可直接使用.
来引用,因此下边使用了方括号[]
的方式,避免了.
后的内容有/
。
借助map方法的模块化使用写法:只需要map()方法的第一个参数传入模块名即可。
组件事件
触发自定义事件 - emit()
以子组件向父组件传递数据为例:
如果不用自定义事件,可以通过父组件给子组件传递函数类型的props实现
step1:给子组件School绑定getSchoolName函数,相当于子组件的School的组件实例会有getSchoolName()这个函数。
step2:用props承接父组件注册的getSchoolName。按钮注册点击事件,调用该函数,并将组件的name参数传递。
如果使用自定义事件来实现:
step1:通过v-on: 给子组件Student绑定自定义事件atguigu
,该事件触发时,调用父组件的getStudentName()方法。
step2:使用this.$emit('atguigu', this.name)
触发子组件Student上的atguigu事件。
除了在子组件标签上使用v-on绑定,也可以通过代码实现:
先给子组件标签起一个引用名,随后在mounted()方法中,拿到该组件,给它绑定事件方法。
如果要保证事件只触发一次:
可以使用:
解绑自定义事件 - off() / destroy()
- 1、
this.$off()
:如果指定参数,则 解绑对应事件(参数指定的),如果不指定参数,默认解绑所有的事件。 - 2、在Vue生命周期中讲过,当组件销毁时,其上绑定的自定义事件以及子组件实例都会被销毁,因此使用
this.$destroy()
生命周期钩子函数,也可以实现自定义事件的解绑。
自定义事件注意事项 - this指向与native
- 1、注意自定义事件触发时的this指向,this可能指向的是事件的产生者,即子组件。
如果想this指向父组件,即该mounted声明周期的组件,要解决这个问题,有两种方式。
(1)使用箭头函数替换,function函数,箭头函数没有this指针,向外寻找mounted()函数的this,就是父组件了。
(2)将function的内容写为父组件的一个methods方法,那么this指向父组件。
- 2、给组件绑定事件,默认绑定的是自定义事件,即便事件名与内置事件如
click、keyup
等同名。如果想为其绑定内置事件,应使用.native
指明。
全局事件总线 - 实现任意组件间通信
如下图事件总线X,是各个组件之间的中介者,如果要实现组件A和D之间通信,可以在A中给X绑定一个自定义事件,那么X中的自定义事件触发时,就会触发A中的回调。此时在D组件中编写触发X该自定义事件的逻辑,并携带参数,那么就实现了D组件向A组件通信。
X应该具备两个特点:
- 1、所有其他组件都可见
- 2、实现
$on、$off、$emit
函数
$on、$off、$emit
函数都是Vue实例或者组件上有的函数,因此上边两个特点,综合一句话就是X是一个所有其他组件都可见的Vue实例或者组件
为了使用所有其他组件都可见,可将X安装在Vue的原型对象上。
X本身可以是Vue实例,不妨直接使用Vue实例本身。
因此一般的事件总线安装方法如下:
在main.js创建Vue实例时,beforeCreate()中,给Vue的原型对象设置$bus = this
.
后续使用时,在mounted()钩子处绑定,在beforeDestroy()钩子处解绑。
消息订阅与发布模型 - 基于pubsub实现
消息订阅与发布模型是一种理念,这里我们使用pubsub - js
库提供的消息订阅与发布函数。
step 1:使用npm下载pubsub - js
npm i pubsub-js
step 2 : 消息订阅
接收数据的组件,使用pubsub订阅消息,指定消息主题名即可。
- 1、import pubsub
- 2、编写定义回调方法demo(),需要注意的时,回调方法中第一个参数为msgName即订阅主题名,而后边才是消息发布时携带的参数。
- 3、在mounted中,指定主题名实现消息订阅,返回订阅id
- 4、在beforeDestroy中,根据订阅时产生的订阅id,取消订阅
step3:消息发布
事件触发更新 - nextTick
语法:this.$nextTick(回调函数)
作用:在下一次DOM更新结束后执行其指定的回调。
使用场景:当事件触发更新视图时,可以放在nextTick()指定的回调函数中,因为事件触发时,Vue会等待触发函数内所有代码执行完毕,才进行DOM更新,如果你想要基于更新后的DOM视图进行操作,应该放在nextTick()指定的回调函数中,这样才可以使得你基于更新后的DOM操作生效。
this.$nextTick(回调函数)
在实际开发中使用特别多。
过度与动画
配置代理解决跨域问题
什么是跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不一同,即跨域
出现跨域的原因
跨域问题出于浏览器的同源策略限制。所谓同源策略(Same-Origin Policy)是一种基于安全考虑的限制,它的主要作用是防止一个网站访问另一个不同来源的网站的数据。同源策略限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互,即一个源的 JavaScript 脚本只能读取同一源的数据,而不能读取其他源的数据。
同源策略主要限制以下几个方面:
-
1、Cookie、LocalStorage 和 IndexDB 等存储器:浏览器只允许网页访问自身网站的 Cookie、LocalStorage 和 IndexDB 等存储器,不能访问其他网站的存储器。
-
2、Ajax 请求:XHR(XMLHttpRequest)对象只能访问同一域下的资源,不能访问其他域的资源。
-
3、DOM 访问:JavaScript 脚本只能访问同一域下的 DOM 对象,不能访问其他域的 DOM 对象。
-
4、Frame 和 iframe:包含不同源页面的 Frame 和 iframe 也受到同源策略的限制。
需要注意的是,同源策略只限制浏览器端的交互,而不限制服务器端的交互。因此,服务器端可以自由地访问和传输不同源的数据。
Vue对于跨域问题的解决 - 配置代理
服务器之间通信不受同源策略影响,即无代理问题,因此,通过将代理服务器启动在前端浏览器进程端口,浏览器请求同端口的代理服务器,代理服务器,再根据浏览器的请求转发给后端服务器,就可以解决请求跨域问题。
方式一
step1:在Vue-Cli中的Vue.config.js中开启Vue代理服务器:
需要注意的时,Vue代理服务器默认运行在前端浏览器同端口的进程,因此这里proxy属性配置的端口地址,实际上为后端服务器端口地址。
step2:向代理服务器发送请求,代理服务器会自动转发到后端服务器端口。
需要说明的是,这种方法有一些缺陷:
- 1、假如要请求的资源,恰好是public中的资源,即该端口中已经有的资源,则就不会走代理服务器。那么假若恰好有与后端路径同名的public文件,则不会请求到后端。
- 2、无法配置多个代理。