---
sidebar_position: 8
title: Performance Optimization
---
# 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:

```ts
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:

```ts
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:

```ts
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:

```ts
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](https://github.com/reduxjs/reselect) to cache `combineA` of `barModel`:

```ts
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.

:::info Additional Information
You can find the complete example code of this section [here](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/runtime-api/performance-optimization).
:::

