React Hooks implement React Hooks

React Hooks(2) Event binding side effects

preface

Introduction to Functional programming (from Kiwi Encyclopedia)

Functional programming, or functional programming, is a programming paradigm that treats computer operations as functional operations and avoids the use of program states and mutable objects. Among them, lambda calculus is the most important foundation of the language. Furthermore, a function of the lambda calculus can accept functions as inputs (arguments) and outputs (outgoing values).

Introduction to Object-oriented Programming (from Kiwi Encyclopedia)

Object-oriented Programming (OOP) is a kind of programming paradigm with Object concept, as well as an abstract policy of program development. It may contain data, attributes, code, and methods. An object is an instance of a class. It regards the object as the basic unit of the program and encapsulates the program and data to improve the reuse, flexibility and expansibility of the software. The program in the object can access and often modify the data related to the object. In object-oriented programming, computer programs are designed as objects related to each other

The functional emphasizes invariance in logical processing. Object orientation changes the internal state of each Object through messaging. They are very different programming ideas, and both have their advantages, which makes the transition from class components to functional components a little confusing.

React is moving closer to functional programming with consistent input and output. Not out of thin air, but in order to solve more problems. Three is the react website mentioned zh-hans.reactjs.org/docs/hooks- hooks motives…

  • Code reuse: Before hooks came along, the common ways of code reuse were HOC and render props, and the problem with both is that you need to deconstruct your own components and introduce deep component nesting
  • Complex component logic: In a class component there are many lifecycle functions and you will need to do things within each function. The pain point of this approach is that the logic is scattered all over the place, the developer is distracted from maintaining the code, and it is difficult to understand the logic
  • Class component confusion: It is difficult for beginners to understand this in class components, and class-based components are difficult to optimize.

The purpose of this article is to prepare you for the next one because if you understand the principle of React Hooks and explain them step by step, you will probably understand everything about React Hooks.

Hooks are mysterious at first. Once we get used to writing class-like components, our minds are fixed on them, the use of life cycles, state, this, etc. Therefore, functional components are written in class mode, which leads us to climb over and over again, so we’ll start our implementation. (Note: the following is only a simple simulation method, with some differences from the actual, but the core idea is the same)

start

Let’s start with a simple React functional component.

function Counter(count) {
  return (
    <div>
      <div>{count}</div>
      <button>Click on the</button>
    </div>
  );
}
Copy the code

Before React Hooks, our components were mostly used for direct rendering without state storage. Function components had no state, so they were also called SFC (Stateless functional Component). The update is now called FC (Functional Component).

In order to make a function inside the state, the react to use a special method is hooks, actually this is to use the closure of the implementation of a similar scope to store state, I first think of is to use an object reference stored data, like a object-oriented way, there is an object, by reference to obtain. React, however, uses closures nicely in order to isolate states as much as possible.

Let’s see how he does it. (I adapted it to make it as simple as possible.)

useState

let _state;
function useState(initialState) {
	_state = _state || initialState; // If there is an old value, so that the state remains after multiple renders.
  function setState(newState) {
    _state = newState;
    render();  // Re-render, Counter will be re-executed
  }
  return [_state, setState];
}
Copy the code
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <div>{count}</div>
      <button onClick={()= >SetCount (count + 1)}> Click</button>
    </div>
  );
}
Copy the code

IO /s/dawn-bash…

Above, no matter how many times Counter is re-rendered, you can still access the latest state through the closure, thus achieving the effect of storing the state.

useEffect

Take a look at the useEffect. useEffect(callback, dep?) Below is a very simple use example.

  useEffect(() = > {
      console.log(count);
  }, [count]);
Copy the code
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() = > {
    console.log(count);
  }, [count]);
  return (
    <div>
      <div>{count}</div>
      <button onClick={()= >SetCount (count + 1)}> Click</button>
    </div>
  );
}
Copy the code

Because functions don’t have the same complicated life cycle as classes, you already familiar with hooks will probably know that useEffect can be used as ComponentDidmount. But here you just do it in order. It will execute before return.

let _deps = {
  args: []};// _deps records the last useEffect dependency
function useEffect(callback, args) {
  const hasChangedDeps = args.some((arg, index) = >arg ! == _deps.args[index]);// Check whether the dependencies are exactly the same
  // If dependencies does not exist, or dependencies change
  if (!_deps.args || hasChangedDeps) {
    callback();
    _deps.args = args;
  }
}
Copy the code

Demo address: codesandbox. IO/s/ecstatic -…

At this point, we have also implemented a single useEffect.

useMemo

UseMemo also uses closures to store data to improve performance.

function Counter() {
  const [count, setCount] = useState(0);
  const computed = () = > {
    console.log('I did it.');
    return count * 10 - 2;
  }
  const sum = useMemo(computed, [count]);
  return (
    <div>
      <div>{count} * 10 - 2 = {sum}</div>
      <button onClick={()= >SetCount (count + 1)}> Click</button>
    </div>
  );
}
Copy the code

Now let’s implement it

let _deps = {
  args: []};// _deps records the last useMemo dependency
function useMemo(callback, args) {
  const hasChangedDeps = args.some((arg, index) = >arg ! == _deps.args[index]);// Check whether the dependencies are exactly the same
  // If dependencies does not exist, or dependencies change
  if(! _deps.args || hasChangedDeps) { _deps.args = args; _deps._callback = callback; _deps.value = callback();return _deps.value;
  }

  return _deps.value;
}
Copy the code

IO/S/Festive -P…

useCallback

What about useCallback? It’s just a wrapper around useMemo, after all, you’re caching the return value of a function, so why don’t I just let the return value be a function?

function useCallback(callback, args) {
	return useMemo(() = > callback, args);
}
Copy the code

As you can see, we’ve easily implemented useMemo as well. There is a problem, however, that this is just a single function, so we need to deal with multiple functions.

The full version

We can do this the way preact does. Use arrays to implement the processing logic of multiple functions.

The core logic is

  • The state of the hook functions useState, useEffect, useMemo, useCallback, and so on are stored in an array when first declared.

  • When updating, take out the state values of the previous function successively.

It can also be understood by the following figure

For the first rendering, each state is cached into an array.

Each time you re-render, get the cache state of each of the arrays.

In order to make the principle clear, a few cuts have been made. But the core logic remains the same.

let currentIndex = 0;
let currentComponent = {
  __hooks: []};function getHookState(index) {
  const hooks = currentComponent.__hooks;
  if (index >= hooks.length) {
    hooks.push({});
  }
  return hooks[index];
}

function argsChanged(oldArgs, newArgs) {
  return! oldArgs || newArgs.some((arg, index) = >arg ! == oldArgs[index]); }function useState(initialState) {
  const hookState = getHookState(currentIndex++);
  hookState._value = [
    hookState._value ? hookState._value[0] : initialState,
    function setState(newState) {
      hookState._value[0] = newState;
      render(); // Re-render, Counter will be re-executed}];return hookState._value;
}

function useEffect(callback, args) {
  const state = getHookState(currentIndex++);
  if(argsChanged(state._args, args)) { callback(); state._args = args; render(); }}function useMemo(callback, args) {
  const state = getHookState(currentIndex++);
  if (argsChanged(state._args, args)) {
    state._args = args;
    state._callback = callback;
    state.value = callback();
    return state.value;
  }

  return state.value;
}
Copy the code

Now you have a simple React Hooks implementation with the above 43 lines.

Complete rendering process

Let’s go through the overall flow chart again to explain the full version of the implementation.

Codesandbox. IO/s/loving – bl…

function Counter() {
  const [count, setCount] = useState(0);
  const [firstName, setFirstName] = useState("Rudi");

  const computed = () = > {
    return count * 10 - 2;
  };
  const sum = useMemo(computed, [count]);

  useEffect(() = > {
    console.log("init"); } []);return (
    <div>
      <div>
        {count} * 10 - 2 = {sum}
      </div>
      <button onClick={()= >SetCount (count + 1)}> Click</button>
      <div>{firstName}</div>
      <button onClick={()= > setFirstName("Fred")}>Fred</button>
    </div>
  );
}
Copy the code

Initialize the

First render

Store all the states in the closure.

Events trigger

Changed the value of the second state.

Second render

Take all the states out in turn and render.

Afterword.

With this implementation, we can also see some of the React Hooks that seem a bit odd. For example, why not call it in a loop, a conditional judgment, or a child function? Because order is important, we push the cache (state) into the array in a certain order, so the last state fetched must be fetched in the same order. Otherwise, it will result in inconsistent retrieval… Of course, we can imagine that if each unit of state could have a unique name, it would not be bound by these rules. But this will cause the development to pass in extra parameters that are unique names. There are also name conflicts, so using conventions in this way simplifies the development costs of developers to a certain extent and eliminates inconsistencies. (PS: If anyone is interested, you can implement a version that does not depend on the order, only depends on the name, as a small toy ~)

Of course, in real life React uses single-linked lists instead of arrays. Slightly different, but essentially the same idea, and useEffect is run after each render.

The above is standing on the shoulders of giants (there are a lot of excellent articles, see reference), plus look at some source code out of the whole process. Finally, I leave a small question for you. What is the logic of the return function in every useEffect? You are welcome to say how to implement it in the comments section. If there are any problems with the article, you are also welcome to point them out in the comments section

reference

Github.com/brickspert/…

Segmentfault.com/a/119000001…

www.zhihu.com/question/30…

Zh-hans.reactjs.org/docs/hooks-…

medium.com/@ryardley/r…

github.com/preactjs/

Zh-hans.reactjs.org/docs/hooks-…

Pay more attention