Skip to content

Latest commit

 

History

History
111 lines (88 loc) · 2.88 KB

Hooks.md

File metadata and controls

111 lines (88 loc) · 2.88 KB

A question of useRef vs useState is an interesting on when discussing React. Although the use case differs a lot, the implementation not so much. Best, let us look at the code.

Here are some snippets from packages/react-reconciler/src/ReactFiberHooks.js. We start with the implementation of useRef hook:

function mountRef(initialValue) {
  ...

  const ref = { current: initialValue };
  if (__DEV__) {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  return ref;
}

Surprisingly, this is not a lot of code. An object with a property current is created and persisted, that is all. Additionally the Object.seal method is called in development build to prevent adding new properties. There is no re-render mechanism, but the hook is just a heap for data. From the implementation point it also bears no connection to DOM, which is just a common use case.

Now we will see that the useState hook does not look very different:

function mountState(initialState) {
  ...

  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;

  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });

  const dispatch = ...
    ...

  return [hook.memoizedState, dispatch];
}

The first line allows to lazy-initialize the state, but then the state object is persisted in the same way as in useRef. The fundamental difference lays in the way in which the data is updated, a call to update the state involves internal processing that leads to component re-render. Furthermore what is interesting is that useState is just a special case of reducer under the hood:

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}

Interestingly many other hooks share the same internals, here is the implementation of useCallback, which might be seen as just a wrapper:

function mountCallback(callback, deps): {
  ...

  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

What makes a small difference, that is deps comparison, is implemented in the update method:

function updateCallback(callback, deps) {
  ...

  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

useMemo ? Anyone interested ?

function mountMemo(nextCreate, deps) {
  ...

  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}