Vue数据
Vue数据双向绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
1 基础介绍
1.1 Object.defineProperty
Object.defineProperty()方***直接在一个对象定义一个新属性,或修改一个对象的现有属性,并返回此对象
1.2 js中const定义的值是否能更改
结论:const定义的基本类型不能改变,当时定义的对象是可以通过修改对象属性等方法来改变“
说白了:const定义的基本类型不能改变,但是对于Object感觉不起作用,任然可以被更改,同理Array也可 以被更改,仍然可以push和pop
1.3 发布者订阅者模式
https://www.cnblogs.com/viaiu/p/9939301.html
观察者模式和发布者订阅者模式有什么区别? 大多数的回答:publishers+subscribers=Observer
观察者模式:
发布者订阅者模式
在此模式中,称为发布者的消息发送者不会将消息直接发送给称为订阅者的特定接收者。这意味着发布者和订阅者不知道彼此的存在。存在第三个组件,称为代理或消息代理或事件总线,它由发布者和订阅者都知道,它过滤所有传入的消息并相应的分发他们。pub-sub是用于在不同系统组件之间传递消息的模式,而这些组件不知道彼此身份的任何信息。经纪人如何过滤所有消息?实际上,有几个消息过滤过程
两种模式主要区别:
- 在Observer模式中,观察者和被观察对象是互相知道的,Observers知道Subject,同时Subject还保留了Observers的记录。然而,在发布者/订阅者中,发布者和订阅者不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信、
- 在发布者订阅者模式中,组件是松散耦合的,而不是Observe模式
- 观察者模式主要以同步方式实现,即当某些事件发生时,Subject调用其所有观察者的适当方法。发布者/订阅者在大多数情况下是异步方式(使用消息队列)
- 观察者模式需要在单个应用程序地址空间找中实现。发布者/订阅者模式更像是跨应用程序模式
2 原理
双向绑定由三个部分组成:observer、Dependency 、watcher、Compile
2.1 observe
通过defineProperty来监听data中的数据,设置set和get方法,监听数据,observe就是发布者
2.2 Dependency 订阅者收集器
就是发布者订阅者中间的中间组件,用来收集订阅者,并发布消息给订阅者
2.3 watcher 订阅者
每个被劫持的属性多会对应一个订阅者,当属性被访问时,订阅者会对新旧数据进行比较,如果发生变化,会执行相应的更新函数,从而更新视图。
2.5 compile
解析每个元素上的指令,并将它们对应的节点绑定相应的更新函数,初始化相应的订阅者,或者替模版数据,初始化视图。说白了就是,用来操作dom ,更新视图
https://www.dazhuanlan.com/2019/12/16/5df6dce2f2d0d/
Vue 扩展
1 依赖注入
想象一个场景,子组件依赖父组件传递的
props,如果有多个这样的props,其实已经很烦了,当有多个子组件,或者孙组件也依赖于由上至下的props时,写那么多props简直是要了亲命了。为了解决这种麻烦,产生了
vuex。但是我们肯定希望组件的高可复用性,不引入vuex就能实现功能,那就要考虑vue本身是否支持这种能力了。熟悉React开发的人应该会想到Context这个高阶组件,它通过Provider和Consumer,可以使得无限嵌套的父子组件共有一个数据源。Vue也有一个类似的,连名字都很相似,就是这篇文章的主人公provide和inject。
https://www.cnblogs.com/llcdxh/p/10330929.html
解决场景:根组件A有一个方法getMap,该组件A下的子组件B,子组件C,子组件D,或者子组件B下的子组件E等层层嵌套情况下,在某种情况下,都需要访问父组件的getMap方法,那么常用的方法是子组件B:this.parent.
parent属性无法很好的扩展到更深层级的嵌套组件上
- 个人理解,将父组件的方法或者数据向下暴露给子或者孙子或更深的组件,provide用于父组件暴露需要暴露的函数或者数据,inject用于后代组件中,获取父辈的方法或数据
2 . 渲染函数和JSX
2.1 render()
Vue推荐在绝大多数情况下使用模板来创建你的HTML.然而在一些场景中,你真的需要JavaScript的完全编程的能力,这时可以用渲染函数
使用模板
<!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="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script> </head> <bod> <div id="app"> {{message}} <Componenta></Componenta> <Component-b></Component-b> <Component-c></Component-c> </div> <script> var Componenta = { template:`<div>我是A</div>` } var ComponentB = { template:`<div>我是B</div>` } var ComponentC = { template:`<div>我是C</div>` } var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' }, components:{ Componenta, ComponentB, ComponentC } }) </script> </body> </html>
如果全使用JavaScript来构建,使用render()函数
<!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="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
</head>
<bod>
<div id="app">
{{message}}
<div>
<component_test :level="3">都是</component_test>
</div>
</div>
<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level===1">
<slot></slot>
</h1>
<h3 v-else-if="level===3">
<slot></slot>
</h3>
<h5 v-else-if="level===5">
<slot></slot>
</h5>
<!--
注释部分 会报错 ,不能v-if v-if v-if 得是 v-if v-else-if v-else-if
<h1 v-if="level===1">
<slot></slot>
</h1>
<h3 v-if="level===3">
<slot></slot>
</h3>
<h5 v-if="level===5">
<slot></slot>
</h5>
-->
</script>
<script>
var component_test={
template:'#anchored-heading-template',
props:{
level:{
type:Number
}
}
}
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
components:{
component_test,
},
})
</script>
</body>
</html> 使用render()构造
<!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="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
</head>
<bod>
<div id="app">
<div>
<component-render :level="1">由render渲染</component-render>
</div>
</div>
<script>
var component_test={
template:'#anchored-heading-template',
props:{
level:{
type:Number
}
}
}
var ComponentRender={
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
}
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
components:{
'component-render':ComponentRender
},
//完全通过render函数去实现组件
})
</script>
</body>
</html> **这样代码精简的多,但是需要非常熟悉Vue的实力property。在上面的例子中,你需要知道,向组件中传递不带v-slot指令的子节点时,这些子节点被存储在组件实例中的$slots.default中。可以深入实力propertyAPI
个人理解,从这个地方才叫真正入vue的门了
createElement参数
接下来你需要熟悉的是如何在createElement函数中使用模板中的那些功能,这里是createElement接受的参数
//render 函数的返回参数:return createElement("",{},[])
let render_1={
render:function(createElement){
return createElement(
'div',
{
style:{color:'red',fontSize:'14px'}
},
this.$slots.default //或者一个数组['ssssdfs']
)
}
} return createElement(para1,para2,para3)
para1:字符串或者对象 定义渲染的是个啥标签
para2:是个对象,定义对应的属性节点,控制style等等
para3:是个数组 定义插槽的格式
Array(20)与Array.apply({length:20})的区别
https://www.cnblogs.com/afeihome/p/6750539.html
评论区的说法应该是对的
使用render函数来实现v-if和v-for一样的效果
<!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="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
</head>
<body>
<div id='app'>
<div>
<ul v-if="items.length">
<li v-for="item in items">
{{item.name}}
</li>
</ul>
</div>
<div>
<render_if :items="items"></render_if>
</div>
</div>
<script>
let render_if={
render:function(createElement){
if(this.items.length){
return createElement('ul',this.items.map(data=>{return createElement('li',data.name)}))
}else{
return createElement('p','没有节点')
}
},
props:['items']
}
var app=new Vue({
el:'#app',
data(){
return {
items:[{name:'刘'},{name:'关'},{name:'张'}]
}
},
components:{
render_if
}
})
</script>
</body>
</html> **渲染函数中没有与v-model的直接对应。所以必须自己实现相应的逻辑**
在render中渲染的组件中不要使用v-model,会覆盖全局性的v-model
props: ['value'],
render: function (createElement) {
var self = this
return createElement('input', {
domProps: {
value: self.value
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
} 这部分代码没搞懂。。。
函数式组件
Vue.component('my-functional-button', {
functional: true,
render: function (createElement, context) {
// 完全透传任何 attribute、事件***、子节点等。
return createElement('button', context.data, context.children)
}
}) 通过向 createElement 传入 context.data 作为第二个参数,我们就把 my-functional-button 上面所有的 attribute 和事件***都传递下去了。事实上这是非常透明的,以至于那些事件甚至并不要求 .native 修饰符。
如果你使用基于模板的函数式组件,那么你还需要手动添加 attribute 和***。因为我们可以访问到其独立的上下文内容,所以我们可以使用 data.attrs 传递任何 HTML attribute,也可以使用 listeners (即 data.on 的别名) 传递任何事件***。
<template functional>
<button
class="btn btn-primary"
v-bind="data.attrs"
v-on="listeners"
>
<slot/>
</button>
</template> 