Home>Article>Web Front-end> Detailed explanation of the two cores of VUE: responsive and component-based development
This article brings you relevant knowledge aboutvue, which mainly introduces the two core issues about vue, responsiveness and componentization. Let’s take a look at it together. I hope Helpful to everyone.
【Related recommendations:javascript video tutorial,vue.js tutorial】
The Object.defineProperty(obj, prop, descriptor)
method will directly define a new property on an object, or modify an existing property of an object, and return this object.
obj——The object to define the attribute
prop——The name or Symbol of the attribute to be defined or modified
descriptor——Object, the attribute descriptor to be defined or modified
// descriptor{ value: undefined, // 属性的值 get: undefined, // 获取属性值时触发的方法 set: undefined, // 设置属性值时触发的方法 writable: false, // 属性值是否可修改,false不可改 enumerable: false, // 属性是否可以用for...in 和 Object.keys()枚举 configurable: false // 该属性是否可以用delete删除,false不可删除,为false时也不能再修改该参数}
Ordinary properties added through assignment operations are enumerable and will be enumerated when enumerating object properties (for...in or Object.keys method). You can change these The values of attributes can also be deleted. This method allows modifying the default extra options (or configuration).
By default, property values added using Object.defineProperty() are immutable.
Example:
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)
The above method implements data response, but there is a big problem. When we trigger set once, we need the entire page Re-render, however this value may only be used in a certain component.
So get and set are optimized:
Object.defineProperty(data, key, { get: function(){ dep.depend() // 这里进行依赖收集 return value }, set: function(newValue){ value = newValue // render() dep.notify() // 这里进行virtualDom更新,通知需要更新的组件render }});
dep is a class that Vue is responsible for managing dependencies
Supplement: Vue cannot detect the addition or addition of property Remove. Since Vue will perform getter/setter conversion on the property when initializing the instance, the property must exist on the data object in order for Vue to convert it into a responsive
const vm = new Vue({ data: { a: 1 }})// vm.a是响应式的vm.b = 2// vm.b是非响应式的
Vue handles changes in the array and directly triggers changes in the view through the subscript. Only push, shift and other methods can be used, and the array cannot use Object.defineProperty()
In fact, Vue uses the decorator mode to rewrite the array. These methods
Object.create(proto,[propertiesObject])
method is to create a new object, using an existing object to provide the__proto__
of the newly created object.
proto
——The prototype object of the newly created object;propertiesObject
——Optional, the type is an object, if the parameter is specified and is not undefined , the passed-in object's own enumerable properties (that is, its own defined properties, rather than the enumerated properties on its prototype chain) will add the specified property value and corresponding property descriptor to the newly created object
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"
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'}
Decorator mode in vue
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__ = arrob
For the above method of the new object arrob, we directly assign the value. This will cause a problem, that is, the user may accidentally change our object. So we can use the Object.defineProperty we talked about earlier to avoid this problem. We create a public method def specifically to set properties whose values cannot be modified
function def (obj, key, value) { Object.defineProperty(obj, key, { // 这里我们没有指定writeable,默认为false,即不可修改 enumerable: true, configurable: true, value: value, });}// 数组方法重写改为arr.forEach(function(method){ def(arrob, method, function () { arraypro[method].apply(this, arguments) // 重写时先调用原生方法 dep.notify()// 并且同时更新 })})
Data attribute:It contains the location of a data value, where the data value can be read and written
Four descriptors of the data attribute:
Meaning | |
---|---|
configurable | means Can the property be deleted by delete to redefine the property? Can the properties of the property be modified? Can the property be modified as an accessor property? The default is true |
enumerable | Indicates whether the attribute can be returned through a for-in loop, the default is true |
writable | Indicates whether the value of the attribute can be modified, the default is true |
value | Contains the data value of this attribute, the default is undefined |
Accessor attribute:This attribute does not contain a data value, but a pair of get and set methods. When reading and writing accessor attributes, these two methods are used to perform operations.
Four descriptors for accessor properties:
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页面入口相当于首页,写好的组件都导入到这个里面
{{ msg }}
私有组件(使用的时候首先进行导入,然后注册,这样视图中就可以调用组件进行渲染了)
需要使用私有组件的时候,需要先导入import Test from "./Test.vue";
然后注册:这样就可以调用组件进行渲染了
//3.使用组件:可以使用单闭合或双闭合
创建全局组件
1. 创建一个局部组件
{{ msg }}
@2 在main.js入口中,导入局部组件Vote,把其注册为全局组件
import Vote from './Vote.vue'; Vue.component('Vote', Vote)
@3 这样在任何组件(视图中),无需基于components注册,直接可以在视图中调用
调用组件的方式
调用组件的时候,可以使用:
双闭合
双闭合的方式可以使用插槽slot
@1 在封装的组件中,基于
标签预留位置
@2 调用组件的时候,基于双闭合的方式,把要插入到插槽中的内容写在双闭合之间
单闭合
组件的名字可以在“kebab-case”和“CamelCase”来切换:官方建议组件名字基于CamelCase命名,渲染的时候基于kebab-case模式使用!
插槽的作用
插槽分为了默认插槽、具名插槽、作用域插槽,
默认插槽:只需要在调用组件
内插入我们想要的插入的html代码,会默认放到组件源代码的
插槽中
组件内部 slot预留位置 默认name:default调用组件的时候 //只有一个的时候可以不用template包裹 头部导航
具名插槽:组件中预设好多插槽位置,为了后期可以区分插入到哪,我们把插槽设置名字
内自己写的代码,我们用template包裹代码,并把v-slot:xxx写在template上,这时就会将xxx里面的代码,包裹到组件源代码的
的标签中
默认名字是default组件内部默认名字是default 调用组件:需要把v-slot写在template上 ... ... v-slot可以简写为#:#xxx
作用域插槽:把组件内部定义的数据,拿到调用组件时候的视图中使用
组件中data内的数据只能在本模块中使用,如果想让调用组件的插槽也能获取数据,就需要对组件内对的slot做bind绑定数据,调用组件的template标签做#top="AAA"
,获取数
==组件内部==:
==调用组件==:
v-slot="AAA"
或:default="AAA"
获取数据组件内部把组件中的list赋值给list属性,把msg赋值给msg属性,插槽中提供了两个作用域属性:list/msg 调用组件 定义一个叫做AAA的变量,来接收插槽中绑定的数据 AAA={ list:[...], msg:... }
调用组件的时候
每创建一个组件其实相当于创建一个自定义类,而调用这个组件就是创建VueCommponent(或者Vue)类的实例
组件中的script中存在的状态值和属性值?
_vode
对象的私有属性中(所以状态值和属性值名字不能重复)template标签
中调用状态值和属性值,不需要加this,直接调用状态名或属性名script标签
中调用状态值和属性值,需要加this调用vue中的单向数据流
父子组件传递数据时,只能由父组件流向子组件,不能由子组件流向父组件。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
组件传参的分类7种:
1.父组件向子组件传参
父组件向子组件传参:props
VueComponent
的实例第一步:父组件在组件调用标签中自定义属性
//如果想把data中的状态值传递过去需要v-bind绑定
注意 如果想把data中的状态值传递过去需要v-bind绑定
第二步:子组件通过props接收(数组,对象)
// props的值是只读的 能改,会报错 //数组格式 props:["msg","num"] //对象格式 props: { msg: { //传参类型必须是字符串 type: String, //必须传参 required: true, }, num: { type: Number, //如果不传参默认是102 default: 102, }, }, //---------------------------------------- //用自定义变量numa接收num,然后页面使用numa(解决只读问题)我是子组件 coma------{{ msg }}----{{ numa }}
props: ["msg", "num"], data() { return { numa: this.num, }; },
2.子组件向父组件传参
子组件向父组件传参,基于==发布订阅(@xxx给子组件标签自定义事件、$emit)==
第一步:父组件在调用子组件的标签上需要自定义一个事件,这个事件及绑定的方法就会添加到子组件的事件池中:底层实质上是调用了this.$on("myEvent",fn)
methods: { getData() {}, },
第二步:子组件用this.$emit()接受(this.$emit(myEvent,参数1,参数2)), 参数可以是子组件的,顺便传给父组件,实现子组件向父组件传值
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("事件名",想传的参数)
data() { return { msg: "我是comb", }; methods: { send() { //执行事件池中的方法,并且传参 this.$bus.$emit("myEvent", this.msg); },
第四步comc使用传递过来的数据
组件 comc----{{ msg }}
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元素11111mounted() { console.log(this.$refs.one); }, ----------------------------------- 获取的是组件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
: 监听组件根元素的原生事件。
例子:
原理:在父组件中给子组件绑定一个==原生(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属性;但是我们调用的组件内部的元素,并没有设置这个属性!!
而我们编写的样式,最后会自动加上属性选择器:
.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前端】
Indicates whether the attribute can be redefined by deleting the attribute through delete, whether the characteristics of the attribute can be modified, or whether the attribute can be modified as an accessor attribute. The default is false | |
indicates whether the attribute can be returned through a for-in loop. The default is false | |
The function called when reading properties, the default value is undefined | |
The function called when writing properties, the default value is undefined |
The above is the detailed content of Detailed explanation of the two cores of VUE: responsive and component-based development. For more information, please follow other related articles on the PHP Chinese website!