Redux vs mobx series (2): derived attributes

Time:2021-3-1

Redux vs mobx series (2): derived attributes


Consider such a page
Redux vs mobx series (2): derived attributes

amongmoney = price * count

When designing the data layer, we can:

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

In this way, our components can directly obtain price, count and money from the store, and then display them. It’s very convenient and simple. When updating:


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,
    }
}

Now our business is complex:
Redux vs mobx series (2): derived attributes

If the store is designed as follows:

var store = {
    inprice: '',
    outprice: '',
    ...
    inmoney: '',
    outmoney: '',
    ...
    Taxmoney: ', // amount including tax
    ...
    Grossmargin: ', // gross profit rate
}

The logic of the page component is still very simple, just get the corresponding data to show. Now I’m going to adjust the priceupdateInprice. How to write update inprice?

function updateInprice(newInprice, oldStore) {
    const inprice = newInprice
    const inmoney = inprice * oldStore.count
    //Tax amount, tax amount, gross profit, gross profit rate
    ....
    const grossmargin = ....
}

waht ?? I need to change so much when I adjust the selling price?? Yes, when you adjust the quantity, adjust the price, adjust the loss… Need to change so much!!! Remember the hidden trouble of mobx in [1] ().
There are many problems in the design of store

  1. The update state becomes complicated
  2. Suppose that “amount after discount” needs to be added in the end, then I need to add “amount after discount” to the update inprice, update count and update discount methods
  3. Suppose I remove the “reported loss” input box now, then I need to go to all the places dealing with the “reported loss”, on the modification side

。。。
In a word, every time the status is updated, I need to ensure the data consistency of the status. The more the status is, the more complex the relationship is, and the more difficult it is to guarantee. Let’s start the whole body. Not surprisingly, with the progress of the project and the change of demand, I should keep working overtime, constantly writing bugs and correcting bugs — less time to accompany my family — marriage break up — abandon myself — die in depression.

imminent! We must reduce the maintenance state as much as possible. Once the state is small enough, we can more easily ensure the correctness of the data layerapp = f(store)The application is correct.

From the above examples, we can see that the cost amount, sales amount, tax amount and gross profit rate do not need our management, because they can be derived from the known states, such as:
Inmoney = inprice * count; outmoney = outprice * (count - reported loss)
These attributes are derived attributes. The attributes that can be derived from the existing state or other calculated values are derived attributes.

Now let’s look at the previous example

/// 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 - reported loss) - inprice * count) / inprice * count
}

Now the number of States is reduced from 12 to 6, and they are independent of each other, so the update is very simple (such as code). When you display the page, you only need to get data from store, and then call the get method to get the derived data.

“Boss, I went home with my wife.” “Good!”

The more complex the data interaction is (note that the more complex the data interaction is), the more important the framework is to handle the derived attributes.

mobx

Mobx handles derived attributes very well,

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

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

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

The total here is the derived attribute

An excerpt from the official mobx document: “if any value affecting the calculated value changes, the calculated value will be automatically derived according to the state. The calculated values can be optimized by mobx in most cases because they are considered pure functions. For example, if the data used in the previous calculation does not change, the calculation properties will not be rerun
`
in other words:

@observer
class X extends Component {
    componentDidMount(){
        setInternal(() => {
            this.forceUpdate()
        }, 1000)
    }
    
    render() {
        const gm =  this.props.grossmargin  //Derivative property gross margin
        ...
    }
}

In the case of continuous execution of the above render (through forceupdate), grossmargin does not recalculate, but uses the last cached value repeatedly until the state of grossmargin is changed.

But there are two things to note
1. const { price, amount, total } = orderLineIn this way, the total cannot be obtained
mustorderLine.total

  1. There is another hole in mobx’s compute. Let’s see the following example
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)

According to the previous statement, although it has been quoted many timesol.totalIt should only print onceinvoke total
. But the actual situation is printed 5 times!!! If we take out the comment of autorun, execute it again and print it onceinvoke total。。。。。 I can only sigh that mobx is full of black magic. This phenomenon is reasonable in mobx. In short, only when derived attributes are used in observer, autorun and reaction, will they be cached. Please seeissues718
However, the render function of the @ observer modified component has been modifiedrewriteFor reaction, you can use the derived properties as you like in the render function of the component.

redux

Redux itself does not provide the processing of derived attributes.

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

Redux’s notification is coarse-grained, that is, every time a store changes, all of them are on the pageConnect componentWill receive the notification, execute mapstatetoprops and render the page (specifically, compare the result of mapstatetoprops with the previous one to determine whether to render).
So if we don’t deal with derived attributes:

  1. If the properties of other components change, the mapstatetoprops above will be executed, resulting in the calculation of derived properties
  2. There is a potential problem in the calculation of derived properties due to the change of properties of other components. When getgrossmargin / getinmoney here returns an object, because each call returns a new object, the result of shallow comparison is unequal, causing meaningless rendering of components.
  3. Even if the properties of this component change, sometimes the calculation is meaningless. For example, the change of tax should not cause the calculation of inmoney

We need to precisely control the processing of derived attributes. Third party LibraryreselectIt’s about doing this,
For example, inmoney:

import { createSelector } from reselect

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

Reselect reuses the cached results until the relevant properties are modified.

Reselect is a bit cumbersome to write. We use it hererepureInstead of reselect.
Repore provides a more natural way of writing

import repure from 'repure'
function getInmoney(inprice, count) {
    ....
}
Const regetinmoney = repure (getinmoney) // add cache function to getinmoney
function getGrossmargin(inprice, count, outprice....) {
    ...
}
Const regetgrossmargin (getgrossmargin) // add caching function to 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 is simpler and more natural than reselect. We are just writing common methods, and then repure them to have the function of caching. Please seeRepere, the replacement of reselect

Both reselect and resure are efficient

end

Making good use of derived attributes will make our application much easier.

Mobx is naturally supported and written in a simple and natural way. But as this article says, there are some hidden pits.
Redux itself does not provide methods, but many third-party libraries provide processing, which is also very efficient. Repure is simple and natural.


Recommended Today

Deeply analyze the principle and practice of RSA key

1、 Preface After experiencing many dark moments in life, when you read this article, you will regret and even be angry: why didn’t you write this article earlier?! Your darkest moments include: 1. Your project needs to be connected with the bank, and the other party needs you to provide an encryption certificate. You have […]