Performance Optimization

Reduck has already done a lot of performance optimization work internally, so performance issues generally do not need to be considered. However, when performance is more sensitive, or when encountering performance issues, you can consider more targeted performance optimization from the following three aspects.

Splitting Models

When useModel returns the complete State object of the Model, any change in any part of the State will cause the component that calls useModel to be re-rendered.

For example:

const fooModel = model('foo').define({
  state: {
    a: '',
    b: '',
  },
  actions: {
    setA(state, payload) {
      state.a = payload;
    },
    setB(state, payload) {
      state.b = payload;
    },
  },
});

function ComponentA() {
  const [state] = useModel(fooModel);

  return <div>{state.a}</div>;
}

Although ComponentA only needs to use the a state, it will still be re-rendered when the b state changes. In this case, we can consider splitting fooModel into separate Models responsible for managing a and b respectively:

const fooModel = model('foo').define({
  state: {
    a: '',
  },
  actions: {
    setA(state, payload) {
      state.a = payload;
    },
  },
});

const barModel = model('bar').define({
  state: {
    b: '',
  },
  actions: {
    setB(state, payload) {
      state.b = payload;
    },
  },
});

State Selection

useModel supports passing in a selector function to filter the returned State and Actions for the component. We can use a selector function to ensure that the State returned to the component is what the component needs directly, thus ensuring that the component is not re-rendered due to changes in other unrelated states.

For the same example above, we can use a selector function for performance optimization, the code is as follows:

const fooModel = model('foo').define({
  state: {
    a: '',
    b: '',
  },
  actions: {
    setA(state, payload) {
      state.a = payload;
    },
    setB(state, payload) {
      state.b = payload;
    },
  },
});

function ComponentA() {
  // By passing in the selector function
  // only the a state is returned to the component
  const [stateA] = useModel(fooModel, state => state.a);

  return <div>{stateA}</div>;
}

Derivative State Caching

When a Model has computed property, the computed function will be executed every time useModel is called.

Consider the following code:

const barModel = model('bar').define({
  state: {
    value: 'bar',
  },
  computed: {
    combineA: [
      fooModel, // fooModel define as above
      (state, fooState) => {
        return state + fooState.a;
      },
    ],
  },
  actions: {
    setValue(state, payload) {
      state.value = payload;
    },
  },
});

function ComponentB() {
  const [state, actions] = useModel(fooModel);
  const [{ combineA }] = useModel(barModel);
  // ...
}

Even if the b state of fooModel changes, the combineA function (more precisely, the last function type element of combineA) will still be called and executed when the component is re-rendered, although the derivative state combineA of barModel depends on barModel itself and the state a of fooModel.

In general, the logic in the computed function is usually very lightweight, but when the logic in the computed function is relatively complex, we can consider caching the calculation logic. For example, we can use reselect to cache combineA of barModel:

import 'createSelector' from 'reselect';

// create cache function
const selectCombineA = createSelector(
  (state) => state.bar.value,
  (state) => state.foo.a,
  (barState, fooState) => {
    return barState + fooState;
  }
);

const barModel = model("bar").define({
  state: {
    value: "bar",
  },
  computed: {
    combineA: [
      fooModel,
      (state, fooState) => {
        return selectCombineA({
          foo: fooState,
          bar: state,
        });
      },
    ],
  },
  actions: {
    setValue(state, payload) {
      state.value = payload;
    },
  },
});

We created a caching function createSelector, which only recalculates the value of combineA when the state of barModel changes or the state a of fooModel changes.

Additional Information

You can find the complete example code of this section here.