Consider a page like this

Where money = price * count.

When designing the data layer, we can:

var store ={
    price: 0.count: 0.money: 0
}
Copy the code

This way our component can fetch price, count, and money directly from the store and display it. It is 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,
    }
}
Copy the code

Now, our business is complicated:

If the store is still designed as follows:

var store = {
    inprice: ' '.outprice: ' '. inmoney:' '.outmoney: ' '. taxmoney:' '.// Tax inclusive. grossmargin:' './ / gross margin
}
Copy the code

The page component logic is still very simple, just get the corresponding data display. So here’s the problem, and now I’m going to change my price update price. So what do I write updateInprice?

function updateInprice(newInprice, oldStore) {
    const inprice = newInprice
    const inmoney = inprice * oldStore.count
    // Update tax amount, tax, gross margin, gross margin. const grossmargin = .... }Copy the code

Waht???? I adjust a price, need to change so much?? Yes, when you adjust the quantity, the purchase price, the reported loss… All need to change so much!! Remember the performance pitfalls of MOBx from # 1. There are many problems with store design:

  1. Status updates become more complicated
  2. So let’s say I need to add the discounted amount at the end, so I need to go to updateInprice, updateCount, updateDiscount and add the discounted amount to that method
  3. Suppose I now remove the “reported loss” input box, then I need to go to all the places where “reported loss” is processed, on the modification side… In a word, EVERY time I update the status, I need to ensure the consistency of the data of the status. The more the status, the more complex the relationship is, and the more difficult it is to guarantee it. Unsurprisingly, as the project progresses and demands change, I should keep working overtime and writing bugs and fixing bugs –> less time to spend with my family –> marriage breaks up –> self-abandonment –> depressed death.

Imminent! We have to keep the number of states maintained as low as possible, and once the number of states is low enough, it’s easier to make sure that the data layer is correct and the app is correct according to app = f (Store).

From the above examples, cost amount, sales amount… Tax… We do not need to manage the gross margin, because it can be deduced from known states, such as: Inmoey = inprice * count; Outmoney = outprice * (count – reported loss) these attributes are derived attributes, which can be derived from the existing state or other calculated values.

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) {
    returnInprice * count)/inprice * count}Copy the code

The number of states is now reduced from 12 to 6, and they are independent of each other, making it easy to update (e.g. code). The page only needs to get the data from the Store and then call the get method to get the derived data.

“Boss, I’m home with my wife.” “All right!

For applications with more complex data interactions, the framework’s handling of derived properties becomes very important.

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; }}Copy the code

Total is the derived property

Here’s an excerpt from mobx’s official documentation: If any value that affects a calculated value changes, the calculated value is automatically derived based on state. 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 has not changed, the calculation property will not be rerun that is:

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

In the case of the above constant execution of render (via forceUpdate), GrosSmargin does not recalculate, but instead reuses the last cached value until the state that affects GrosSmargin changes.

1. Const {price, amount, total} = orderLine

  1. After all, compute in mobx has another problem, 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)
Copy the code

According to the previous statement, only one invoke Total should be printed although ol.total is quoted many times here. But it actually printed 5 times!! what ???? If we uncomment Autorun, execute again and print an invoke Total… Mobx is full of dark magic. This makes sense in Mobx, where derived properties are cached only when used in observer, Autorun, or Reaction. See Issue 718 for details. However, the @observer render function has been rewritten as reaction, so you can use the derived properties in the render function of the component as you like.

redux

Redux itself does not provide handling of derived attributes.

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

Redux’s notifications are coarse-grained, which means that every time a store changes, all connect components on the page will be notified, perform mapStateToProps, and render the page. To determine whether to render). So if we don’t deal with derived attributes:

  1. Property changes to other components cause the mapStateToProps above to execute, causing the derived properties to be evaluated
  2. There is also a potential problem with attribute changes in other components causing derived attributes to be evaluated. When getGrossmargin/getInmoney returns an object, each call returns a new object, resulting in unequal shallow comparisons and meaningless rendering of the component.
  3. Even if the properties of this component change, sometimes the calculation is meaningless. For example, tax changes should not cause inmoney’s calculation

We need precise control over the handling of derived attributes. Third-party libraries like ResELECT do this, like InMoney:

import { createSelector } from reselect

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

Reselect reuses the cached results until the associated attributes are modified.

Reselect is a bit cumbersome to write. We use Repure instead of resELECT here. Repure provides a more natural way of writing

import repure from 'repure'
function getInmoney(inprice, count) {... }const reGetInmoney = repure(getInmoney) // Add caching to getInmoney
function getGrossmargin(inprice, count, outprice....) {... }const reGetGrossmargin(getGrossmargin) // Add caching to getGrossmargin. function mapStateToProps(state) {const { inprice, outprice, tax ... } = state
    const inmoney = reGetInmoney(inpirce, count)
    constgrossmargin = reGetGrossmargin(inprice, count, outprice....) . return { inprice, outprice, tax, ... inmoney, grossmargin, ... }}Copy the code

Repure is much easier and more natural to write than reselect, we’re just writing a normal method and repure it to make it caching. See Reselect’s alternative, Repure

Both ResELECT and RePure are highly effective

end

Using derived properties will make our application a lot easier.

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