Redux vs Mobx系列(二):衍生属性

Redux vs Mobx系列(二):衍生属性


考虑这样得一个页面
组件结构javascript

其中money = price * countjava

在设计数据层的时候, 咱们能够:react

var store ={
    price: 0,
    count: 0,
    money: 0
}

这样 咱们的组件 就能够直接从 store里面获取price, count, money,而后展现就能够了,很方便简单,当更新的时候:git

function updatePrice(newPrice, oldStore){
    const price = newPrcie
    const money = price * oldStore.count
    
    return {
        price,
        count: oldStore.count,
        money,
    }
}

function updateCount(newCount, oldStore){
    const count = newCount
    const money = count * oldStore.price
    
    return {
        price: oldStore.price
        count,
        money,
    }
}

如今,咱们业务复杂了:
1521178184987.jpg-61kBgithub

若是store仍是设计以下:redux

var store = {
    inprice: '',
    outprice: '',
    ...
    inmoney: '',
    outmoney: '',
    ...
    taxmoney: '', // 含税金额
    ...
    grossmargin: '', // 毛利率
}

页面组件逻辑依然很简单,获取对应数据展现就能够了。 问题来了,如今我要调整一下 售价 updateInprice。 updateInprice 应该怎么写呢?缓存

function updateInprice(newInprice, oldStore) {
    const inprice = newInprice
    const inmoney = inprice * oldStore.count
    // update 含税金额, 税额, 毛利, 毛利率
    ....
    const grossmargin = ....
}

waht ??我调整一个售价, 须要改这么多??是的, 当您 调整数量, 调整进价, 调整报损量。。。都须要改这么多!!! 还记得[一]()里面mobx的性能隐患吗。
store这么设计,问题不少:app

  1. 更新状态变的错综复杂
  2. 假设最后须要加上 “折后金额”, 那么我须要去updateInprice, updateCount, updateDiscount(修改折扣)方法里 加上对 “折后金额”的处理
  3. 假设我如今去掉 “报损量”这个输入框, 那么我须要到 全部处理 “报损量”的地方,在修改一边

。。。
一句话, 每次状态更新的时候, 我须要保证状态的数据一致性, 状态越多关系越复杂,越难保证, 牵一发动全身。 不出意外, 随着项目的进行需求的变换, 我应该会不停的加班, 不断的写bug, 改bug ---> 陪伴家人的时间变少 ---> 婚姻破裂 ---> 自暴自弃 ---> 郁闷的离开人世。 框架

迫在眉睫!咱们必须尽量的减小维护的状态, 一旦状态足够少,咱们就更容易的保证了数据层的正确 那么根据app = f(store) , 应用就正确了。函数

已上面的例子来看,其中 成本金额, 销售金额... 税额...毛利率 这几个状态都不须要咱们管理, 由于从已知的状态 彻底能够推导出来, 好比:
inmoey = inprice * count; outmoney = outprice * (count - 报损量)
这几个属性 就是衍生属性,能够根据现有的状态或其它计算值衍生出的属性就是衍生属性。

如今咱们在来看以前的这个例子:

/// store
var store = {
    inprice: '',
    outprice: '',
    ...
    tax: ''
}
/// update
function updateInprice(newInprice, store) {
    store.inprice = newInprice
}


/// get compute date
function getInmoney(store){
    return store.inprice * store.count
}
...
function getGrossmargin(store) {
    return  (outprice * (count - 报损量) - inprice * count) / inprice * count
}

如今 状态有12个减小到6个, 并且互相独立, 这样更新也很简单(如代码)。 页面在展现的时候 只须要从store获取数据, 而后调用get方法 获取衍生数据就能够了。

“老板, 我回家陪老婆了。” “好嘞!”

对于数据交互越复杂的应用(注意是 数据交互越复杂), 框架对衍生属性的处理就很是重要了。

mobx

mobx对衍生属性处理的很好,

class OrderLine {
    @observable price = 0;
    @observable amount = 1;

    constructor(price) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }
}

这里的total就是衍生属性

摘录mobx官方文档的一句话:`若是任何影响计算值的值发生变化了,计算值将根据状态自动进行衍生。 计算值在大多数状况下能够被 MobX 优化的,由于它们被认为是纯函数。 例如,若是前一个计算中使用的数据没有更改,计算属性将不会从新运行
`
也就是说 :

@observer
class X extends Component {
    componentDidMount(){
        setInternal(() => {
            this.forceUpdate()
        }, 1000)
    }
    
    render() {
        const gm = this.props.grossmargin // 衍生属性 毛利率
        ...
    }
}

在上面这种render不断的执行状况下(经过forceUpdate)触发, grossmargin并不会从新计算,而是重复使用上一次缓存的值,直到影响grossmargin的状态改变。

不过仍是有两个地方须要注意
1. const { price, amount, total } = orderLine 这样是获取不到 total的
必须 orderLine.total

  1. mobx的compute还有一个毕竟坑的地方, 看下面的例子
import { observable, computed, autorun } from "mobx";

class OrderLine {
    @observable price = 3;
    @observable amount = 1;

    @computed get total() {
        console.log('invoke total')

        if (this.price > 6) {
            return 5 * this.amount
        }

        return this.price * this.amount;
    }
}


const ol = new OrderLine()
ol.price = 5

/*autorun(() => {    // autorun
    console.log('xxxx:', ol.total)
})*/

console.log('xxxx:', ol.total)
console.log('xxxx:', ol.total)
console.log('xxxx:', ol.total)
console.log('xxxx:', ol.total)
console.log('xxxx:', ol.total)

按照以前的说法, 这里虽然屡次引用了ol.total 应该只会打印一次invoke total
。 可是实际状况倒是 打印了5次!!! what ???? 若是咱们把autorun的注释去掉, 再次执行 而后打印了一次invoke total。。。。。只能感叹mobx 处处都是黑魔法。 这个现象在mobx里是合理的, 简单说 就是只有当衍生属性 在observer、 autorun,reaction里使用的时候 才会缓存。 具体请看issues718
不过@observer 修饰的组件 render函数已经被 重写为reaction了, 全部你们在组件的render函数里面是能够为所欲为的使用衍生属性的。

redux

redux自己并无提供对衍生属性的处理。

function mapStateToProps(state) {
    const { inprice, outprice, tax ... } = state
    const inmoney = getInmoney(state)
    const grossmargin = getGrossmargin(state)
    ....
    return {
        inprice,
        outprice,
        tax,
        ...
        inmoney,
        grossmargin,
        ...
    }
}

redux的通知是 粗粒度的, 也就是说每当有store发生改变的时候, 全部在页面上 connect组件 都会接受到通知, 执行一下mapStateToProps, 渲染页面(具体是浅比较mapStateToProps的结果与上一次,来判断是否渲染)。
因此若是咱们不对 衍生属性处理的话:

  1. 其余组件的属性改变, 会引发上面的mapStateToProps执行,引发衍生属性的计算
  2. 其余组件的属性改变, 引发衍生属性的计算 还有一个潜在的问题。就是当这里的getGrossmargin / getInmoney 返回的是一个对象的时候, 因为每次调用都是返回一个新对象, 致使浅比较的结果是 先后不等, 引发组件的无心义渲染。
  3. 即便是本组件的属性变化, 有时计算也是没有意义的。 好比tax的改变,不该该引发 inmoney的计算

咱们须要精确的控制 衍生属性的处理。 第三方库reselect是作这个事情的,
好比 inmoney:

import { createSelector } from reselect

const inmoneySelect = createSelector(
    state => state.inprice
    state => state.count
    
    (inprice, count) => getInmoney(inpirce, count)
)

reselect 会重复利用缓存结果, 直到相关的属性修改。

reselect写起来有点繁琐。 咱们这里使用repure 来替代reselect。
repure提供更加天然的写法

import repure from 'repure'
function getInmoney(inprice, count) {
    ....
}
const reGetInmoney = repure(getInmoney) // 给getInmoney增长缓存的功能
function getGrossmargin(inprice, count, outprice....) {
    ...
}
const reGetGrossmargin(getGrossmargin) //给getGrossmargin增长缓存的功能

...

function mapStateToProps(state) {
    const { inprice, outprice, tax ... } = state
    const inmoney = reGetInmoney(inpirce, count)
    const grossmargin = reGetGrossmargin(inprice, count, outprice....)
    ....
    return {
        inprice,
        outprice,
        tax,
        ...
        inmoney,
        grossmargin,
        ...
    }
}

repure比reselect书写更加简单天然, 咱们就是在写普通的方法, 而后repure一下,让其具备缓存的功能。 具体请看reselect的替代者repure

不论是reselect仍是 repure都很高效

end

用好衍生属性会让咱们的应用简单不少。

mobx天生支持, 写法简单天然。 不过正如本文所说, 有些隐藏的坑。redux自己没有提供方法, 可是有不少第三方库提供了处理, 也很高效。 其中repure的写法是比较简单天然的。

相关文章
相关标签/搜索