考虑这样得一个页面javascript
其中money = price * count
。java
在设计数据层的时候, 咱们能够: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, } }
如今,咱们业务复杂了:github
若是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
。。。
一句话, 每次状态更新的时候, 我须要保证状态的数据一致性, 状态越多关系越复杂,越难保证, 牵一发动全身。 不出意外, 随着项目的进行需求的变换, 我应该会不停的加班, 不断的写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对衍生属性处理的很好,
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
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自己并无提供对衍生属性的处理。
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的结果与上一次,来判断是否渲染)。
因此若是咱们不对 衍生属性处理的话:
咱们须要精确的控制 衍生属性的处理。 第三方库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都很高效
用好衍生属性会让咱们的应用简单不少。
mobx天生支持, 写法简单天然。 不过正如本文所说, 有些隐藏的坑。redux自己没有提供方法, 可是有不少第三方库提供了处理, 也很高效。 其中repure的写法是比较简单天然的。