Author: RichLab Yanliang

React-hooks Editor’s note: This is an in-depth article about react-hooks, which take time to read and understand. Welcome to more exciting interactions
zhihu.

1 Why Hook?

1.1 From React component design theory

React defines front-end development constraints in a new programming paradigm that brings a new mental model to view development:

  • React argues that the UI view is a visual mapping of data, i.eUI = F(DATA)Here,FResponsible forProcess input data and respond to changes in data
  • In the formulaFReact abstracts into components. React isOrchestrate applications Based on component-based granularityA component is the smallest unit of code reuse
  • In design, React usespropsProperty to receive external data, usingstateProperty to manage the data (state) generated by the component itself, and to implement (runtime) the need to React to data changes, ReactAdopt class-based component design!
  • In addition, React believes that components have a life cycle, thus pioneering the concept of life cycle into component design, from component create to DeStory provides a series of apis for developers to use

This is the rationale for designing the React component. The React component we are most familiar with looks like this:

Class MyConponent extends React.Component {// React generates data state = {counts: ClickHandle = () => {this.setState({counts: this.state.counts++});if (this.props.onClick) this.props.onClick();
  }

  // lifecycle API
  componentWillUnmount() {
    console.log('Will mouned! ');
  }

    // lifecycle API
  componentDidMount() {
    console.log('Did mouned! '); } // Accept external data (or processing) and render the data visually (props) {return( <> <div>Input content: {props.content}, btn click counts: {this.state.counts}</div> <button onClick={this.clickHandle}>Add</button> </> ); }}Copy the code

1.2 Class Component problems

1.2.1 Component reuse dilemma

Components are not simple islands of information. They may be related to each other. On the one hand, they share data and on the other, they reuse functions:

  • React uses One-way data flow (Flux) to solve data sharing problems between components
  • The React team has come up with a number of solutions for reusing (statically) components, initially using CreateClass + Mixins and later using Class Component instead of CreateClassRender PropsandHigher Order ComponentThe React team continued to explore Component reuse until the Function Component+ Hooks design

The age-old problem with HOC use:

  • Nested hell, where every HOC call produces a component instance
  • You can use class decorators to mitigate the maintainability issues that come with component nesting, but decorators are still HOC in nature
  • Too many layers of wrapping can cause problems with the props attribute

Render Props:

  • The data flow is more intuitive, and descendant components can clearly see where the data is coming from
  • But essentially the Render Props are implemented based on closures, and the heavy reuse of components inevitably introduces the problem of callback Hell
  • The component’s context is missing, so nothis.propsAttributes that cannot be accessed like HOCthis.props.children

1.2.2 Javascript Class defects

1. The direction of this (language defect)

class People extends Component {
  state = {
    name: 'dm', age: 18,} handleClick(e) {// error! console.log(this.state); }render() {
    const { name, age } = this.state;
    return(<div onClick={this.handleClick}>My name is {name}, i am {age} years old.</div>); }}Copy the code

The createClass does not need to handle the reference to this. A slight error will occur in the Class Component because of the reference to this.

2, compile size (and performance) problem:

// Class Component
class App extends Component {
  state = {
    count: 0
  }

  componentDidMount() {
    console.log('Did mount! ');
  }

  increaseCount = () => {
    this.setState({ count: this.state.count + 1 });
  }

  decreaseCount = () => {
    this.setState({ count: this.state.count - 1 });
  }

  render() {
    return (
      <>
        <h1>Counter</h1>
        <div>Current count: {this.state.count}</div>
        <p>
          <button onClick={this.increaseCount}>Increase</button>
          <button onClick={this.decreaseCount}>Decrease</button>
        </p>
      </>
    );
  }
}

// Function Component
function App() {
  const [ count, setCount ] = useState(0);
  const increaseCount = () => setCount(count + 1);
  const decreaseCount = () => setCount(count - 1);

  useEffect(() => {
    console.log('Did mount! '); } []);return (
    <>
      <h1>Counter</h1>
      <div>Current count: {count}</div>
      <p>
        <button onClick={increaseCount}>Increase</button>
        <button onClick={decreaseCount}>Decrease</button>
      </p>
    </>
  );
}
Copy the code

Class Component compiler results (Webpack) :

var App_App = function (_Component) {
  Object(inherits["a"])(App, _Component);

  function App() {
    var _getPrototypeOf2;
    var _this;
    Object(classCallCheck["a"])(this, App);
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    _this = Object(possibleConstructorReturn["a"])(this, (_getPrototypeOf2 = Object(getPrototypeOf["a"])(App)).call.apply(_getPrototypeOf2, [this].concat(args)));
    _this.state = {
      count: 0
    };
    _this.increaseCount = function () {
      _this.setState({
        count: _this.state.count + 1
      });
    };
    _this.decreaseCount = function () {
      _this.setState({
        count: _this.state.count - 1
      });
    };
    return _this;
  }
  Object(createClass["a"])(App, [{
    key: "componentDidMount",
    value: function componentDidMount() {
      console.log('Did mount! ');
    }
  }, {
    key: "render",
    value: function render() {
      returnreact_default.a.createElement(/*... * /); }}]);return App;
}(react["Component"]);
Copy the code

Function Component compiles (Webpack) :

function App() {
  var _useState = Object(react["useState"])(0),
    _useState2 = Object(slicedToArray["a" /* default */ ])(_useState, 2),
    count = _useState2[0],
    setCount = _useState2[1];
  var increaseCount = function increaseCount() {
    return setCount(count + 1);
  };
  var decreaseCount = function decreaseCount() {
    return setCount(count - 1);
  };
  Object(react["useEffect"]) (function () {
    console.log('Did mount! '); } []);return react_default.a.createElement();
}
Copy the code

  • Javascript implementation of the class itself is relatively weak, there is no similar Java/C++ multiple inheritance concept, the logical reuse of the class is a problem
  • The Class Component is treated as Javascript inside ReactThe Function classTo deal with the
  • Function Component is compiled to be a normal Function that is js engine friendly

🤔 Question: How does React distinguish pure function components from class components?

1.3 Missing functions of Function Component

Not all components need to handle life cycles. Function Components were designed early in React release to simplify Class Component writing with render only.

  • Function Components are pure functions that facilitate Component reuse and testing
  • The problem with the Function Component is that it simply receives props, binds events, and returns JSX. It is a stateless Component and relies on the Handle passed by props to respond to changes in data (state). So Function Component cannot exist without Class Comment!
functionChild(props) { const handleClick = () => { this.props.setCounts(this.props.counts); }; // UI changes can only be made with Parent Component update props! !return (
    <>
      <div>{this.props.counts}</div>
      <button onClick={handleClick}>increase counts</button>
    </>
  );
}

class Parent extends Component() {// State management depends on Class Component counts = 0render () {
    const counts = this.state.counts;
    return( <> <div>sth... </div> <Child counts={counts}setCounts={(x) => this.setState({counts: counts++})} /> </> ); }}Copy the code

Therefore, whether Function Comonent can exist independently from Class Component depends on enabling Function Comonent to have state processing capability. That is, after the first render of the Component, “The component itself has a mechanism to trigger state changes and cause re-render,” which are Hooks!

Hooks fix the shortcoming of Function Component relative to Class Component and enable Function Component to replace Class Component.

1.4 Function Component + Hooks

1, function is relatively independent, and render irrelevant parts, can be directly removed to hook implementation, such as request library, login state, user core, buried point, etc., theoretically decorators can be used to hook implementation (such as react-use, provides a large number of UI, animation, events and other common functions hook implementation).

Case: Popup component depends on window width to adapt its display width, album component depends on window width to adapt single/multi-column layout

🤔 : Imagine how to do this using Class Component

function useWinSize() {
  const html = document.documentElement;
  const [ size, setSize ] = useState({ width: html.clientWidth, height: html.clientHeight });

  useEffect(() => {
    const onSize = e => {
      setSize({ width: html.clientWidth, height: html.clientHeight });
    };

    window.addEventListener('resize', onSize);

    return () => {
      window.removeEventListener('resize', onSize);
    };
  }, [ html ]);

  returnsize; } // Depending on the width of win, fit the image layoutfunction Article(props) {
  const { width } = useWinSize();
  const cls = `layout-${width >= 540 ? 'muti' : 'single'}`;

  return( <> <article>{props.content}<article> <div className={cls}>recommended thumb list</div> </> ); } // The elastic layer width is adapted to win width and heightfunction Popup(props) {
  const { width, height } = useWinSize();
  const style = {
    width: width - 200,
    height: height - 300,
  };
  return (<div style={style}>{props.content}</div>);
}
Copy the code

2, render related UI and function (state) can also be separated, the function in hook implementation, state and UI separation

Case: form verification

function App() {
  const { waiting, errText, name, onChange } = useName();
  const handleSubmit = e => {
    console.log(`current name: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <>
        Name: <input onChange={onChange} />
        <span>{waiting ? "waiting..." : errText || ""}</span>
      </>
      <p>
        <button>submit</button>
      </p>
    </form>
  );
}
Copy the code

Implementation and use of Hooks

2.1 useState

useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]
Copy the code

Function: Returns a state and setters that modify the state, called tuples in other languages. Once mounted, the state can only be modified by this setter.

🤔 : Why does useState return tuple instead of object?

UseState function declaration

  • A function component that uses the Hooks API returns setters that change the state of the component and cause the component re-render
  • Unlike the normal hook, the hook can be called multiple times with different effects, and the hook will die along with the Fiber Node

2.1.1 Why can I only call the Hooks API in Function Component?

The default implementation of the Hooks API:

function throwInvalidHookError() {
  invariant(false.'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.');
}

var ContextOnlyDispatcher = {
    ...
  useEffect: throwInvalidHookError,
  useState: throwInvalidHookError,
  ...
};
Copy the code

When calling Hook in Function Component:

function renderWithHooks(current, workInProgress, Component, props, refOrContext, nextRenderExpirationTime) {
  currentlyRenderingFiberThe $1= workInProgress; // The pointer points to the currently render fiber node....if(nextCurrentHook ! == null) {// Data update ReactCurrentDispatcherThe $1.current = HooksDispatcherOnUpdateInDEV;
  } else{// first render ReactCurrentDispatcherThe $1.current = HooksDispatcherOnMountInDEV; }} / / / hook API implementation HooksDispatcherOnMountInDEV = {... useState:function (initialState) {
    currentHookNameInDev = 'useState'; .returnmountState(initialState); }};Copy the code

2.1.2 Why must the Hooks API be called at the top of the function component scope?

In the class component, state is an object corresponding to the FiberNode memoizedState property, which is updated in the class component when setState() is called. But in the function component, memoizedState is designed as a linked list (Hook object) :

// Hook type definitiontypeHook = {memoizedState: any, / / store the latest state baseState: any baseUpdate: Update < any, any > | null, queue: UpdateQueue < any, any > | null, / / update the queue next: Hook | null, / / the next Hook} / / define an updatetypeUpdate<S, A> = { ... Action: A, eagerReducer: ((S, A) = > S) | null, eagerState: S | null, / / next to Update the status value: the Update < A > S, | null,... }; // The queue definition needs to be updatedtypeUpdateQueue < S, A > = {last: Update the < A > S, | null, / / last Update operation dispatch: (A = > mixed) | null, lastRenderedReducer: ((S, A) = > S) | null, / / the latest process state of reducer lastRenderedState: S | null, / / after the latest rendering state};Copy the code

Example:

function App() {
  const [ n1, setN1 ] = useState(1);
  const [ n2, setN2 ] = useState(2);

  // if (sth) {
  //    const [ n4, setN4 ] = useState(4);
  // } else {
  //    const [ n5, setN5 ] = useState(5);
  // }

  const [ n3, setN3 ] = useState(3);
}
Copy the code

Hook storage (linked list) structure:

Fiber (Hook) linked list structure

  • The Hook API call produces a corresponding Hook instance (appended to the Hooks chain), but returns state and the corresponding setter to the component, and the framework does not know when re-render which Hooks instance that setter corresponds to (unless the Hooks are stored in a HashMap, This requires that the key be passed to React, which increases the complexity of using Hooks.
  • Re-render will re-execute the entire component from the first line of codeThat is, the entire Hooks chain is executed sequentially, or if STH is not satisfied when re-render is performeduseState(5)The branch, in contrast to useState(4), is not executed, resulting inuseState(5)The return value is actually 4, becauseAfter first render, the corresponding Hook’s memoizedState can only be modified with the dispatch returned by useStateTherefore, it must beKeep the order of Hooks unchangedCannot call Hooks on the branch, only on the top level will ensure that the Hooks are executed in the correct order!

2.1.3 How does useState Hook update data?

UseState () mount stage (part) source code implementation:

// useState() executes mountState for first renderfunction(initialState) {// Create a new hook object from the current Fiber, attach the hook to the end of the Fiber hook chain, and return the hook var hook = mountWorkInProgressHook();  hook.memoizedState = hook.baseState = initialState; var queue = hook.queue = { last: null, dispatch: null, lastRenderedReducer: (state, action) => isFn(state) ? action(state) : action, lastRenderedState: initialState }; // currentlyRenderingFiberThe $1Save the Fiber node currently being rendered // connect the returned Dispatch to the node that called the hook, Dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiberThe $1, queue);
  return[hook.memoizedState, dispatch]; } //// functions as followssetThe State!functiondispatchAction(fiber, queue, action) { ... Var update = {action, // next: null,}; var last = queue.last;if (last === null) {
    update.next = update;
  } else{ last.next = update; Queue. Last = update; . ScheduleWork (Fiber, expirationTime); }Copy the code

  • dispatchActionThe function is the key to updating state, and it generates oneupdateMount to the Hooks queue, commit a React update schedule, and do the same as the class component.
  • It is theoretically possible to call dispatch multiple times at the same time, but only the last call will take effect (the last pointer to the queue points to the state of the last update).
  • Pay attention touseStateUpdate data andsetStateThe difference is that the former will merge with old state, so we just need to pass in the changes, butuseStateDirect coverage!

The Schedule phase is between the Reconcile and commit phases. The starting point of schedule is scheduleWork. ReactDOM. Render, setState, forceUpdate, React Hooks dispatchActions go through scheduleWork. Ref:
zhuanlan.zhihu.com/p/54042084


Update phase (state changes, parent re-render, etc.) useState() updates state:

function updateState(initialState) {
  var hook = updateWorkInProgressHook();
  var queue = hook.queue;
  var newState;
  var update;

  if(numberOfReRenders > 0) {// Component own re-render newState = hook. MemoizedState; // renderPhaseUpdates is a global variable that is a HashMap structure: HashMap<(Queue: Update)> Update = renderphaseupdates.get (Queue); }else {
    // update
    newState = hook.baseState;
    update = hook.baseUpdate || queue.last;
  }

  do{ newState = update.action; Update = update.next; }while(update ! == null) hook.memoizedState = newState;return [hook.memoizedState, queue.dispatch];
}
Copy the code

  • React executes the entire update queue on hook objects in sequence to get the latest state, so the tuple[0] returned by useState() will always be the latest state!
  • As you can see, initialState is not used at all during the Update phase!

2.1.4 useState Hook Update process

function App() {
  const [n1, setN1] = useState(1);
  const [n2, setN2] = useState(2);
  const [n3, setN3] = useState(3);

  useEffect(() => {
    setN1(10);
    setN1(100); } []);return (<button onClick={() => setN2(20)}>click</button>);
}
Copy the code

Graphical update process:

UseState Update process

  • SetState returns setter execution that results in re-render
  • Multiple setter operations are merged internally (loop through incoming setters to ensure useState gets the latest state)

2.2 useEffect

useEffect(effect: React.EffectCallback, deps? : ReadonlyArray<any> | undefined)Copy the code

ComponentDidMount, componentDidUpdate, componentWillUnmount, etc.

2.2.1 useEffect Implementation Analysis

HooksDispatcherOnMountInDEV = {
    useEffect: function() {
    currentHookNameInDev = 'useEffect'; .returnmountEffectImpl(Update | Passive, UnmountPassive | MountPassive, create, deps); }};function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  return hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
}

functionPushEffect (tag, create, destroy, deps) {var effect = {tag: tag, create: create, useEffect: Destroy, // Store the return function of the callback passed in by useEffect for effect cleanup deps: deps, next: null}; . componentUpdateQueue = createFunctionComponentUpdateQueue(); componentUpdateQueue.lastEffect = effect.next = effect; .return effect;
}

function renderWithHooks() {... currentlyRenderingFiberThe $1.updateQueue = componentUpdateQueue; . }Copy the code

  • Unlike useState, which passes in a specific state, useEffect callback is a callback function. The biggest difference with useState is the execution timing. UseEffect callback is executed after the component has been rendered as a real DOM (so it can be used for DOM manipulation).
  • The useEffect call also appends a hook to the Hooks chain of the current Fiber node and returns one to its memoizedStateeffectObject,effectThe object will eventually be mounted to the Fiber nodeupdateQueueQueue (when all the Fiber nodes are rendered on the page, the execution of the Fiber nodes will beginupdateQueueThe functions saved in)

2.2.2 DEPS parameters are important

Here’s a very common piece of code, 🤔 what’s wrong with it? Run the demo

/ / write with a Hookfunction App() {
  const [data, setData] = useState(' ');

  useEffect(() => {
    setTimeout(() => {
      setData(`current data: ${Date.now()}`);
    }, 3000);
  });

  return<div>{data}</div>; } class App extends Component {state = {data =' '}

  componentDidMount() {
    setTimeout(() => {
      this.setState({ data: `current data: ${Date.now()}`}); }, 3000); }render() {
    return<div>{this.state.data}</div>; }}Copy the code

  • In re-render, the function component re-executes the entire function, including all “registered” hooks. By default, useEffect callback is also re-executed!
  • UseEffect can accept the second argumentdepsDeps must be passed in as the actual dependency, no less, no more!
  • Deps elements must be mutable; for example, useRef, dispatch, etc. cannot and need not be passed
  • Deps is a shallow comparison (see source code), passing in objects and functions is meaningless
  • As a best practice, pass dePS whenever possible when using useEffect.

2.2.3 Cleaning side effects

The callback callback passed in by useEffect returns a function that will be executed during Fiber cleanup:

function App() {
  useEffect(() => {
    const timer = setTimeout(() => {
        console.log('print log after 1s! ');
    }, 1000);
    window.addEventListener('load', loadHandle);

    return () => window.removeEventListener('load', loadHandle); }, []); } class App extends Component {componentDidMount() {
    const timer = setTimeout(() => {
        console.log('print log after 1s! ');
    }, 1000);
    window.addEventListener('load', loadHandle);
  }

  componentDidUnmount() {
    window.removeEventListener('load', loadHandle); }}Copy the code

2.3 useContext

For state sharing between components, the Context API is officially provided in the class component:

  • useReact.createContextThe API creates the Context, which allows state sharing because it can be invoked outside the component
  • useContext.ProviderAPI mount status in the upper layer component
  • useContext.ConsumerApis provide state or pass for specific componentscontextTypeProperty specifies the component’s reference to the Context

When consuming the state provided by the context, you must specify the context reference using the contextType property or wrap the component with < context.consumer >, which is inconvenient to use (see the React Context official example).

The React team provides the useContext Hook API for function components to retrieve the state stored in the Context inside function components:

useContext<T>(Context: ReactContext<T>, unstable_observedBits: void | number | boolean): T
Copy the code

The useContext implementation is relatively simple. It simply reads the _currentValue value mounted on the context object and returns:

functionUseContext (content, observedBits) {// Handle observedBits temporarily // isPrimaryRenderer will only be used in React Nativefalse
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
Copy the code

To understand the implementation of useContext, you must first understand the Context source code implementation.
The React source series | React Context explanation”

UseContext greatly simplifies the process of consuming Context and makes it possible to share state between components. In fact, a large part of the community’s current state management scheme is based on useContext (the other is useState), The exploration of state management schemes is described in a later article.

2.4 useReducer

useReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init? : I => S, ): [S, Dispatch<A>]Copy the code

Use to manage complex data structures (useState is generally used to manage the state of flat structures). It basically implements the core functions of Redux. In fact, it is easy to implement a useReducer Hook based on the Hooks Api:

const useReducer = (reducer, initialArg, init) => {
  const [state, setState] = useState(
    init ? () => init(initialArg) : initialArg,
  );
  const dispatch = useCallback(
    action => setState(prev => reducer(prev, action)),
    [reducer],
  );
  return useMemo(() => [state, dispatch], [state, dispatch]);
};
Copy the code

Reducer provides the ability to rearrange state outside of the component, while the Dispatch object returned by useReducer is “performance safe” and can be passed directly to child components without causing child re-render.

functionReducer (state, action) {reducer(state, action) { switch (action.type) {case "increment":
      return{... state, count: state.count + state.step, }; . }}function App() {
  const [state, dispatch] = useReducer(reducer, {count: initialCount, step: 10});

  return (
    <>
      <div>{state.count}</div>
      // redux like diaptch
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <ChildComponent dispatch={dispatch} />
    </>
  );
}
Copy the code

2.5 Memoization related Hooks API

2.5.1 useCallback

useCallback<T>(callback: T, deps: Array<mixed> | void | null): T
Copy the code

Due to the particularity of javascript functions, when the function signature is passed into useEffect as dePS, it will still cause re-render (even if the function body is not changed). This phenomenon also exists in class components:

// When a Parent re-render class extends Component {// When a Parent re-render class extends Component {render() { const someFn = () => {}; // when re-render, the someFn function is re-instantiatedreturn (
      <>
        <Child someFn={someFn} />
        <Other />
      </>
    );
  }
}

class Child extends Component {
  componentShouldUpdate(prevProps, nextProps) {
    returnprevProps.someFn ! == nextProps.someFn; // Function comparisons will always returnfalse}}Copy the code

Function Component (see demo) :

function App() {
  const [count, setCount] = useState(0);
  const [list, setList] = useState([]);
  const fetchData = async () => {
    setTimeout(() => {
      setList(initList);
    }, 3000);
  };

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return (
    <>
      <div>click {count} times</div>
      <button onClick={() => setCount(count + 1)}>Add count</button>
      <List list={list} />
    </>
  );
}
Copy the code

Solution:

  • Move the function outside the component (the disadvantage is that you can’t read the state of the component)
  • If conditions permit, move the function body touseEffectinternal
  • If the function calls more thanuseEffectInternal (if you need to pass it to a child component)useCallbackAPI wrapping function,useCallbackThe essence of dependency analysis of a function is to re-execute it when it depends on changes

2.5.2 useMemo & memo

useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T
Copy the code

UseMemo is used to cache the results of some time-consuming calculations and only re-execute the calculations if the dependent parameters change:

functionApp(props) { const start = props.start; const list = props.list; const fibValue = useMemo(() => fibonacci(start), [start]); Const MemoList = useMemo(() => <List List ={List} />, [List]);return (
    <>
      <div>Do some expensive calculation: {fibValue}</div>
      {MemoList}
      <Other />
    </>
  );
}
Copy the code

Simple understanding:
useCallback(fn, deps) === useMemo(() => fn, deps)

In the function component, React provides an API that does the same thing as the class and PureComponent. Memo will perform a shallow comparison of each object during its re-render. If the reference does not change, it will not trigger a rerender.

// Re-render const MemoList = react.memo (({list}) => {return (
    <ul>
      {list.map(item => (
        <li key={item.id}>{item.content}</li>
      ))}
    </ul>
  );
});
Copy the code

UseMemo is called inside a component and has access to its props and state, so it has more fine-grained dependency control than React. Memo.

2.6 useRef

The useRef Hook returns a mutable reference to a ref object, but useRef is much more versatile than ref and can store any javascript value, not just a DOM reference.

UseRef’s implementation is relatively simple:

/ / the mount stagefunction mountRef(initialValue) {
  var hook = mountWorkInProgressHook();
  var ref = { current: initialValue };
  {
    Object.seal(ref);
  }
  hook.memoizedState = ref;
  returnref; } // Update phasefunction updateRef(initialValue) {
  var hook = updateWorkInProgressHook();
  return hook.memoizedState;
}
Copy the code

UseRef is special:

  • UseRef is the only one of the Hooks apis that returns mutable data
  • The only way to change the useRef value is to change its current value, and the change in value does not cause re-render
  • Each time the component renders, useRef returns a fixed value, not the one described belowCapture Valuesfeatures

2.7 Other Hooks apis

  • UseLayoutEffect: UseLayoutEffect is executed before the node is drawn by the browser (componentDidMount and componentDidUpdate are executed at the same time).
  • UseDebugValue: Used for developer tool debugging
  • UseImperativeHandle: used with the forwardRef to customize the value exposed by the ref to the parent component

2.8 Capture Values

1. UseState has Capture values. View Demo

2. UseEffect has capture values

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`; }); // Click button three times in succession, the page title will be changed to 1, 2, 3, instead of 3, 3, 3return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
Copy the code

3. The Event Handle has Capture Values. View the Demo

4. All Hooks apis have capture values except useRef, check demo (setTimeout always gets state values), state is Immutable, ref is mutable.

functionmountRef(initialValue) { var hook = mountWorkInProgressHook(); var ref = { current: initialValue }; // ref is a plain object reference, with no closure {object.seal (ref); } hook.memoizedState = ref;return ref;
}
Copy the code

The Hooks apis that are not useRef related essentially form closures that have their own state, which is the essence of Capture Values.

2.9 Custom Components: Simulate some common life cycles

  • ComponentDidMount: Callback is no longer executed when re-render when deps is empty
// End of mount, updated to DOM const onMount =function useDidMount(effect) => {
    useEffect(effect, []);
};
Copy the code

  • componentDidUpdate
// Render DOM before rendering const onUpdate =function useUpdate(effect) => {
  useLayoutEffect(effect, []);
};
Copy the code

  • componentWillUnMount
const unMount = function useWillUnMount(effect, deps = []) => {
  useEffect(() => effect, deps);
};
Copy the code

  • ShouldComponentUpdate (or react.pureComponent)
Const MyComponent = react.memo (() => {return <Child prop={prop} />
}, [prop]);

// or
function A({ a, b }) {
  const B = useMemo(() => <B1 a={a} />, [a]);
  const C = useMemo(() => <C1 b={b} />, [b]);
  return (
    <>
      {B}
      {C}
    </>
  );
}
Copy the code

3. Hooks

1. Hooks solve component function reuse, but do not solve JSX reuse problem. For example, in (1.4) form validation case:

function App() {
  const { waiting, errText, name, onChange } = useName();
  // ...

  return( <form> <div>{name}</div> <input onChange={onChange} /> {waiting && <div>waiting<div>} {errText && <div>{errText}<div>}  </form> ); }Copy the code

Although user input and verification logic can be encapsulated into useName Hook, the DOM part is still coupled, which is not conducive to component reuse. We expect the React team to come up with an effective solution.

React-hooks obscure (or perhaps discard) the concept of the lifecycle, but introduce a higher threshold of learning in which Hooks Hooks understand the lifecycle, Hooks Rules, useEffect dependencies, etc. Hooks have higher barriers to use than Vue3.0’s upcoming Hooks.

3. Classes have much more OOP than functions. Use Hooks+Function Component to implement React. How to organize code in Function Component to make logic clear? Behind this is a trade-off between functional and object-oriented programming paradigms.

4 Ref

  • A Complete Guide to useEffect (翻译 : overreacted. IO /zh-hans/a-c…)
  • Function Component Primer
  • Close reading complete guide to useEffect
  • How ReactFiber works in concurrent mode
  • The Iceberg of React Hooks
  • React Hooks: Memoization
  • React’s useCallback and useMemo Hooks By Example
  • React hooks: not magic, just arrays
  • The React principle of Hooks
  • UseMemo and useCallback Usage Guide
  • React Context


Finally, thank you for reading such a long article

The Ant RichLab Front End Team is committed to sharing quality technical articles with you

Welcome to follow our column, share the article with your friends, and grow together 🙂



Our team is recruiting urgently: interactive graphics technology, front-end/full stack development, front-end architecture, algorithm, big data development and other directions, the expectation level P6+ ~ P7, the team technical atmosphere is good, big room for advancement, resume can be directly hit me [email protected]