I particularly like the new hooks API, but it has some strange limitations when you use it. Here, I present a model for thinking about how to use the new API to address those who have trouble understanding the reasons for these rules.

Explore how hooks work

I’ve heard that some people are struggling with this new hooks API representation, so I thought I’d try to parse how it works, at least on the surface.

The rules of the hooks

On the hooks proposal, the official team suggested that we follow two rules when using it.

  • Do not call hooks in loops, conditional statements, or nested functions
  • Call hooks only in the React function

The latter I think goes without saying. To attach a behavior to a functional component, you need to be able to associate that behavior with the component in some way. However, I think the former can be confusing because programming with such aN API seems unnatural, and that’s what I’m going to explore today

State management in hooks is all about arrays

To get a clearer picture of hooks, let’s look at what a simple implementation of the hook API might look like. Please note that this is speculation, just one possible way to implement the API to get you to know him. This is not necessarily how apis work internally.

How to implementuseState()

Let’s examine an example here to demonstrate how the implementation of a state hook works. First we implement a component using useState():

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return <Button onClick={()= > setFirstName("Fred")}>Fred</Button>;
}
Copy the code

The idea behind the hooks API is that you can return a setter function as the second entry in the hook function, and the setter will control the state that the hooks manage.

So what does React have to do with it?

Let’s see how this works inside React. The above can be used to render specific components in an execution context. This means that the data stored here is not at the same level as the component currently being rendered. This state is not shared with other components, but it remains within the scope of subsequent rendering of a particular component.

1) Initialize two empty arrays setters and state, and set the current cursor to 0.

2) First render



useState()
state

First render: Writes items of the array in cursor increments.

Each subsequent render resets the cursor and only reads these values from each array.

Subsequent render: Reads the cursor increment item from the array

4) Each setter in setters has a corresponding cursor pointing to the state value of the state array. Call the setter method to change the state value of the corresponding position.

A simple implementation

The following code is a simple practice in principle:

let state = []; // Declare an array of access states
let setters = [];// Declare access to an array of state changing functions
let cursor = 0;/ / the cursor

/ / setter function
function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}

// useState pseudo-code implementation
export function useState(initVal) {
    state.push(initVal);
    setters.push(createSetter(cursor));


  const setter = setters[cursor];
  const value = state[cursor];

  cursor++;
  return [value, setter];
}

// Component code that uses hooks
function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
  const [lastName, setLastName] = useState("Yardley"); // cursor: 1

  return (
    <div>
      <Button onClick={()= > setFirstName("Richard")}>Richard</Button>
      <Button onClick={()= > setFirstName("Fred")}>Fred</Button>
    </div>
  );
}


console.log(state); // Render before: []
MyComponent();
console.log(state); // first selection: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Render later: ['Rudi', 'Yardley']

// Click the 'Fred' button

console.log(state); // After click: ['Fred', 'Yardley']


// Simulate the React rendering loop
function MyComponent() {
  cursor = 0; // Reset the cursor
  return <RenderFunctionComponent />; } / / renderingCopy the code

Why is order so important

Now, what happens if we change the hooks order of the component state rendering cycle based on some external factor? Do something the React team doesn’t support:

let firstRender = true;

function RenderFunctionComponent() {
  let initName;

  if (firstRender) {
    [initName] = useState("Rudi");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("Yardley");

  return <Button onClick={()= > setFirstName("Fred")}>Fred</Button>;
}
Copy the code

We have a useState call in the conditional statement to see what happens,

1) First render

2) Second render

firstName
lastName
Rudi

UseEffect pseudo-code implementation

Implement useEffect based on the above useState code:

function useEffect(callback, depArray) {
  consthasNoDeps = ! depArray;// Check if there is an array
  const deps = memoizedState[cursor]; // Remove dependencies
  consthasChangedDeps = deps ? ! depArray.every((el, i) = > el === deps[i])
    : true; // Check whether the array has changed
  if (hasNoDeps || hasChangedDeps) {
    callback(); / / execution
    memoizedState[cursor] = depArray; / / update
  }
  cursor++; / / update the cursor
}
Copy the code

conclusion

In our example, the implementation is based on large arrays. In React, its implementation is similar to a one-way linked list, connecting all the hooks.