VUE两大核心之响应式与组件化开发详解

广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买

VUE两大核心之响应式与组件化开发详解

本篇文章给大家带来了关于vue的相关知识,其中主要介绍了关于vue的两大核心,响应式与组件化的相关问题,下面一起来看一下,希望对大家有帮助。

【相关推荐:javascript视频教程、vue.js教程】

vue2.0 响应式1. 对象的响应式1.1 Object.defineProperty

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c
登录后复制1.2 set和get
// 模拟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是非响应式的
登录后复制2. 数组的响应式

vue 中处理数组的变化,直接通过下标触发视图的更改,只能使用push、shift等方法,而数组不能使用Object.defineProperty() 其实 Vue用装饰者模式来重写了数组这些方法

Object.create(proto,[propertiesObject]) 方法是创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

proto——新创建对象的原型对象;propertiesObject ——选填,类型是对象,如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符

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 overwrittenme.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'}
登录后复制

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
登录后复制

上面对于新对象arrob的方法,我们是直接赋值的,这样会有一个问题,就是用户可能会不小心改掉我们的对象,所以我们可以用到我们前面讲到的Object.defineProperty来规避这个问题,我们创建一个公用方法def专门来设置不能修改值的属性

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()// 并且同时更新  })})
登录后复制3. 补充:对象的数据属性和访问器属性

数据属性: 它包含的是一个数据值的位置,在这可以对数据值进行读写

数据属性的四个描述符:

含义configurable表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为trueenumerable表示能否通过for-in循环返回属性,默认为truewritable表示能否修改属性的值,默认为truevalue包含该属性的数据值,默认为undefined

访问器属性: 这个属性不包含数据值,包含的是一对get和set方法,在读写访问器属性时,就是通过这两个方法来进行操作处理的。

访问器属性的四个描述符:

含义configurable表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为falseenumerable表示能否通过for-in循环返回属性,默认为falseget在读取属性时调用的函数,默认值为undefinedset在写入属性时调用的函数,默认值为undefinedvue3.0数据响应

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..」

+ 一般UI组件库提供的功能组件就够用了 + 偶尔UI组件库中不存在的,才需要自己封装「难点」 + 我们经常会把功能型组件进行二次封装(结合自己项目的业务逻辑)「特殊亮点」

业务型组件

+ 通用业务型组件「好多页面都需要用到的,我们把其封装成为公共的组件」 + 普通组件

以后开发项目,拿到设计稿的第一件事情:划分组件「按照功能版块划分、本着复用性原则,拆的越细越好(这样才能更好的实现复用)」

组件的创建及使用

创建一个 Xxx.vue 就是创建一个vue组件{局部组件、私有组件},组件中包含:结构、样式、功能

结构:基于template构建

+ 只能有一个根元素节点(vue2) + vue的视图就是基于template语法构建的(各种指令&小胡子...),最后vue会把其编译为真实的DOM插入到页面指定的容器中 首先基于 vue-template-compiler 插件把template语法编译为虚拟DOM「vnode」 其次把本次编译出来的vnode和上一次的进行对比,计算出差异化的部分「DOM-DIFF」 最后把差异化的部分变为真实的DOM放在页面中渲染

样式:基于style来处理

+ lang="less" 指定使用的CSS预编译语言「需要提前安装对应的loader」+ scoped 指定当前编写的样式是私有的,只对当前组件中的结构生效,后期组件合并在一起,保证样式之间不冲突

功能:通过script处理

+ 导出的这个对象是VueComponent类的实例(也是Vue的实例):对象 -> VueComponent.prototype -> Vue.prototype + 在对象中基于各种 options api 「例如:data、methods、computed、watch、filters、生命周期函数...」实现当前组件的功能 + 在组件中的data不再是一个对象,而是一个“闭包” + 各个组件最后会合并在一起渲染,为了保证组件中指定的响应式数据是“私有的”,组件之间数据即使名字相同,也不会相互污染...所以需要基于闭包来管理

注意;App.vue页面入口相当于首页,写好的组件都导入到这个里面

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c0
登录后复制

私有组件(使用的时候首先进行导入,然后注册,这样视图中就可以调用组件进行渲染了)

需要使用私有组件的时候,需要先导入import Test from "./Test.vue";

然后注册:这样就可以调用组件进行渲染了

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c1
登录后复制

创建全局组件

1. 创建一个局部组件

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c2
登录后复制

@2 在main.js入口中,导入局部组件Vote,把其注册为全局组件

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c3
登录后复制

@3 这样在任何组件(视图中),无需基于components注册,直接可以在视图中调用

<template> <Vote></Vote></template>

插槽

调用组件的方式

调用组件的时候,可以使用:

双闭合 <Test></Test>

双闭合的方式可以使用插槽slot

@1 在封装的组件中,基于 <slot> 标签预留位置 @2 调用组件的时候,基于双闭合的方式,把要插入到插槽中的内容写在双闭合之间

单闭合 <Test/>

组件的名字可以在“kebab-case”和“CamelCase”来切换:官方建议组件名字基于CamelCase命名,渲染的时候基于kebab-case模式使用!

插槽的作用

让组件具备更高的复用性(或扩展性)我们封装好一个组件,把核心部分都实现了,但是我们期望用户调用组件的时候,可以自定义一些内容,防止在已经封装好的组件内部:

插槽分为了默认插槽、具名插槽、作用域插槽

默认插槽:只需要在调用组件<Test><Test>内插入我们想要的插入的html代码,会默认放到组件源代码的<slot name="default"></slot>插槽中

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c4
登录后复制

具名插槽:组件中预设好多插槽位置,为了后期可以区分插入到哪,我们把插槽设置名字

在调用组件<Test><Test>内自己写的代码,我们用template包裹代码,并把v-slot:xxx写在template上,这时就会将xxx里面的代码,包裹到组件源代码的<slot name=”xxx“></slot>的标签中==组件内部:== <slot name="xxx"> 默认名字是default==调用组件:==需要把v-slot写在template上
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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c5
登录后复制

作用域插槽:把组件内部定义的数据,拿到调用组件时候的视图中使用

组件中data内的数据只能在本模块中使用,如果想让调用组件的插槽也能获取数据,就需要对组件内对的slot做bind绑定数据,调用组件的template标签做#top="AAA",获取数

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c6
登录后复制把组件中的list赋值给list属性,把msg赋值给msg属性,插槽中提供了两个作用域属性:list/msg
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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c7
登录后复制定义一个叫做AAA的变量,来接收插槽中绑定的所有数据(对象格式)如果插槽名是default则使用v-slot="AAA":default="AAA"获取数据
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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c8
登录后复制

组件传参

调用组件的时候

每创建一个组件其实相当于创建一个自定义类,而调用这个组件就是创建VueCommponent(或者Vue)类的实例

实例(this)->VueComponent.prototype->Vue.prototype->Object.prototype当前实例可以访问Vue.prototype上的一些公共属性和方法

组件中的script中存在的状态值和属性值?

==状态值==:data中的数据值称为状态值==属性值==:props中的数据值称为属性值状态值和属性值是直接挂载到_vode对象的私有属性中(所以状态值和属性值名字不能重复)我们在视图template标签中调用状态值和属性值,不需要加this,直接调用状态名或属性名我们在功能script标签中调用状态值和属性值,需要加this调用==computed(计算属性)==:也是挂载实例上的,所以他们三个都不能重名

vue中的单向数据流

父子组件传递数据时,只能由父组件流向子组件,不能由子组件流向父组件。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

组件传参的分类7种:

父组件向子组件传参:props子组件向父组件传参:发布订阅(@xxx给子组件标签自定义事件、$emit)组件相互传参(兄弟):发布订阅(on、emit)【2和3传参是一种】祖先向后代传参(provide[提供],inject[接收])vue实例属性传参(parent、children[n]、root、refs)vuexlocalStorage sessionStorage

1.父组件向子组件传参

父组件向子组件传参:props

我们传给组件的值,默认是==字符串类型==的,比如msg如果想传==数字类型==的,则需要调用v-bind或冒号的形式传值我们每调用一次coma,都会生成一个独立的VueComponent的实例

第一步:父组件在组件调用标签中自定义属性

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 = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c9
登录后复制

注意 如果想把data中的状态值传递过去需要v-bind绑定

第二步:子组件通过props接收(数组,对象)

props中的属性是只读的,子组件不能修改这些值,否则会报错解决只读问题:用自定义变量接收传递过来的值,页面使用自定义变量props可以是对象或数组类型,对象可以对数据做校验,数组不能
// 模拟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)0
登录后复制

2.子组件向父组件传参

子组件向父组件传参,基于==发布订阅(@xxx给子组件标签自定义事件、$emit)==

第一步:父组件在调用子组件的标签上需要自定义一个事件,这个事件及绑定的方法就会添加到子组件的事件池中:底层实质上是调用了this.$on("myEvent",fn)

// 模拟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)1
登录后复制

第二步:子组件用this.$emit()接受(this.$emit(myEvent,参数1,参数2)), 参数可以是子组件的,顺便传给父组件,实现子组件向父组件传值

// 模拟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)2
登录后复制

第三步:父组件使用传递过来的数据

// 模拟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)3
登录后复制

3.组件之间相互传参 原生事件法 (发布订阅)

b--->c发送数据

c向事件池中添加方法(自定义方法):$onb执行方法把参数传过去:$emit

第一步:全局的main.js中创建一个全局的EventBus,挂载 vue的原型上 this.$bus

作用:将EventBus看作定义在公有属性上的事件池(事件公交),之后基于这个$bus.$on()绑定的事件函数,在哪个vue实例上都可以基于$bus.$empty()执行,还可以传值
// 模拟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)4
登录后复制

第二步:comc向事件池中绑定事件:this.$bus.$on("事件名",函数)

// 模拟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)5
登录后复制

第三步:comb从事件池中获取事件函数并执行:this.$bus.$emit("事件名",想传的参数)

// 模拟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)6
登录后复制

第四步 comc使用传递过来的数据

// 模拟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)7
登录后复制

4.祖先和后代相互传参

第一步:祖先要使用provide方法传参,不是写在methods里面,与methods同级
// 模拟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)8
登录后复制

第二步:后代使用inject属性接受祖先中的参数,inject是data中的数据,是数组类型

inject: ["title"],因为inject是数组类型,所以它符合如果数据项不是对象类型,则不做劫持,如果数据项是对象,则这个对象中的属性会做劫持。

// 模拟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)9
登录后复制vue实例属性传参

vue的实例中存在一些属性能够获取不同关系的元素,获取之后就可以基于这个元素获取其中的数据或方法了:

$parent 获取父元素的数据/方法 获取父元素的整个vm实例子组件可以在任何生命周期函数中获取父元素【父子组件的生命周期
Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});0
登录后复制 $children 获取子元素的数据/方法(mounted钩子函数,要有下标)this.$children[n]:获取第n个子元素的vm实例父组件只能在mounted生命周期函数里或之后获取子元素【父子组件的生命周期
Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});1
登录后复制$root获取根组件的数据/方法this.$root:获取根元素的vm实例(main.js中new 的Vue实例)
Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});2
登录后复制this.$refs:this的子元素中需要定义ref属性:比如ref="xxx":==如果ref定义在DOM标签中==:this.$refs.xxx获取的是DOM对象==如果ref定义在子组件标签中==:this.$refs.xxx获取的是子组件的vm实例
Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});3
登录后复制父子组件的生命周期

重点:父组件更新默认不会触发子组件更新,但是**==如果子组件中绑定调用了父组件的数据aaa,父组件的aaa数据更新触发重新渲染时,使用aaa数据{{$parent.aaa}}的子组件也会触发更新==**

一、父子组件生命周期执行过程

父->beforeCreated父->created父->beforeMount 子->beforeCreate 子->created 子->beforeMount 子->mounted父->mounted

二、子组件更新过程:

父->berforeUpdate 子->berforeUpdate 子->updated父->updated

三、父组件更新过程:

父->berforeUpdate父->updated

四、父组件销毁过程:

父->beforeDestorybeforeDestorydestoryed父->destoryed

扩展------------------------父组件绑定在子组件标签中的事件,是无法触发的,如何解决?

@xxx.native: 监听组件根元素的原生事件。

例子<my-component @click.native="onClick"></my-component>

原理:在父组件中给子组件绑定一个==原生(click/mouseover...)==的事件,就将子组件变成了普通的HTML标签,不加'. native'父组件绑定给子组件标签的事件是无法触发的

虚拟DOM

虚拟DOM对象:_vnode,作用:

第一步:vue内部自己定义的一套对象,基于自己规定的键值对,来描述视图中每一个节点的特征:

tag标签名text文本节点,存储文本内容children:子节点data:属性
第二步:基于vue-template-compiler去渲染解析 template 视图,最后构建出上述的虚拟DOM对象第三步:组件重新渲染,又重新生成一个 _vnode第四步:对比两次的 _vnode. 获取差异的部分第五步:把差异的部分渲染为真实的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属性;但是我们调用的组件内部的元素,并没有设置这个属性!!

Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});4
登录后复制

而我们编写的样式,最后会自动加上属性选择器:

Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});5
登录后复制==组件样式私有化的原理==:设置唯一的属性(组件ID)、组件内部给所有样式后面都加上该属性选择器==问题==:组件内部的元素没有设置这个属性,但是我们编写的样式是基于这个属性选择器在css设置的选择器,==解决==:在组件内部的元素选择器前加/deep/:
Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});6
登录后复制API

在真实项目中,我们会把数据请求和axios的二次封装,都会放到src/api路径下进行管理

Object.defineProperty(data, key, {  get: function(){    dep.depend() // 这里进行依赖收集    return value  },  set: function(newValue){    value = newValue    // render()    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render  }});7
登录后复制

【相关推荐:javascript视频教程、web前端】

以上就是VUE两大核心之响应式与组件化开发详解的详细内容,更多请关注9543建站博客其它相关文章!

广告:SSL证书一年128.66元起,点击购买~~~

9543建站博客
一个专注于网站开发、微信开发的技术类纯净博客。

作者头像
admin创始人

肥猫,知名SEO博客站长,14年SEO经验。

上一篇:uniapp上传图片返回不及时是什么原因
下一篇:html代码怎么写的

发表评论

关闭广告
关闭广告