Introduction to Writing faster React code (PART 1) : Memoize-One

The introduction

Different types of services require different performance standards. If a ToB backend management system requires first screen speed and SEO, obviously not reasonable and unnecessary.

The first thing to consider is not how to optimize, but whether it’s worth it. React performance is good enough. After all, “premature optimization is the devil”, it’s always “ok, but not necessary”.

As a developer, it is extremely important to have a deep understanding of the shortcomings of your tools and the ability to optimize them.

React performance optimization can be divided into two parts:

  1. Reduce rerender times (immutable data, shouldComponentUpdate, PureComponent)
  2. Reduce rerender complexity (Memoize-One)

In this paper, we optimize the render method based on Memoiz-One to reduce unnecessary render complexity.

Existing problems

Let’s start with a simple component like this:

class Example extends Component {
  state = {
    filterText: ""
  };

  handleChange = event= > {
    this.setState({ filterText: event.target.value });
  };

  render() {
    const filteredList = this.props.list.filter(item= >
      item.text.includes(this.state.filterText)
    );

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>
          {filteredList.map(item => (
            <li key={item.id}>{item.text}</li>
          ))}
        </ul>
      </Fragment>); }}Copy the code

This component receives the list passed by the parent, filters out the filteredList containing the filterText, and displays it.

What’s the problem?

Without any processing, the parent component render will always result in the child component render, even if the state/props of the child component is not changed. If the amount of data to be filtered is large and the filtering logic is complex, this will be a very important optimization point.

What is the effect to achieve?

  1. Render is not performed when state(filterText)/props(list) is not changed
  2. Render if state(filterText)/props(list) is not changed

memoize-one

A memoization library which only remembers the latest invocation

The basic use

import memoize from "memoize-one";

const add = (a, b) = > a + b; // Basic calculation method
const memoizedAdd = memoize(add); // Generate a cacheable calculation method

memoizedAdd(1.2); / / 3

memoizedAdd(1.2); / / 3
// Add is not executed: the last result is returned directly

memoizedAdd(2.3); / / 5
// The Add function is called to get the new result

memoizedAdd(2.3); / / 5
// Add is not executed: the last result is returned directly

memoizedAdd(1.2); / / 3
// The Add function is called to get the new result
// Even if the result has been cached before
// But it is not the last cached result, so the cached result is lost
Copy the code

Now that we know the basics, let’s optimize the above case.

To optimize the case

import memoize from "memoize-one";

class Example extends Component {
  state = { filterText: "" };

  // Only when list or filterText changes will the real filter method be recalled (memoize is added)
  filter = memoize((list, filterText) = >
    list.filter(item= > item.text.includes(filterText))
  );

  handleChange = event= > {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // After the last render, 'memoize-one' will repeat the result of the last render if the parameters have not changed
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>
          {filteredList.map(item => (
            <li key={item.id}>{item.text}</li>
          ))}
        </ul>
      </Fragment>); }}Copy the code

The source code parsing

Less than 20 lines if you strip out the TS references and comments. Memoize – one is essentially a higher-order functions, real calculation function as an argument, return a new function, new function into the internal caches last and last return value, if the involved in the last into the equal, the return value is returned last, otherwise, to call the real calculation function, and slow deposit as well as the results, For next use.

Pretend there is a flow chart 🙂

// The default comparison takes the same method as the default comparison. The user can define the comparison method
import areInputsEqual from './are-inputs-equal';

// Function signature
export default function<ResultFn: (.any[]) = >mixed> (resultFn: ResultFn, isEqual? : EqualityFn = areInputsEqual,) :ResultFn {
  // Last time this
  let lastThis: mixed;
  // Last parameter
  let lastArgs: mixed[] = [];
  // Last return value
  let lastResult: mixed;
  // Whether it has been called for the first time
  let calledOnce: boolean = false;

  // The function to be returned
  const result = function(. newArgs: mixed[]) {
    // If the argument or this has not changed or is not called first
    if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
      // Return the result of the last calculation directly
      return lastResult;
    }

    // Parameter change or first call
    lastResult = resultFn.apply(this, newArgs);
    calledOnce = true;
    // Save the current parameters
    lastThis = this;
    // Save the current result
    lastArgs = newArgs;
    // Returns the current result
    return lastResult;
  };

  // Return the new function
  return (result: any);
}
Copy the code

You can also use decko, which has three built-in decorators: bind/ Memoize /debounce.

Extension: Fibonacci series

Here is an example of a Fibonacci sequence that uses iteration instead of recursion and uses closures to cache previous results.

const createFab = (a)= > {
  const cache = [0.1.1];
  return n= > {
    if (typeofcache[n] ! = ="undefined") {
      return cache[n];
    }
    for (let i = 3; i <= n; i++) {
      if (typeofcache[i] ! = ="undefined") continue;
      cache[i] = cache[i - 1] + cache[i - 2];
    }
    return cache[n];
  };
};

const fab = createFab();
Copy the code

conclusion

In this paper, we introduce the use of memoiz-One library based on React and its principles. In React, we have achieved computed results similar to Vue, which reduce unnecessary render complexity based on dependency caching.

From a business development perspective, Vue provides apis that greatly improve development efficiency.

React doesn’t solve many problems on its own, but thanks to its active community, we can find solutions to all the problems we encounter in our work. And while exploring these solutions, we can learn a lot of classic programming ideas and reduce our dependence on frameworks.

I’ve always said that React will make you a better JavaScript developer. – Tyler McGinnis

Refer to the link

  • You Probably Don’t Need Derived State, by React Team
  • Memoize Technology Memoize-One, by Leon
  • By alexreardon memoize – one
  • Compute properties and listeners, by Vue