考慮這樣得一個頁面 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
迫在眉睫!咱們必須儘量的減小維護的狀態, 一旦狀態足夠少,咱們就更容易的保證了數據層的正確 那麼根據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的寫法是比較簡單天然的。