一、 Vue(ref和$refs属性介绍与使用)
在Vue中一般很少会用到直接操作DOM,但不可避免有时候需要用到,这时我们可以通过ref和$refs这两个来实现,本文我们就来详细的介绍下这个内容
除了自定义属性外,Vue实例还暴露一些有用的实例属性和方法,他们都有前缀$,以便和用户定义的属性和方法分开
常用的属性有:
常用的属性有:
1:vm.$el //获取vue实例关联的Dom元素
2: vm.$data //获取vue实例的data选项
3:vm.$options //获取vue实例的自定义属性
4:vm.$refs //获取vue实例中所有含有ref属性的dom元素,如果有多个,只返回最后一个。
5: vm.$ref // ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件的 $refs 对象上,如果是在普通的DOM元素上使用,引用指向的就是 DOM 元素,如果是在子组件上,引用就指向组件的实例
ref加载普通元素上,this.$refs. 获取到的就是dom元素
ref记载在子组件上,this.$refs. 获取到的就是组件实例,可使用组件的所有方法。
ref有三种用法:
①ref加在普通的元素上,用this.ref.name获取到的是dom元素;
②ref加在子组件上,用this.ref.name获取到的是组件实例,可以使用组件的所有方法;
③如何利用v-for和ref获取一组数据或者dom节点。
当v-for用于元素或者组件的时候,引用信息将是包含dom节点或组件实例的数组;
关于ref注册时间的重要说明:因为ref本身是作为渲染结果被创建的,在初始渲染的时候,你不能访问它们--它们还不存在!$refs也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
ref
ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件的 $refs 对象上,如果是在普通的DOM元素上使用,引用指向的就是 DOM 元素,如果是在子组件上,引用就指向组件的实例。
ref作用
用来给元素或者子组件注册引用信息。引用信息将会注册给父组件的$refs对象上。
1、如果给普通的dom元素使用,引用指向的是dom元素。
2、如果是给子组件使用,引用指向的是子组件的实例。
注意:
1、$refs只有在组件渲染完成后才填充,在初始渲染的时候不能访问它们,并且它是非响应式的,因此不能用它在模板中做数据绑定。
2、微信小程序中给dom元素使用ref无效。得到的是 空对象.
$refs
$refs 是一个对象,持有已注册过 ref 的所有的子组件。
具体演示
1.基础代码
先来准备案例基础代码,如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
var vm = new Vue({
el: "#app",
data: {},
methods: {}
})
</script>
</body>
</html>
2.普通DOM
2.1 普通方式
我们先通过 getElementById 方法来获取
2.2 ref使用
接下来我们通过 ref 属性来试试。
然后查看 vm 实例对象
通过上面的演示我们发现 在vm实例上有一个 $refs属性,而且该属性就有我们通过ref注册的DOM对象,于是我们可以这样获取DOM对象
3.组件
ref 也可以作用在组件中,我们来看下效果
3.1 添加组件
先来添加一个自定义的组件
3.2 ref 使用
在 子组件中使用 ref属性,会将子组件添加到父组件的$refs对象中,如下
查看vm对象
通过 vm 实例查看 发现 $refs中绑定的有 我们的login组件,而且还看到了对应的 组件中的 msg属性和 show方法,那这样我们可以调用了,如下
3.5 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
<input type="button" value="获取h3的值" @click="getElement()">
<h3 id="myh3" ref="myh3" >我是一个h3</h3>
<hr>
<login ref='mylogin'></login>
</div>
<script>
var login = {
template: "<h3>我是login子组件</h3>",
data(){
return {
msg: "ok"
}
},
methods:{
show(){
console.log("show方法执行了...")
}
}
}
var vm = new Vue({
el: "#app",
data: {},
methods: {
getElement(){
// 通过 getElementById 方式获取 DOM 对象
// console.log(document.getElementById("myh3").innerHTML)
// console.log(this.$refs.myh3.innerHTML)
console.log(this.$refs.mylogin.msg)
this.$refs.mylogin.show()
}
},
components:{
login
}
})
</script>
</body>
</html>
二. vue中父子组件通过ref传值「dialog组件」
一个基于vue的项目,有可能会很多的组件,组件之间难免需要进行数据的传递,比如: 父组件 传数据 给子组件;子组件传数据给父组件等,需要用到组件之间的通信处理方式。
项目中经常用到element中的dialog组件,现记录父子组件通过ref传值。
操作流程:
1.父组件中点击按钮吊起子组件模态框dialog进行内容设置,并给子组件传递id
this.$nextTick(() => { //此函数执行时所有的DOM挂载和渲染都已完成
this.$refs.dialogRef.init(this.fatherId); //获取子组件中init方法并将父组件id传递给子组件
});
2.在子组件中需接收父组件传来的内容id并查询内容详情
init (val) {
this.activityId = val //接收父组件传递的id值
}
3.在子组件dialog中可以编辑内容,然后将数据通过$emit传递给父组件
this.$emit("setActivityBtn", this.SetForm); //setActivityBtn为父组件接收的方法,将参数传给父组件
4.父组件接收数据后提交到服务器
setActivityBtn(data) { //获取子组件传来的值
let params = data
},
以下为父子组件全部代码
子组件Dialog
<script>
export default {
name: '',
data () {
return {
id: '', //用来查询详情的id
dialogFormVisible: false, //模态框
SetForm: { }, //模态框数据
}
},
methods: {
// 初始化方法
init (val) {
this.activityId = val //接收父组件传递的值
this.dialogFormVisible = true;
this.getActivityInfo()
},
//获取内容详情
getActivityInfo () {
},
//模态框确定按钮
setActivityBtn () {
this.$emit("setActivityBtn", this.SetForm); //将参数传给父组件
this.dialogFormVisible = false;
},
}
</script>
父组件
<script>
import Dialog from '../components/Dialog'
export default {
name: '',
data () {
return {
fatherId:'', //详情id
dialogShow: false, //模态框
}
},
components: { // 组件的引用
Dialog
},
methods: {
//吊起模态框
activitySet() {
this.dialogShow= true;
this.$nextTick(() => { //此函数执行时所有的DOM挂载和渲染都已完成
this.$refs.dialogRef.init(this.fatherId); //获取子组件中init方法并将父组件id传递给子组件
});
},
//确定按钮
setActivityBtn(data) { //获取子组件传来的值
let params = data
XXXXXXXX(params).then(res => {
if (res.data.code == 0) {
this.dialogFormVisible = false
}
})
},
}
</script>
吊起模态框
<script>
import Dialog from '../components/Dialog'
export default {
name: '',
data () {
return {
fatherId:'', //详情id
dialogShow: false, //模态框
}
},
components: { // 组件的引用
Dialog
},
methods: {
//吊起模态框
activitySet() {
this.dialogShow= true;
this.$nextTick(() => { //此函数执行时所有的DOM挂载和渲染都已完成
this.$refs.dialogRef.init(this.fatherId); //获取子组件中init方法并将父组件id传递给子组件
});
},
//确定按钮
setActivityBtn(data) { //获取子组件传来的值
let params = data
XXXXXXXX(params).then(res => {
if (res.data.code == 0) {
this.dialogFormVisible = false
}
})
},
}
</script>
三.拓展
方式一、父子组件通过ref传值,然后在子组件中data函数直接return获得
父组件中:可以通过ref向子组件传值
this.$refs.dialogRef.name1=this.fatherName1
this.$refs.dialogRef.name2=this.fatherName2
子组件中:可以通过数组的形式向父组件传递多个参数
this.$emit("setActivityBtn", [this.SetForm,this.dialogFormVisible]);
方式二.v-bind绑定,子组件中props接受,return中定义要改变传给父组件的属性:
父组件
<script>
this.fatherName= this.detailData.name;
this.fatherSalePrice= this.detailData.salePrice;
</script>
子组件
<script>
export default {
props: {
sonName: {
type:String,
default:''
},
sonSalePrice: {
type:Number,
default:0
},
},
data:function () {
return {
cartName:this.sonName,
cartSalePrice:this.sonSalePrice
}
},
methods: {
addCart() {
this.$emit('confirmAddCart',[this.cartName,this.cartSalePrice]);
}
}
}
</script>
注:vue的思想是数据驱动视图,所以尽量少的用直接操作dom,当然一些需要获取元素宽高等场景时也会用到$refs
事件驱动
四 vue的思想是数据驱动视图
在前端来说数据驱动式框架,必然离不开事件驱动,事件驱动一定程度上弥补了数据驱动的不足,在dom操作的时代通常都是这样操作:
通过特定的选择器查找到需要操作的节点 -> 给节点添加相应的事件监听
响应用户操作,效果是这样:
用户执行某事件(点击,输入,后退等等) -> 调用 JavaScript 来修改节点
这种模式对业务来说是没有什么问题,但是从开发成本和效率来说会比较力不从心,在业务系统越来越庞大的时候,就显得复杂了。另一方面,找节点和修改节点这件事,效率本身就很低,因此出现了数据驱动模式。
数据驱动
读取模板,同时获得数据,并建立 VM( view-model ) 的抽象层 -> 在页面进行填充
要注意的是,MVVM 对应了三个层,M - Model,可以简单的理解为数据层;V - View,可以理解为视图,或者网页界面;VM - ViewModel,一个抽象层,简单来说可以认为是 V 层中抽象出的数据对象,并且可以与V 和 M 双向互动(一般实现是基于双向绑定,双向绑定的处理方式在不同框架中不尽相同)。
用户执行某个操作 -> 反馈到 VM 处理(可以导致 Model 变动) -> VM 层改变,通过绑定关系直接更新页面对应位置的数据
Vue 模式
Vue 通过{{}}绑定文本节点,data里动态数据与Props静态数据进行一个映射关系,当data中的属性或者props中的属性有变动,以上两者里的每个数据都是行为操作需要的数据或者模板 view 需要渲染的数据,一旦其中一个属性发生变化,则所有关联的行为操作和数据渲染的模板上的数据同一时间进行同步变化,这种基于数据驱动的模式更简便于大型应用开发。只要合理的组织数据和代码,就不会显得后续皮软。
何为动态数据 data,何为静态数据 props
- 相同点
两者选项里都可以存放各种类型的数据,当行为操作改变时,所有行为操作所用到和模板所渲染的数据同时都会发生同步变化。
- 不同点
Data 被称之为动态数据的原因,在各自实例中,在任何情况下,我们都可以随意改变它的数据类型和数据结构,不会被任何环境所影响。
Props 被称之为静态数据的原因,在各自实例中,一旦在初始化被定义好类型时,基于 Vue 是单向数据流,在数据传递时始终不能改变它的数据类型。
更为关键地是,对数据单向流的理解,props的数据都是通过父组件或者更高层级的组件数据或者字面量的方式进行传递的,不允许直接操作改变各自实例中的props数据,而是需要通过别的手段,改变传递源中的数据。
- data 选项
当一个实例创建的时候,Vue会将其响应系统的数据放在data选项中,当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。初始定行的行为代码也都会随着响应系统进行一个映射。
而 data 选项中的数据在实例中可以任意改变,不受任何影响,前提必须数据要跟逻辑相辅相成。
- 初始化映射
<template>
<div>
<p v-if='boolean'>true</p>
<p v-for='value in obj'>{{value}}</p>
<p v-for='item in list'>{{item}}</p>
<p>{{StringMsg}}</p>
<p>{{NumberMsg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
obj : {a:'1',b:'2',c:'3'},
list:['a','b','c'],
boolean : true,
StringMsg : 'hello vue',
NumberMsg : 2.4,
}
}
}
</script>
运行代码时,在data选项里定义了五种数据类型,通过指令和{{}}进行渲染,证实了data选项里可以定义任何数据类型。
- 视图与数据映射
<template>
<div>
<p>{{StringMsg}}</p>
<p>{{NumberMsg}}</p>
<button @click='changeData'>改变数据</button>
</div>
</template>
<script>
export default {
data () {
return {
StringMsg : 'hello vue',
NumberMsg : 2.4
}
},
methods: {
changeData () {
this.StringMsg = 2.4;
this. NumberMsg = 'hello vue'
}
}
}
</script>
每个.vue 的文件则就是一个实例,在 data 中定义了两种数据:
String 类型
Number 类型
同时还定义了一个 changeData 事件。
在运行代码时候,data选项已经进入了Vue的响应系统里,model层(数据层)与view层(视图层)进行了对应的映射,任何数据类型都可以定义。
当用户发生点击操作的时候,同时可以把 StringMsg, NumberMsg 的数据对调,充分说明了,无论值和类形都可以进行随意转换。
- 行为与数据的映射
<template>
<div>
<p>{{StringMsg}}</p>
<p>{{NumberMsg}}</p>
<button @click='changeData'>改变数据</button>
<button @click='findData'>查看数据</button>
</div>
</template>
<script>
export default {
data () {
return {
StringMsg : 'hello vue',
NumberMsg : 2.4
}
},
methods: {
changeData () {
this.StringMsg = 2.4;
this.NumberMsg = 'hello vue'
},
findData () {
console.log(`StringMsg: ${this.StringMsg}`)
console.log(`NumberMsg: ${this.NumberMsg}`)
}
}
}
</script>
改变数据以后,通过点击 findData 事件来进行验证,虽然在初始化定义好了行为数据的检测代码,但是当数据在执行 findData 之前先执行 changeData,一旦改变 data 选项里的数据时,findData 里对应的数据同时也会进行相应的映射。
this.StringMsg //=> 2.4
this.NumberMsg //=>‘hello vue’
五 、总结:
- data 选项里的数据是灵活的
- 可以定义任何数据类型
- 也可以改变成任何数据类型
- 当数据变化时,视图和行为绑定的数据都会同步改变
props
- 使用props传递数据作用域是孤立的,它是父组件通过模板传递而来,想接收到父组件传来的数据,需要通过props选项来进行接收。
- 子组件需要显示的声明接收父组件传递来的数据的数量,类型,初始值。
- 简单的接收可以通过数组的形式来进行接收。
父组件
<template>
<div>
<demo :msg='msgData' :math = 'mathData' ></demo>
</div>
</template>
<script>
import Demo from './Demo.vue'
export default {
data () {
return {
msgData:'从父组件接收来的数据',
mathData : 2
}
},
components : {
Demo
}
}
</script>
子组件
<template>
<div>
<p>{{msg}}</p>
<p>{{math}}</p>
</div>
</template>
<script>
export default {
name: 'demo',
props: [ 'msg' , 'math'],
}
</script>
在子组件中需要通过显示定义好需要从父组件中接收那些数据。
同样的在父组件中在子组件模板中过v-bind来传递子组件中需要显示接收的数据。
语法: :== v-bind(是封装的语法糖) :msg = msgData
msg 第一个参数必须要与子组件的 props 同名
msgData 则是父组件中需要向子组传递的数据
props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
父组件
<template>
<div>
<demo :fn = 'myFunction' ></demo>
</div>
</template>
<script>
import Demo from './Demo.vue'
export default {
components : {
Demo
},
methods: {
myFunction () {
console.log('vue')
}
}
}
</script>
六 、为什么会出现MVVM?
1. 首先解释:MVVM是什么?干什么用的?
1:MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式。
2:其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。
3:以Vue.js 为例。Vue是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。
4:它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel负责连接 View 和 Model,保证视图和数据的一致性,这种轻量级的架构让前端开发
更加高效、便捷。
-
Model
- 模型、数据 -
View
- 视图、模板(视图和模型是分离的) -
ViewModel
- 连接Model
和View
本文来源:码农网
本文链接:https://www.codercto.com/a/33262.html
2.为什么会出现MVVM? 框架的发展? MVC----MVP----MVVM
1:最初是MVC:MVC是一种架构模式,M表示Model,V表示视图View,C表示控制器Controller
就是 模型—视图—控制器,也就是说一个标准的Web 应用程式是由这三部分组成的
2:在HTML5 还未火起来的那些年,MVC 作为Web 应用的最佳实践是OK 的.
3:这是因为 Web 应用的View 层相对来说比较简单,前端所需要的数据在后端基本上都可以处理好
4:View 层主要是做一下展示,那时候提倡的是 Controller 来处理复杂的业务逻辑,所以View 层相对来说比较轻量,就是所谓的瘦客户端思想。
1、 开发者在代码中大量调用相同的 DOM API,处理繁琐 ,操作冗余,使得代码难以维护。
2、大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
3、 当 Model 频繁发生变化,开发者需要主动更新到View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到Model 中,这样
的工作不仅繁琐,而且很难维护复杂多变的数据状态。
MVVM的出现完美的解决了上面的几个问题:
1:MVVM 由 Model、View、ViewModel 三部分构成,Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负
责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
2:在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数
据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
3:ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发
者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
3. Vue.js与MVVM的关系?
Vue.js 可以说是MVVM 架构的最佳实践,VUE并没有完全遵循MVVM,专注于 MVVM 中的 ViewModel,不仅做到了数据双向绑定,而且也是一款相对比较轻量级的JS 库,API 简洁,很容易上手。
Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式来实现数据绑定的。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
3.1 关于 ViewModel
MVVM ViewModel
v-model用于表单数据的双向绑定
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
-
- v-bind绑定一个value属性
-
- v-on指令给当前元素绑定input事件
自定义组件使用v-model,应该有以下操作:
- 接收一个value prop
- 触发input事件,并传入新值
在原生表单元素中:
<input v-model="inputValue">
相当于
<input v-bind:value="inputValue" v-on:input="inputValue = $event.target.value">
在自定义组件中
<my-component v-model="inputValue"></my-component>
相当于
<my-component v-bind:value="inputValue" v-on:input="inputValue = argument[0]"></my-component>
这个时候,inputValue接受的值就是input事件的回调函数的第一个参数,所以在自定义组件中,要实现数据绑定,还需要$emit去触发input的事件。
this.$emit('input', value)
3.2 MVVM 框架的三大要素
- 响应式:
vue
如何监听到data
的每个属性变化? - 模板引擎:
vue
的模板如何被解析,指令如何处理? - 渲染:
vue
的模板如何被渲染成html
?以及渲染过程
3.3 vue 中如何实现响应式
3.3.1 什么是响应式
修改 data 属性之后,vue 立刻监听到
data 属性被代理到 vm 上
3.3.1 .2 Object.defineProperty
3.3.1 .3 模拟实现
4. vue 中如何解析模板
4.1 模板是什么
- 本质:字符串
- 有逻辑,如 v-if v-for 等
- 与 html 格式很像,但有很大区别
- 最终还要转换为 html 来显示
模板最终必须转换成 JS 代码,因为
- 有逻辑( v-if v-for ),必须用 JS 才能实现
- 转换为 html 渲染页面,必须用 JS 才能实现
- 因此,模板最重要转换成一个 JS 函数(
render
函数)
4.2 render 函数
- 模板中所有信息都包含在了 render 函数中
- this 即 vm
- price 即 this.price 即 vm.price ,即 data 中的 price
- _c 即 this._c 即 vm._c
4.3 render 函数与 vdom
- vm._c 其实就相当于 snabbdom 中的 h 函数
- render 函数执行之后,返回的是 vnode
-
updateComponent
中实现了vdom
的patch
- 页面首次渲染执行
updateComponent
- data 中每次修改属性,执行
updateComponent
五、vue 的整个实现流程
- 第一步:解析模板成
render
函数 - 第二步:响应式开始监听
- 第三步:首次渲染,显示页面,且绑定依赖
- 第四步:
data
属性变化,触发rerender
5.1 第一步:解析模板成 render 函数
- 模板中的所有信息都被
render
函数包含 - 模板中用到的
data
中的属性,都变成了 JS 变量 - 模板中的 v-
model
v-for
v-on
都变成了 JS 逻辑 -
render
函数返回vnode
5.2 第二步:响应式开始监听
- Object.defineProperty
- 将 data 的属性代理到 vm 上
5.3 第三步:首次渲染,显示页面,且绑定依赖
- 初次渲染,执行
updateComponent
,执行vm._render()
- 执行
render
函数,会访问到vm.list vm.title
- 会被响应式的
get
方法监听到 - 执行
updateComponent
,会走到vdom
的patch
方法 -
patch
将vnode
渲染成DOM
,初次渲染完成
为何要监听 get
,直接监听 set
不行吗?
- data 中有很多属性,有些被用到,有些可能不被用到
- 被用到的会走到 get ,不被用到的不会走到 get
- 未走到 get 中的属性, set 的时候我们也无需关心
- 避免不必要的重复渲染
https://www.codercto.com/a/33262.html
前端面试之MVVM浅析
本文来源:码农网
本文链接:https://www.codercto.com/a/33262.html
超详细VUE专栏