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:
- Reduce rerender times (immutable data, shouldComponentUpdate, PureComponent)
- 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?
- Render is not performed when state(filterText)/props(list) is not changed
- 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