Vue的组件和数据流管理

本文深入剖析Vue框架的特点,包括高效的数据追踪机制、灵活的v-model应用、作用域问题的解决及五种数据流管理方式。此外,还探讨了组件拆分的原则和注意事项。

一、灵活的Vue

1. 高效的追踪机制

      Vue通过Object.defineProperty定义数据的存取描述符(set和get),来追踪数据变化。

      在Vue组件初始化时,data下的属性会被循环遍历添加上set和get,直到所有的子孙属性都被添加完。当data的某一属性值发生了变化(执行set)时,它就会向订阅了该数据的组件发布通知,去更新组件。

      在set里,组件可以订阅该数据变化的通知。

      set和get叫对象的存取描述符,与存取描述符对应的叫数据描述符。二者不能共存,默认情况下,是数据描述符。如下:

      Vue在初始化的时候,会删掉数据描述符,加上存取描述符。这种方式追踪数据变化,比React更高效。

      React用比较引用来追踪数据变化的方式。在setState的时候,React会用新的state直接替换掉旧state。

      即便不改变数据的数值属性,只要数据的引用地址发生变化了,React组件也会触发更新。如果不加处理,React组件会多很多次更新。

      如下面,每次执行handleClick,即便没有改变数据的数值,(但引用地址发生变化了),组件也会重新渲染。这个渲染是没必要的。

      为了避免组件的这种没意义的渲染,React推出了PureComponent(纯组件),帮助开发者做一层数据的浅比较,仅引用地址发生变化的,就没必要重新渲染了。开发的时候,还可以拦截shouldComponentUpdate生命周期钩子,显式地干预是否需要重新渲染组件。

      在得到通知数据变化时,React和Vue都采用了vDOM diff算法,各自去更新组件。在追踪数据变化上,Vue比React更加高效。

2. 令人尴尬的v-model

      Vue无法检测对象属性的增删变化和数组索引长度的变化,在定义object类型的数据时,一般把它下面的属性也定义了。但是,JS是一门动态语言,可以不预先定义对象,并且任意地操作对象的属性。结合v-model使用时,object类型数据的属性也可以不用预先定义。

      例如,在表单型的组件里,一个常见的行为,不给formData定义title、type等这些属性,v-model会自动响应用户输入,并添加属性到formData上。

<template>
    <div>
        <input v-model="formData.title" placeholder="请输入标题" />
        <select v-model="formData.type">
            <option value="1">类别</option>
        </select>
    </div>
</template>
data() {
    return {
        formData: {}
    }
}
复制代码

      props是组件间自上向下通信数据的方式,子组件逆向修改props,Vue会警告应当避免直接操作props,用data或者computed属性代替,修改值也不会被传递到父组件。

      然而,借助v-model,object类型props值,也可以由子组件传递给父组件。简单类型的props值仍旧无法修改。

      原来,v-model内部检测到绑定的是对象的key时(indexOf('.') > 0),会调用set方法,更新属性到对象上,并给组件添加了订阅事件。

Vue1.0采用的是双向数据绑定,Vue2.0采用的是单向数据流,即只能父对子通信,子对父通信要用回调函数的方式。 Vue3.0以前无法检测到数据属性的增删变化,常用Vue.prototype.$set和JSON.parse(JSON.stringify)去纠正这一点。未来Vue3.0将用proxy代替Object.defineProperty监听数据变化,proxy将直接监听对象,而不是监听属性,它可以检测到数组和对象的增删变化。

3. 作用域问题

      JS开发有三大难点,原型、闭包和作用域。在es6 module的帮助下,原型和闭包的坑已经不多了,剩下的作用域成了最常遇到的坑。

      在React当中,给组件添加事件必须要修正函数的执行作用域。像下面这样,onClick是定义在全局作用域上的,它的this就是undefined。因为js是静态作用域语言,它作用域是定义时确定的。在这里,要拿到React组件实例内部的state,所有bind函数的作用域到组件实例上。

      在每一个事件函数上,不厌其烦地绑定函数的作用域,这在Vue里是根本不care的。Vue会在组件实例化的时候,把methods里的函数作用域都绑定到组件实例上

      CSS的作用域问题,也是困扰前端开发者的大问题。命名冲突的className会带来意想不到的惊喜,采用提升权重的方式,可以提高选择器的竞争力,但这会让项目更加臃肿,CSS管理混乱不堪,维护开发非常麻烦。

      Vue采用类似shadow DOM的方式对CSS进行封装,添加了scoped属性后,CSS只作用在组件内部,组件之间的CSS不会互相影响。

      React采用css in js、css module、style-component的方式封装CSS,都没有Vue好用。

      React的css in js写法

4. Vue的灵活性

      相比React,Vue和JS这门语言契合度是最高的,它没有科班化的数据流管理,没有刀耕火种的JS编写方式。

      Vue的追踪数据机制、v-model双向绑定和js/css作用域,都很巧妙的利用JS作为一门动态语言的优势。这使Vue成为一门非常容易上手的技术框架,在快节奏、频繁的迭代的开发需求中占有一席之地。我在商业产品部一年多一共开发和维护了9个项目,包括1个react、1个angular和7个vue项目。Vue非常容易上手,有些简单的需求,就让后端同学代劳了。

      一直以来,都有一个争议点,Vue适合做小型项目,React适合开发大型项目。以前,Vue饱受诟病的是数据流管理,实际上,现在Vue2和React已经相差不大了,借助v-model和vuex,Vue甚至比React更胜一筹。未来Vue3会用Typescript写,构建项目将会更加稳健。

二. 5种数据流管理方式

1. props + emit回调

react和vue都在用的组件通信方式之一,简约又简单。

依赖组件父子关系。

如下,子组件实例化过程中,如果发现父组件订阅了子组件的事件,就会把订阅的事件添加到events列表里,以此允许开发者来发布事件,即$emit事件。

添加订阅者到event队列。

如果是祖孙级组件和兄弟级组件,它们之间的通信就需要很多个emit回调在组件之间传递。这种通信方式会组件耦合性太强,程序稳定性降低。

2. props + eventBus

适用于所有组件,不依赖组件之间的嵌套关系。

在EventBus里,实例化一个Vue实例作为一个观察者,所有的组件都作为订阅者。

// EventBus.js
import Vue from 'vue';

export default new Vue();
复制代码
// 添加订阅者
eventBus.$on('reset-preview', this.closeHandler);
// 发布通知
eventBus.$emit('preview');
复制代码

Vue内部实现了on和emit两个方法,on方法添加订阅者到队列里,emit在事件变化时,发布通知给订阅者。

EventBus虽然不依赖组件嵌套关系,但是数据流向是随意的,对于复杂的业务需求,难以支撑。所以需要一个中心化的观察者,观察数据变化,自上而下组件响应数据变化,自下而上更新数据变化到观察者中心。

emit回调和eventBus的区别是什么?

emit回调是强调组件关系,父组件是订阅者,子组件是发布者。 eventBus不关心组件关系,eventBus实例化的一个实例是发布者,组件都是订阅者。

3. vuex

优点:

  1. 解决所有祖孙级和父子级组件嵌套的数据流问题
  2. 缓存数据,减少http请求次数
  3. 单向数据流,数据流向更清晰
  4. 减少props和回调函数,组件之间解耦

使用vuex需要注意的点

  1. 与后台约束性强的数据,不宜写到vuex里。

vuex和vue一样都是通过劫持setter/getter追踪数据变化的,所以vuex也不能检测到array和object的增删。

例如表单型的数据,需要增加字段,向后台提交。vuex无法处理属性增删,$store.commit到store里,store里的数据不会更新。

  1. 在数据生命周期结束时,清空store里缓存的数据。

例如,一个复杂类型的数据,从服务端请求出来存到vuex里,然后在多个组件之间传递,在数据处理结束之后,又提交给服务端。此时,应该清除vuex保存的数据,以便在下一次打开页面时,vuex里的数据是干净的。

一般,在组件的created生命周期检查vuex缓存是否存在,没有缓存,则重新拉数据。在组件生命周期结束前,重置store,清除数据缓存。

什么情况下适合用vuex呢?

  1. 在需要缓存数据的时候
  2. emit实在解决不了时候

只有在用React实在解决不了的时候,才用Redux。--------Redux作者

过度使用vuex,将使项目变得臃肿,组件之间耦合度增加。

4. route

传统型,适用于各页面之间的数据传递。对于一些需要粘贴url让其他人访问的需求,需要在router里加上必须参数,而不能在vuex里。

比如筛选列表页,媒体流量桶管理页。这种页面结构相同,种类又众多。不能拆分成多个页面,但又需要独立的页面展示效果,适合把数据保存在route里。

使用router传参时,需要注意:

  1. 组件生命周期钩子(确保必需的route参数) 1.1 对于依赖router保存数据的页面,注意在vue组件生命周期钩子里加校验 1.2 父子组件的渲染顺序是,父created ---> 子created ---> 子mounted ---> 父mounted,所以在created生命周期里校验route参数,确保组件render以后有正确的数据显示。

1.3 watch route变化,及时更新数据。

5. 组件实例方法调用

用于嵌套的表单型组件

比如创建父任务时,父任务表单里同时又可以添加子任务,子认为有分别可以添加不同的任务奖励规则。 比如创建订单时,可以添加广告组、广告计划、广告创意,广告创意里可以添加各类素材。 这些嵌套复杂的表单型组件,比较适合用这个。

三. 组件拆分原则

1. 善用slot,开闭规则。

修改封闭,扩展开放。

2. 功能拆分,单一职责规则

功能组件还是ui组件?简化使用。 比如pagnition处理total < 1时,不显示分页器 比如筛选列表组件,保留UI部分,把提交按钮用slot写进去 功能组件,比如upload组件

3. 最少知道原则

尽可能减少外部依赖 组件内的功能点,对外界的依赖越少越好。 越简单越好。 参考设计的最高境界,Kiss规则。

四. 一些注意事项

1. 利用$next获取组件渲染之后的dom

2. v-for的key

3. 覆写复杂类型的props

4. ajax的封装

创建axios的实例对ajax的封装,减少处理回调的代码量。直接修改axios的拦截器会污染整个工程ajax调用规则。对于多次import进来的文件,webpack只会打包一次。

转载于:https://juejin.im/post/5c9b45906fb9a070f8407bd9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值