Heim > Artikel > Web-Frontend > Detaillierte Erläuterung der beiden Kerne von VUE: reaktionsfähige und komponentenbasierte Entwicklung
Dieser Artikel bringt Ihnen relevantes Wissen über vue. Er stellt hauptsächlich die beiden Kernthemen von Vue vor, Reaktionsfähigkeit und Komponentisierung. Ich hoffe, dass er für alle hilfreich ist.
【Verwandte Empfehlungen: Javascript-Video-Tutorial, vue.js-Tutorial】
Die Methode Object.defineProperty(obj, prop, descriptor)
definiert direkt eine neue Eigenschaft eines Objekts oder ändert eine vorhandene Eigenschaft eines Objekts und gibt dieses Objekt zurück. Object.defineProperty(obj, prop, descriptor)
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
obj——要定义属性的对象
prop——要定义或修改的属性的名称或Symbol
descriptor——对象,要定义或修改的属性描述符
// descriptor{ value: undefined, // 属性的值 get: undefined, // 获取属性值时触发的方法 set: undefined, // 设置属性值时触发的方法 writable: false, // 属性值是否可修改,false不可改 enumerable: false, // 属性是否可以用for...in 和 Object.keys()枚举 configurable: false // 该属性是否可以用delete删除,false不可删除,为false时也不能再修改该参数}
通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。
而默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
示例:
const a = {b : 1} console.log(Object.getOwnPropertyDescriptor(a, 'b')) // {value: 1, writable: true, enumerable: true, configurable: true} Object.defineProperty(a, 'c', {value: '2'}) console.log(Object.getOwnPropertyDescriptor(a, 'c')) // {value: '2', writable: false, enumerable: false, configurable: false} a.c = 3 console.log(a.c) // 2 Object.defineProperty(a, 'c', {value: '4'}) console.log(a.c) // error: Uncaught TypeError: Cannot redefine property: c
// 模拟vue响应式过程const app = document.getElementById('app')const data = { a: { b: { c: 1 } }}function render () { const virtualDom = `这是我的内容${data.a.b.c}` app.innerHTML = virtualDom}function observer (obj) { let value for (const key in obj) { // 递归设置set和get value = obj[key] if (typeof value === 'object'){ arguments.callee(value) } else { Object.defineProperty(obj, key, { get: function(){ return value }, set: function(newValue){ value = newValue render() } }) } }}render()observer(data)setTimeout(() => { data.a.b.c = 22}, 2000)setTimeout(() => { data.a.b.c = 88}, 5000)
上述方法实现了数据的响应,但存在很大的问题,我们触发一次set,就需要整个页面重新渲染,然而这个值可能只在某一个组件中使用了。
所以将get和set优化:
Object.defineProperty(data, key, { get: function(){ dep.depend() // 这里进行依赖收集 return value }, set: function(newValue){ value = newValue // render() dep.notify() // 这里进行virtualDom更新,通知需要更新的组件render }});
dep是Vue负责管理依赖的一个类
补充: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
const vm = new Vue({ data: { a: 1 }})// vm.a是响应式的vm.b = 2// vm.b是非响应式的
vue 中处理数组的变化,直接通过下标触发视图的更改,只能使用push、shift等方法,而数组不能使用Object.defineProperty()
其实 Vue用装饰者模式来重写了数组这些方法
Object.create(proto,[propertiesObject])
方法是创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
proto
——新创建对象的原型对象;propertiesObject
obj – das Objekt zum Definieren des Attributsprop – der Name oder das Symbol des zu definierenden oder zu ändernden Attributs
descriptor – das Objekt, der zu definierende oder zu ändernde Attributdeskriptor
const a = {} // 相当于 const a = Object.create(Object.prototype) const person = { isHuman: false, printIntroduction: function () { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = Object.create(person); me.name = 'Matthew'; // "name" is a property set on "me", but not on "person" me.isHuman = true; // inherited properties can be overwritten me.printIntroduction(); // expected output: "My name is Matthew. Am I human? true"
Gewöhnliche Eigenschaften, die durch Zuweisungsvorgänge hinzugefügt werden, sind aufzählbar und werden beim Aufzählen von Objekteigenschaften aufgezählt (for...in oder Object.keys). Die Werte dieser Eigenschaften können geändert oder gelöscht werden. Mit dieser Methode können Sie die standardmäßigen Zusatzoptionen (oder Konfigurationen) ändern.Standardmäßig sind mit Object.defineProperty() hinzugefügte Eigenschaftswerte unveränderlich.
Beispiel:
const o = Object.create(Object.prototype, { foo: { // foo会成为所创建对象的数据属性 writable:true, configurable:true, value: "hello" }, bar: { // bar会成为所创建对象的访问器属性 configurable: false, get: function() { return 10 }, set: function(value) { console.log("Setting `o.bar` to", value); } }});console.log(o) // {foo: 'hello'}1.2 set and get
const arraypro = Array.prototype // 获取Array的原型 const arrob = Object.create(arraypro) // 用Array的原型创建一个新对象,arrob.__proto__ === arraypro,免得污染原生Array; const arr=['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 需要重写的方法 arr.forEach(function(method) { arrob[method] = function () { arraypro[method].apply(this, arguments) // 重写时先调用原生方法 dep.notify() // 并且同时更新 } }) // 对于用户定义的数组,手动将数组的__proto__指向我们修改过的原型 const a = [1, 2, 3] a.__proto__ = arrobDie obige Methode erreicht eine Datenantwort, aber es gibt ein großes Problem, wenn wir set einmal auslösen, muss die gesamte Seite neu gerendert werden Der Wert darf nur in einer Komponente verwendet werden.
Zusatz: Vue kann das Hinzufügen oder Entfernen von Eigenschaften nicht erkennen. Da Vue beim Initialisieren der Instanz eine Getter/Setter-Konvertierung für die Eigenschaft durchführt, muss die Eigenschaft für das Datenobjekt vorhanden sein, damit Vue es in ein reaktionsfähiges |
2 konvertieren kann. Die Reaktionsfähigkeit des Arrays |
---|---|
propertiesObject – optional, der Typ ist ein Objekt. Wenn dieser Parameter angegeben ist und nicht undefiniert ist, werden die eigenen aufzählbaren Eigenschaften des übergebenen Objekts verwendet. Die selbst definierten Eigenschaften und nicht die aufgezählten Eigenschaften in der Prototypenkette) fügen dem neu erstellten Objekt den angegebenen Eigenschaftswert und den entsprechenden Eigenschaftsdeskriptor hinzu Geben Sie den Wert direkt an, daher liegt ein Problem vor, das heißt, der Benutzer kann versehentlich unser Objekt ändern. Daher können wir die zuvor erwähnte Object.defineProperty verwenden, um dieses Problem zu vermeiden. Wir erstellen eine öffentliche Methode def, die speziell zum Festlegen von Attributen verwendet wird Werte können nicht geändert werden Datenattribute: |
|
gibt an, ob das Attribut durch Löschen des Attributs durch Löschen neu definiert werden kann, ob die Eigenschaften des Attributs geändert werden können oder ob das Attribut dies kann neu definiert werden. Der Standardwert ist true | enumerable |
Accessor-Attribut: Dieses Attribut enthält keine Datenwerte. Es enthält ein Paar von Get- und Set-Methoden. Beim Lesen und Schreiben von Accessor-Attributen werden diese beiden Methoden zum Ausführen von Operationen verwendet.
Die vier Deskriptoren des Accessor-Attributs:
Bedeutung | |
---|---|
konfigurierbar | gibt an, ob das Attribut durch Löschen des Attributs durch Löschen neu definiert werden kann, ob die Eigenschaften des Attributs sein können geändert, oder ob das Attribut in ein Accessor-Attribut geändert werden soll, der Standardwert ist false |
enumerable | Gibt an, ob das Attribut über eine For-In-Schleife zurückgegeben werden kann, der Standardwert ist false |
get | Die beim Lesen des Attributs aufgerufene Funktion, der Standardwert ist undefiniert |
set | Funktion, die beim Schreiben von Eigenschaften aufgerufen wird, der Standardwert ist undefiniert |
vue3.0的响应式和vue2.0响应式原理类似,都是在get中收集依赖,在set中通知依赖更新视图,但vue3.0使用了es6新增的proxy来代替Object.defineProperty()
proxy相对于Object.defineProperty()的好处:
- Object.defineProperty需要指定对象和属性,对于多层嵌套的对象需要递归监听,Proxy可以直接监听整个对象,不需要递归;
- Object.defineProperty的get方法没有传入参数,如果我们需要返回原值,需要在外部缓存一遍之前的值,Proxy的get方法会传入对象和属性,可以直接在函数内部操作,不需要外部变量;
- set方法也有类似的问题,Object.defineProperty的set方法传入参数只有newValue,也需要手动将newValue赋给外部变量,Proxy的set也会传入对象和属性,可以直接在函数内部操作;
- new Proxy()会返回一个新对象,不会污染源原对象
- Proxy可以监听数组,不用单独处理数组
proxy劣势: vue3.0将放弃对低版本浏览器的兼容(兼容版本ie11以上)
这样上边的observe方法就可以优化成:
function observer () { var self = this; data = new Proxy(data, { get: function(target, key){ dep.depend() // 这里进行依赖收集 return target[key] }, set: function(target, key, newValue){ target[key] = newValue; // render() dep.notify() // 这里进行virtualDom更新,通知需要更新的组件render } });}
按照功能(或按照复用性)把一个页面拆成各个板块(模块),每一个模块都是一个单独的文件(单独的组件),最后把各个模块(组件)拼在一起即可!!
目的 :方便团队协作开发 实现复用
功能型组件「UI组件库中提供的一般都是功能型组件:element/iview/antdv/vant/cube..」
业务型组件
以后开发项目,拿到设计稿的第一件事情:划分组件「按照功能版块划分、本着复用性原则,拆的越细越好(这样才能更好的实现复用)」
组件的创建及使用
创建一个 Xxx.vue 就是创建一个vue组件{局部组件、私有组件},组件中包含:结构、样式、功能
结构:基于template构建
+ 只能有一个根元素节点(vue2)
+ vue的视图就是基于template语法构建的(各种指令&小胡子...),最后vue会把其编译为真实的DOM插入到页面指定的容器中
首先基于 vue-template-compiler 插件把template语法编译为虚拟DOM「vnode」
其次把本次编译出来的vnode和上一次的进行对比,计算出差异化的部分「DOM-DIFF」
最后把差异化的部分变为真实的DOM放在页面中渲染
样式:基于style来处理
功能:通过script处理
+ 导出的这个对象是VueComponent类的实例(也是Vue的实例):对象 -> VueComponent.prototype -> Vue.prototype
+ 在对象中基于各种 options api 「例如:data、methods、computed、watch、filters、生命周期函数...」实现当前组件的功能
+ 在组件中的data不再是一个对象,而是一个“闭包”
+ 各个组件最后会合并在一起渲染,为了保证组件中指定的响应式数据是“私有的”,组件之间数据即使名字相同,也不会相互污染...所以需要基于闭包来管理
注意;App.vue页面入口相当于首页,写好的组件都导入到这个里面
<template> <div> {{ msg }} </div> </template> <script> export default { name: "Test", data() { return { //编写响应式数据 msg: "你好,世界", }; }, }; </script> <style> .box { font-size: 20px; color: red; } </style>
私有组件(使用的时候首先进行导入,然后注册,这样视图中就可以调用组件进行渲染了)
需要使用私有组件的时候,需要先导入import Test from "./Test.vue";
然后注册:这样就可以调用组件进行渲染了
<template> <div> //3.使用组件:可以使用单闭合或双闭合 <test></test> <test> </test> </div> </template> <script> //1、导入组件 import Test from "./Test.vue"; export default { name: "App", components:{ //2、注册使用的组件 Test, } }; </script>
创建全局组件
1. 创建一个局部组件
<template> <div>{{ msg }}</div> </template> <script> export default { name: "Vote", data() { return { msg: "今夜阳光明媚", }; }, }; </script>
@2 在main.js入口中,导入局部组件Vote,把其注册为全局组件
import Vote from './Vote.vue'; Vue.component('Vote', Vote)
@3 这样在任何组件(视图中),无需基于components注册,直接可以在视图中调用
调用组件的方式
调用组件的时候,可以使用:
双闭合
双闭合的方式可以使用插槽slot
@1 在封装的组件中,基于
标签预留位置
@2 调用组件的时候,基于双闭合的方式,把要插入到插槽中的内容写在双闭合之间
单闭合
组件的名字可以在“kebab-case”和“CamelCase”来切换:官方建议组件名字基于CamelCase命名,渲染的时候基于kebab-case模式使用!
插槽的作用
插槽分为了默认插槽、具名插槽、作用域插槽,
默认插槽:只需要在调用组件<test><test></test></test>
内插入我们想要的插入的html代码,会默认放到组件源代码的<slot name="default"></slot>
插槽中
组件内部 slot预留位置 默认name:default <slot></slot> 调用组件的时候 //只有一个的时候可以不用template包裹 <test> <div>头部导航</div> </test>
具名插槽:组件中预设好多插槽位置,为了后期可以区分插入到哪,我们把插槽设置名字
<test><test></test></test>
内自己写的代码,我们用template包裹代码,并把v-slot:xxx写在template上,这时就会将xxx里面的代码,包裹到组件源代码的<slot name="”xxx“"></slot>
的标签中 <slot name="xxx"></slot>
默认名字是default组件内部 <slot> 默认名字是default 调用组件:需要把v-slot写在template上 <template> ... </template> <template> ... </template> v-slot可以简写为#:#xxx</slot>
作用域插槽:把组件内部定义的数据,拿到调用组件时候的视图中使用
组件中data内的数据只能在本模块中使用,如果想让调用组件的插槽也能获取数据,就需要对组件内对的slot做bind绑定数据,调用组件的template标签做#top="AAA"
,获取数
==组件内部==: <slot name="top" :list="list" :msg="msg"></slot>
==调用组件==: <template #top="AAA"></template>
v-slot="AAA"
或:default="AAA"
获取数据组件内部 <slot></slot> 把组件中的list赋值给list属性,把msg赋值给msg属性,插槽中提供了两个作用域属性:list/msg 调用组件 <template></template> 定义一个叫做AAA的变量,来接收插槽中绑定的数据 AAA={ list:[...], msg:... }
调用组件的时候
每创建一个组件其实相当于创建一个自定义类,而调用这个组件就是创建VueCommponent(或者Vue)类的实例
组件中的script中存在的状态值和属性值?
_vode
对象的私有属性中(所以状态值和属性值名字不能重复)template标签
中调用状态值和属性值,不需要加this,直接调用状态名或属性名
script标签
中调用状态值和属性值,需要加this调用
vue中的单向数据流
父子组件传递数据时,只能由父组件流向子组件,不能由子组件流向父组件。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
组件传参的分类7种:
1.父组件向子组件传参
父组件向子组件传参:props
VueComponent
的实例第一步:父组件在组件调用标签中自定义属性
//如果想把data中的状态值传递过去需要v-bind绑定 <coma></coma>
注意 如果想把data中的状态值传递过去需要v-bind绑定
第二步:子组件通过props接收(数组,对象)
// props的值是只读的 能改,会报错 <input> //数组格式 props:["msg","num"] //对象格式 props: { msg: { //传参类型必须是字符串 type: String, //必须传参 required: true, }, num: { type: Number, //如果不传参默认是102 default: 102, }, }, //---------------------------------------- //用自定义变量numa接收num,然后页面使用numa(解决只读问题) <h1>我是子组件 coma------{{ msg }}----{{ numa }}</h1> <input> props: ["msg", "num"], data() { return { numa: this.num, }; },
2.子组件向父组件传参
子组件向父组件传参,基于==发布订阅(@xxx给子组件标签自定义事件、$emit)==
第一步:父组件在调用子组件的标签上需要自定义一个事件,这个事件及绑定的方法就会添加到子组件的事件池中:底层实质上是调用了this.$on("myEvent",fn)
<coma></coma> methods: { getData() {}, },
第二步:子组件用this.$emit()接受(this.$emit(myEvent,参数1,参数2)), 参数可以是子组件的,顺便传给父组件,实现子组件向父组件传值
<button>向父组件发送数据</button> data() { return { flag: "你很美", n: 101, }; methods: { goParentData() { //执行父组件自定义的事件 this.$emit("myEvent", this.flag, this.n); }, },
第三步:父组件使用传递过来的数据
data() { return { n: 0, flag: "", }; }, methods: { getData(...parans) { console.log(parans); //传递过来的是数组 this.n = parans[0]; this.flag = parans[1]; }, },
3.组件之间相互传参 原生事件法 (发布订阅)
b--->c发送数据
第一步:全局的main.js中创建一个全局的EventBus,挂载 vue的原型上 this.$bus
$bus.$on()
绑定的事件函数,在哪个vue实例上都可以基于$bus.$empty()
执行,还可以传值//创建一个全局的 Eventbus let Eventbus=new Vue(); //挂载 vue的原型上 后期基于this.$bus Vue.prototype.$bus=Eventbus;
第二步:comc向事件池中绑定事件:this.$bus.$on("事件名",函数)
created() { //向事件池中添加方法 this.$bus.$on("myEvent", () => {}); },
第三步:comb从事件池中获取事件函数并执行:this.$bus.$emit("事件名",想传的参数)
<button>发送数据给comc</button> data() { return { msg: "我是comb", }; methods: { send() { //执行事件池中的方法,并且传参 this.$bus.$emit("myEvent", this.msg); },
第四步 comc使用传递过来的数据
<h1>组件 comc----{{ msg }}</h1> data() { return { msg: "", }; //创建之后的钩子函数向事件池中添加方法 created() { //向事件池中添加方法 this.$bus.$on("myEvent", (value) => { console.log(value); this.msg = value; }); },
4.祖先和后代相互传参
data() { return { title: "我是about祖先", }; }, provide() { return { title: this.title, }; },
第二步:后代使用inject属性接受祖先中的参数,inject是data中的数据,是数组类型
inject: ["title"],因为inject是数组类型,所以它符合如果数据项不是对象类型,则不做劫持,如果数据项是对象,则这个对象中的属性会做劫持。
data() { return { title: "我是about祖先", }; }, //祖先 传递的title是非响应式 provide() { return { title: this.title, }; }, //------------------------------ data() { return { //obj非响应式 obj: { //title是响应式 title: "我是about祖先", }, }; }, //祖先 传递的参数失去响应式,但里面的值会是响应式 provide() { return { obj: this.obj, }; },
vue的实例中存在一些属性能够获取不同关系的元素,获取之后就可以基于这个元素获取其中的数据或方法了:
created() { console.log(this.$parent.title); },
this.$children[n]
:获取第n个子元素的vm实例mounted() { console.log(this.$children[0].msg); },
this.$root
:获取根元素的vm实例(main.js中new 的Vue实例)et mv = new Vue({ router, data() { return { rootmsg: "我是草根" } }, render: h => h(App) }).$mount('#app') --------------------- mounted() { console.log(this.$root.rootmsg); },
this.$refs
:this的子元素中需要定义ref
属性:比如ref="xxx"
:this.$refs.xxx
获取的是DOM对象this.$refs.xxx
获取的是子组件的vm实例//获取的是dom元素 <div>11111</div> mounted() { console.log(this.$refs.one); }, ----------------------------------- 获取的是组件 <comb></comb> mounted() { console.log(this.$refs.b); }, //如果不是组件获取的就是dom元素,如果是组件,获取的就是组件的实例
重点:父组件更新默认不会触发子组件更新,但是**==如果子组件中绑定调用了父组件的数据aaa,父组件的aaa数据更新触发重新渲染时,使用aaa数据{{$parent.aaa}}的子组件也会触发更新==**
一、父子组件生命周期执行过程:
beforeCreated
created
beforeMount
beforeCreate
created
beforeMount
mounted
mounted
二、子组件更新过程:
berforeUpdate
berforeUpdate
updated
updated
三、父组件更新过程:
berforeUpdate
updated
四、父组件销毁过程:
beforeDestory
beforeDestory
destoryed
destoryed
@xxx.native
: 监听组件根元素的原生事件。
例子:<my-component></my-component>
原理:在父组件中给子组件绑定一个==原生(click/mouseover...)==的事件,就将子组件变成了普通的HTML标签,不加'. native'父组件绑定给子组件标签的事件是无法触发的
虚拟DOM
虚拟DOM对象:_vnode
,作用:
第一步:vue内部自己定义的一套对象,基于自己规定的键值对,来描述视图中每一个节点的特征:
- tag标签名
- text文本节点,存储文本内容
- children:子节点
- data:属性
vue-template-compiler
去渲染解析 template 视图,最后构建出上述的虚拟DOM对象组件库
element-ui
:饿了么antdv
:蚂蚁金服iview
:京东
- Element - The world's most popular Vue UI framework
- ==vue2.xx==:elemnetui
- ==vue3.xx==:element plus
如何在项目中使用功能性组件?
==第一步==:安装element-ui
:$npm i element-ui -s
==第二步==:导入:
完整导入:整个组件库都导入进来,想用什么直接用Vue.use(xxx)即可
缺点:如果我们只用几个组件,则无用的导入组件会造成项目打包体积变大[不好],所以项目中推荐使用按需导入
按需导入:
1、需要安装依赖$ npm install babel-plugin-component
样式私有化
在Vue中我们基于scoped设置样式私有化之后:
会给组件创建一个唯一的ID(例如:data-v-5f109989
)
在组件视图中,我们编写所有元素(包含元素调用的UI组件),都设置了这个ID属性;但是我们调用的组件内部的元素,并没有设置这个属性!!
<div> <button> <span>新增任务</span> </button> </div>
而我们编写的样式,最后会自动加上属性选择器:
.task-box { box-sizing: border-box; ... } ---------编译后成为:--------- .task-box[data-v-5f1969a9]{ box-sizing: border-box; }
/deep/
:/deep/.el-textarea__inner, /deep/.el-input__inner{ border-radius: 0; }
在真实项目中,我们会把数据请求和axios的二次封装,都会放到src/api路径下进行管理
//main.js import api from '@/api/index'; // 把存储接口请求的api对象挂载搭配Vue的原型上: 后续在各个组件基于this.$api.xxx()就可以发送请求了,无需在每个组件中再单独导入这个api对象。 Vue.prototype.$api=api;
【相关推荐:javascript视频教程、web前端】
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der beiden Kerne von VUE: reaktionsfähige und komponentenbasierte Entwicklung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!