admin管理员组

文章数量:1287631

Basically this:

function MyComponent() {
  let [count, setCount] = useState(1)
  let memo = useMyMemo(() => new MyClass)

  return <div onClick={update}>{count}</div>

  function update() {
    setCount(count + 1)
  }
}

function useMyMemo(fn) {
  // what to do here?
}

class MyClass {

}

I would like for useMyMemo to only return 1 instance of the class per ponent instance. How do I set this up to implement it without resorting to using any of the existing React hooks? If it's not possible without the React hooks, they why not? If it is possible only through accessing internal APIs, how would you do that?

As a bonus it would be helpful to know how it could be passed property dependencies, and how it would use that to figure out if the memo should be invalidated.

Basically this:

function MyComponent() {
  let [count, setCount] = useState(1)
  let memo = useMyMemo(() => new MyClass)

  return <div onClick={update}>{count}</div>

  function update() {
    setCount(count + 1)
  }
}

function useMyMemo(fn) {
  // what to do here?
}

class MyClass {

}

I would like for useMyMemo to only return 1 instance of the class per ponent instance. How do I set this up to implement it without resorting to using any of the existing React hooks? If it's not possible without the React hooks, they why not? If it is possible only through accessing internal APIs, how would you do that?

As a bonus it would be helpful to know how it could be passed property dependencies, and how it would use that to figure out if the memo should be invalidated.

Share Improve this question asked Aug 10, 2019 at 10:44 Lance PollardLance Pollard 79.5k98 gold badges330 silver badges607 bronze badges 1
  • 1 Not an answer, but you'll need to dig into React internals to do this. Hooks work because React knows what ponent function it's currently calling. useState and such use that internal information to do their work (see the resolveDispatcher calls throughout the ReactHooks.js file), they're basically wrappers for calls on a "dispatcher" object. One such dispatcher is implemented here. – T.J. Crowder Commented Aug 10, 2019 at 10:55
Add a ment  | 

2 Answers 2

Reset to default 6

I think you're describing how useMemo already works, assuming you pass [] as the dependencies parameter. It should already create one instance of MyClass per instance of MyComponent. However, the documentation says that future versions of useMemo will not guarantee that the value is only called once, so you might want to try useRef.

const memo = useRef(null);
if (!memo.current) {
  memo.current = new MyClass()
}

If you want it to create a new instance of MyClass when dependencies change, you'll have to either use useMemo (and accept the chance that the value might be invalidated occasionally), or do your own shallow parison against the previous value. Something like this:

const useMyMemo = (create, dependencies) => {
  const val = React.useRef(create());
  const prevDependencies = React.useRef([]);
  if (!shallowEquals(dependencies, prevDependencies.current)) {
    val.current = create();
    prevDependencies.current = [...dependencies];
  }
  return val;
};

I'll leave the implementation of shallowEquals to you, but I believe lodash has one.

Now you really asked for a function that doesn't use any React hooks, and mine uses useRef. If you don't want useRef, you can create your own simple memoization function that always returns the same pointer regardless of changes to .current.

Simple mock implementation of useMemo using useRef.

Note: You can shallowCompare or deepCompare to pute isChanged based on your need. Example if dependencies are objects.

/* File: useMemo.js */

import { useRef } from "react";

export function useMemo(callback, deps) {
  const mPointer = useRef([deps]); // Memory pointer (Make sure to read useRef docs)
  const isChanged = mPointer.current[0].some(
    // If dependencies changed
    (item, index) => item !== deps[index]
  );

  if (mPointer.current.length === 1 || isChanged) {
    // If first time or changed, pute and store it
    mPointer.current = [deps, callback()];
  }

  return mPointer.current[1];
}

See CodeSandbox demo.

Read: useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the ponent.


Pure javascript mock using closure - [ For educational purpose ONLY! ]

/* File: useMemo.js */

function memo() {
  const stack = {};
  return (callback, deps) => {
    /* Creating hashkey with dependencies + function source without whitespaces */
    const hash = deps.join("/") + "/" + callback.toString().replace(/\s+/g, "");

    if (!stack[hash]) {
      stack[hash] = callback();
    }

    return stack[hash];
  };
};

export const useMemo = memo();

See CodeSandbox demo.

Idea explaination : With above code trying to create a unique hash by bining dependencies value & function source using toString, removing spaces in string to make hash as unique as possible. Using this as a key in the hashmap to store the data and retrieve when called. The above code is buggy, for example callback result that are cached are not removed when ponent unmount's. Adding useEffect and return clean-up function should fix this during unmount. Then this solution bees tightly coupled with react framework

Hope this helps. Feedback is weled to improve the answer!

本文标签: javascriptHow to implement your own useMemo from Scratchfor ReactStack Overflow