admin管理员组

文章数量:1326657

I am trying to figure out a way to be able to memoize React ponents by specifying particular props.

For instance, if you use React.memo — it memoizes the ponent based on all props.

What I am trying to achieve is being able to pass particular props as a dependency to a util (say, SuperMemo) and the ponent will be memoized based on those props. The approach is very similar to what repose — pose the ponent before export.

Here's an example code

import React from "react";

const isFunction = value =>
  value &&
  (Object.prototype.toString.call(value) === "[object Function]" ||
    "function" === typeof value ||
    value instanceof Function);

export const memo = (Comp, resolver) => {
  if (isFunction(resolver)) {
    const Memoized = props => {
      const deps = resolver(props);
      if (deps && deps.length) {
        // eslint-disable-next-line react-hooks/rules-of-hooks
        return React.useCallback(React.createElement(Comp, props), deps);
      }

      return React.createElement(Comp, props);
    };

    Memoized.displayName = `memoized(${Comp.name})`;
    return Memoized;
  }

  return React.memo(Comp);
};

export default memo;

Here is how it will be used to pose ponents

import Todo from "./Todo";
import memo from "../memo";

export default memo(Todo, props => [props.text]);

I have a working codesandbox here — memo-deps

This is what I have observed —

  • I should not use React.useCallback or any hook inside a conditional statement because React needs to know the order in which hooks are invoked and using it inside a conditional may mess up the order during runtime
  • But React.useCallback works pretty neat in a conditional for my case as I know the order will remain the same during runtime
  • I am not using the hook inside the conditional statement during render, instead I am posing the ponent during export conditionally
  • I am thinking about React ponents as plain JavaScript functions and trying to memoize it like how I would memoize a regular JavaScript function
  • I could easily replace React.useCallback with lodash.memoize and the end result will be pretty much the same
  • I don't want to use an external library like lodash.memoize or build a custom implementation of memoization while React.useCallback pretty much does the work for me

This is where I am not sure what's happening (these are my questions) —

  • React ponents are not really vanilla JavaScript functions and I cannot memoize them with lodash.memoize
  • lodash.memoize and React.useCallback are not the same when I try to memoize a React ponent
  • React executes the function before figuring out the render even when React.memo is used (maybe to check prevProps vs newProps?)
  • Is my implementation okay even though it breaks the rules of React? (use hook in a conditional statement)
  • How else can I memoize a React.createElement if not for React.useCallback?

The reason as to why I might want to do this —

I don't want to memoize handlers (closure with a value and event) every time I pass them to a ponent wrapped in React.memo. I want to be able to declaratively write memoize dependencies for ponents.

I am trying to figure out a way to be able to memoize React ponents by specifying particular props.

For instance, if you use React.memo — it memoizes the ponent based on all props.

What I am trying to achieve is being able to pass particular props as a dependency to a util (say, SuperMemo) and the ponent will be memoized based on those props. The approach is very similar to what repose — pose the ponent before export.

Here's an example code

import React from "react";

const isFunction = value =>
  value &&
  (Object.prototype.toString.call(value) === "[object Function]" ||
    "function" === typeof value ||
    value instanceof Function);

export const memo = (Comp, resolver) => {
  if (isFunction(resolver)) {
    const Memoized = props => {
      const deps = resolver(props);
      if (deps && deps.length) {
        // eslint-disable-next-line react-hooks/rules-of-hooks
        return React.useCallback(React.createElement(Comp, props), deps);
      }

      return React.createElement(Comp, props);
    };

    Memoized.displayName = `memoized(${Comp.name})`;
    return Memoized;
  }

  return React.memo(Comp);
};

export default memo;

Here is how it will be used to pose ponents

import Todo from "./Todo";
import memo from "../memo";

export default memo(Todo, props => [props.text]);

I have a working codesandbox here — memo-deps

This is what I have observed —

  • I should not use React.useCallback or any hook inside a conditional statement because React needs to know the order in which hooks are invoked and using it inside a conditional may mess up the order during runtime
  • But React.useCallback works pretty neat in a conditional for my case as I know the order will remain the same during runtime
  • I am not using the hook inside the conditional statement during render, instead I am posing the ponent during export conditionally
  • I am thinking about React ponents as plain JavaScript functions and trying to memoize it like how I would memoize a regular JavaScript function
  • I could easily replace React.useCallback with lodash.memoize and the end result will be pretty much the same
  • I don't want to use an external library like lodash.memoize or build a custom implementation of memoization while React.useCallback pretty much does the work for me

This is where I am not sure what's happening (these are my questions) —

  • React ponents are not really vanilla JavaScript functions and I cannot memoize them with lodash.memoize
  • lodash.memoize and React.useCallback are not the same when I try to memoize a React ponent
  • React executes the function before figuring out the render even when React.memo is used (maybe to check prevProps vs newProps?)
  • Is my implementation okay even though it breaks the rules of React? (use hook in a conditional statement)
  • How else can I memoize a React.createElement if not for React.useCallback?

The reason as to why I might want to do this —

I don't want to memoize handlers (closure with a value and event) every time I pass them to a ponent wrapped in React.memo. I want to be able to declaratively write memoize dependencies for ponents.

Share Improve this question edited May 7, 2019 at 10:15 Dinesh Pandiyan asked May 7, 2019 at 9:08 Dinesh PandiyanDinesh Pandiyan 6,2993 gold badges35 silver badges50 bronze badges 5
  • There's no 'outside a ponent' in the code you posted. useCallback is used inside Memoized ponent. And it's misused because React.createElement(Comp, props) is not a function. – Estus Flask Commented May 7, 2019 at 9:14
  • Perhaps I used the wrong terminology. I meant that the Memoized is a wrapper over the real ponent and it is not affected by the render logic of the real ponent. – Dinesh Pandiyan Commented May 7, 2019 at 9:20
  • I was curious about React.createElement(Comp, props) as well. Should useMemo be used there instead of useCallback? – Dinesh Pandiyan Commented May 7, 2019 at 9:23
  • 2 it is not affected by the render logic of the real ponent - this is the reason why it's acceptable and doesn't break the rules of hooks. It should be React.useMemo(() => React.createElement(Comp, props), ...) then. React.useCallback(React.createElement(Comp, props), deps) is workable but it's semantically incorrect and results in unnecessary createElement call. – Estus Flask Commented May 7, 2019 at 9:38
  • This has nothing to do with your question per se, but if you use Typescript, you don't have to waste time on implementing functions like isFunction. – Titulum Commented Dec 14, 2020 at 13:12
Add a ment  | 

1 Answer 1

Reset to default 6 +25

React.memo accepts a function as the second parameter to do a custom props parison.

By default it will only shallowly pare plex objects in the props object. If you want control over the parison, you can also provide a custom parison function as the second argument.

You can use that in your util function like this :

export const memoWithSecondParam = (Comp, deps = []) => {
  return React.memo(Comp, (prevProps, nextProps) => {
    return deps.every(d => prevProps[d] === nextProps[d])
  });
};

And call it like this :

export default memoWithSecondParam(Todo, ["text"]);

本文标签: