考慮這樣得一個頁面
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的寫法是比較簡單天然的。