/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow * @format */ import invariant from 'assert'; import {arrayEqual} from './collection'; type memoizeUntilChanged = (( func: (A, B, C, D) => R, keySelector_?: (A, B, C, D) => U, compareKeys_?: (U, U) => boolean, ) => (A, B, C, D) => R) & (( func: (A, B, C) => R, keySelector_?: (A, B, C) => U, compareKeys_?: (U, U) => boolean, ) => (A, B, C) => R) & (( func: (A, B) => R, keySelector_?: (A, B) => U, compareKeys_?: (U, U) => boolean, ) => (A, B) => R) & (( func: (A) => R, keySelector_?: (A) => U, compareKeys_?: (U, U) => boolean, ) => A => R) & ((func: () => R) => () => R) & (( func: (...any: $ReadOnlyArray) => R, (...any: $ReadOnlyArray) => U, compareKeys_?: (U, U) => boolean, ) => (...any: $ReadOnlyArray) => R); /** * Create a memoized version of the provided function that caches only the latest result. This is * especially useful for optimizing React component methods without having to worry about * maintaining state explicitly. For example: * * class MyComponent extends React.Component { * constructor(props) { * super(props); * this._computeSomethingExpensive = memoizeUntilChanged(this._computeSomethingExpensive); * } * _computeSomethingExpensive(x) { ... } * render() { * const thingToRender = this._computeSomethingExpensive(this.props.value); * return
{thingToRender}
; * } * } * * Sometimes, you need to customize how the arguments are compared. In this case you can pass a * key selector function (which derives a single value from the arguments), and an equality function * (which compares keys). For example: * * class MyComponent extends React.Component { * constructor(props) { * super(props); * this._computeSomethingExpensive = memoizeUntilChanged( * this._computeSomethingExpensive, * (x: Array, y: Array) => ({x, y}), * (a, b) => arrayEqual(a.x, b.x) && arrayEqual(a.y, b.y), * ); * } * _computeSomethingExpensive(x: Array, y: Array) { ... } * render() { * const thingToRender = this._computeSomethingExpensive(this.props.value); * return
{thingToRender}
; * } * } */ export default ((func, keySelector_?, compareKeys_?) => { invariant( !(keySelector_ == null && compareKeys_ != null), "You can't provide a compare function without also providing a key selector.", ); let prevKey = null; let prevResult; const keySelector = keySelector_ || DEFAULT_KEY_SELECTOR; const compareKeys = compareKeys_ || arrayEqual; // $FlowIssue: Flow can't express that we want the args to be the same type as the input func's. return function(...args) { const key = (keySelector: Function)(...args); invariant(key != null, 'Key cannot be null'); // $FlowIssue: We can't tell Flow the relationship between keySelector and compareKeys if (prevKey == null || !compareKeys(key, prevKey)) { prevKey = key; prevResult = (func: Function).apply(this, args); } return prevResult; }; }: memoizeUntilChanged); const DEFAULT_KEY_SELECTOR = (...args) => args;