useCallback

Basic example

const memoizedCallback = useCallback(
() = > {    doSomething(a, b);
  },
  [a, b],
); Copy the code

Return a Memoized function with an inline callback and an array of dependencies passed to useCallback as arguments. The callback is updated only when dependencies change, avoiding unnecessary rendering. In practice, I use a configuration of ESLint that automatically adds dependencies.

react-hooks/exhaustive-deps: 'error'/'warn'
Copy the code

Then, without looking specifically at dependencies, several times resulted in a loop call (I’ll show the code that makes the loop call later), so we removed the eslint-rule later

Memoization

Memoization is worth mentioning here. Memoization is basically the same as Memoization from the hooks source code.

A simple version of useCallback

let hookStates = [];
let hookIndex = 0;
function useCallbacks(callback, dependencies) {
  if(hookStates[hookIndex]) {// Indicates not the first rendering    let [lastCallback, lastDependencies] = hookStates[hookIndex];
 let same = dependencies.every((item, index) => item === lastDependencies);  if (same) {  hookIndex++;  return lastCallback;  }  } // The first rendering or not the first but the same dependency returns a new one hookStates[hookIndex++] = [callback, dependencies];  return callback; } Copy the code

The source code

export function useCallback<T>(
  callback: T,
  inputs: Array<mixed> | void | null,
): T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
 workInProgressHook = createWorkInProgressHook(); // Almost both of these sentences are used in hooks  const nextInputs = inputs ! == undefined && inputs ! == null ? inputs : [callback]; // Use callback if undefined or null, or use dependency otherwise  const prevState = workInProgressHook.memoizedState;  if(prevState ! == null) {const prevInputs = prevState[1]; / / dependencies if(areHookInputsEqual(nextInputs, prevInputs)) {// Compare dependencies, also obtained from memoizedState returnprevState[0]; // Return the callback from the last memoizedState }  } workInProgressHook.memoizedState = [callback, nextInputs]; // The first or non-first time is different to save memoizedState returncallback; / / return the callback}  Copy the code

useMemo

Basic usage

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Copy the code

Two parameters. A callback, a dependency, not unlike useCallback, returns a Memoized function. Even the example gives a hidden hint that is only used when expensive calculations are made.

A simple version of useMemo

  
function useMemos(nextCreate, dependencies) {
  if(hookStates[hookIndex]) {// Indicates not the first rendering    let [lastMemo, lastDependencies] = hookStates[hookIndex];
    let same = dependencies.every((item, index) => item === lastDependencies);
 if (same) {  hookIndex++;  return lastMemo;  }  } const nextValue = nextCreate(); // This is not necessary// The first rendering or not the first but the same dependency returns a new one hookStates[hookIndex++] = [nextValue, dependencies];  return nextValue;  } Copy the code

The source code

export function useMemo<T>(
  nextCreate: () => T,
  inputs: Array<mixed> | void | null,
): T {
  currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
 workInProgressHook = createWorkInProgressHook();   const nextInputs = inputs ! == undefined && inputs ! == null ? inputs : [nextCreate]; const prevState = workInProgressHook.memoizedState;  if(prevState ! == null) { const prevInputs = prevState[1];  if (areHookInputsEqual(nextInputs, prevInputs)) {  return prevState[0];  }  }  const nextValue = nextCreate(); UseCallback = useCallback = useCallback = useCallback workInProgressHook.memoizedState = [nextValue, nextInputs];  return nextValue; } Copy the code

Usage scenarios

Source code differences

/ / hooks declare a different type on first input. / / hooks declare a different type on first input. / / hooks declare a different type on first input

useCallback

 export function useCallback<T>(
  callback: T,
  inputs: Array<mixed> | void | null,
): T {
}
Copy the code

useMemo

export function useMemo<T>(
  nextCreate: () => T,
  inputs: Array<mixed> | void | null,
): T {
}
Copy the code

It feels like useMemo is designed specifically to handle functions, although useCallback can do that as well, but I’ve seen quite a few examples. UseMemo is used this way, and it seems to form a convention

useMemo

const initialCandies = React.useMemo(
() = > ['snickers'.'skittles'.'twix'.'milky way'].[]. )
Copy the code

useCallback

const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c ! == candy))  }
  const dispenseCallback = React.useCallback(dispense, [])
Copy the code

When exactly

Usemmo-and-usecallback The original author is Kent C. Dodds

References are equal

Understood as a dependency reference (usually represented as a non-primitive type, such as an object, array, or function. They see the types and attributes as the same, but the references as different.

function Foo({bar, baz}) {
  const options = {bar, baz}
  React.useEffect(() => {
    buzz(options)
  }, [options]) // we want this to re-run if bar or baz change
 return <div>foobar</div> }  function Blub() {  return <Foo bar="bar value" baz={3} /> } Copy the code

Can you tell what’s wrong with this code? The useEffect callback is invoked each time. I changed the code slightly for better testing

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      bar: ['1']. baz: ['1']. };  }   handleChangeBazInput = (e) => {  this.setState({  baz: [e.target.value],  });  };   handleChangeBarInput = (e) => {  this.setState({  bar: [e.target.value],  });  };   render() {  return (  <div>  <input type="text" onBlur={this.handleChangeBazInput}></input>  <input type="text" onBlur={this.handleChangeBarInput}></input>  <Foo baz={this.state.baz} bar={this.state.bar}></Foo>  </div>  );  } }  function Foo({ bar, baz }) {  const options = { bar, baz };  React.useEffect(() => { console.log(options); // Whether bar and baz have changed or not, this will be printed here}, [options]); [baz,bar] [baz,bar] [baz,bar return (  <div>  <div> {baz}</div>  <div> {bar}</div>  </div>  ); }  ReactDOM.render(<App />, document.getElementById("root")); Copy the code

But not if bar and baz are generic values, such as string

this.state = {
      bar: '1'.      baz: '1'.    };
  }
  handleChangeBazInput = (e) => {  this.setState({  baz: e.target.value,  });  };   handleChangeBarInput = (e) => {  this.setState({  bar: e.target.value,  });  };   React.useEffect(() => {  console.log(options); }, [bar,baz]); // Note that this is not options Copy the code

This is an example given by the author. I don’t think it is very specific

before

function Foo({bar, baz}) {
  React.useEffect(() => {
    const options = {bar, baz}
    buzz(options)
  }, [bar, baz]) // we want this to re-run if bar or baz change
 return <div>foobar</div> } function Blub() {  const bar = () => {}  const baz = [1, 2, 3]  return <Foo bar={bar} baz={baz} /> } Copy the code

after

function Foo({bar, baz}) {
  React.useEffect(() => {
    const options = {bar, baz}
    buzz(options)
  }, [bar, baz])
 return <div>foobar</div> }  function Blub() {  const bar = React.useCallback(() => {}, [])  const baz = React.useMemo(() => [1, 2, 3], [])  return <Foo bar={bar} baz={baz} /> } Copy the code

I myself tried using usecallback to pack up a layer and found and before before. Bar and baz are always printed when they are arrays

function App() {
  const [bar, setBar] = useState(["1"]);
  const [baz, setBaz] = useState(["1"]);

  const handleChangeBazInput = useCallback((e) => {
 setBaz([e.target.value]); } []);  const handleChangeBarInput = useCallback((e) => {  setBar([e.target.value]); } []);  return (  <div>  <input type="text" onBlur={(e) => handleChangeBazInput(e)}></input>  <input type="text" onBlur={(e) => handleChangeBarInput(e)}></input>  <Foo baz={baz} bar={bar}></Foo>  </div>  ); }  function Foo({ bar, baz }) {  const options = { bar, baz };  React.useEffect(() => { console.log(options); // This will still print }, [bar, baz]);  return (  <div>  {""}  <div>{baz}</div> <div> {bar}</div>  </div>  ); } Copy the code

I don’t know if there is a problem with my understanding, if you know, you can tell me

Huge overhead

This is not a big deal, because we rarely have to deal with huge calculations in our work

before

function RenderPrimes({iterations, multiplier}) {
  const primes = calculatePrimes(iterations, multiplier)
  return <div>Primes! {primes}</div>
}
Copy the code

after

function RenderPrimes({iterations, multiplier}) {
  const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [
    iterations,
    multiplier,
  ])
 return <div>Primes! {primes}</div> } Copy the code

Code that produces an endless loop


  const searchList = useCallback(() => {
   fetchMyVisitList({
      page: {
        pageNo: 1,
 pageSize: 20  },  params: {}  }).then(res => {  if (res.success) {  // do something  }  })  }, [list])   useEffect(() => {  searchList()  }, [searchList]) // All dependencies are added automaticallyCopy the code

conclusion

First said useCallback and useMemo basic usage, and then wrote a simple version, and then from the source analysis. The idea is the same, with Memoization. As Kent C. Dodds says, performance tuning costs money. When you don’t need it, you don’t need it at all, and it’s worse if you use it. Finally, I wrote an example about using useCallback and not using useCallback, and found there was no difference at all. I wonder if I didn’t understand it well enough. If you know, can you tell me