Due to my work experience, I have not had enough experience in React development so far. When I came into contact with React Hook, I did not have personal and practical feelings about the advantages expressed in written form of React Hook, so I did not seriously understand the “real smell” of React Hook. In this article, I will try to analyze the advantages of React Hook based on my limited understanding and experience, and verify them in practice as well as what scenarios are suitable for using Hook.

Generally introduce a new thing (knowledge), divided into three parts: what is it? Why is that? How does it work?

React Hook?

Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.

React functional components are no longer stateless. You can hook them directly into class capabilities.

Why React Hook?

This is not a replacement for class components, or at least a guide to writing functional components. So why lead in this direction?

What’s wrong with the class component?

  • Over time, components become bloated and difficult to maintain and reuse
  • It’s messy to declare functions in periods
  • Special writing of class (this binding)
  • Class compilation is large, performance is poor, and hot loading is unstable
  • The class itself is highly complex

Among them, reuse is the most difficult problem, and also the most fundamental reason for hook.

How do we reuse class components without hooks?

  • Attributes apply colours to a drawing
  • High order component

Both approaches are limited by the inherent problems of class components and do not fundamentally solve the problem of improving component reuse, but only provide a means of reuse.

The combination of functional components and hooks allows components to have their own state, and can be highly reusable, reducing the number of class components in the page will be easier to maintain.

How is that good reusability? What are the specific advantages?

  • Function components have no life cycle
  • There is no special way of writing
  • Hook injection gives it the capability of a class component, but with a concise writing style
  • The essence of reuse is the encapsulation of hook capabilities, such as custom hooks, that are part of a separate functional component

If you’ve ever had to change a functional component to a class component in real development, you’ve entered a world that is difficult to maintain and reuse.

How to use React Hook?

Regular use

Let’s take a look at an example. Instead of starting with a simple API, let’s go straight to the gameplay of wrapping hooks

const useUserList = () = > {
    const [loading, setLoading] = useState(false);
  const [users, setUsers] = useState([]);
  const loadUsers = async params => {
    setLoading(true);
    setUsers([]);
    const users = await loadUsers('/request', params);
    setUsers(users);
    setLoading(false);
  };
  const addUsers = useCallback(
    user= > setUsers(users= > users.concat(user)),
    []
  );
  const deleteUsers = useCallback(
    use= > setUsers(users= > without(users, user)),
    []
  );
  return [users, {loading, loadUsers, addUsers, deleteUsers}];
}
Copy the code

The above is a simple encapsulation hook operation that is often used in business logic.

Experience the function of this hook first:

  • Provide a list of users
  • Provides load, add, and delete operations
  • There is also load state, which can affect the behavior of the page

What the hell is going on? Managing state and associated behavior is the encapsulation of state and behavior, which is the essence of reuse. In the future, just use the hook of user side data and operation reuse. So you see here, you’re not reusing the UI, you’re reusing the capabilities.

How to use it properly

Of course for me in the writing of the above has not been able to feel the scene of “delicious”, just changed a way out of the logic of writing, so try to look at the example above, in my opinion, more reasonable use of is to get the hook play its greater reusability value, if do this hook is the most meaningful. Check out the hardcore field analysis below.

From the above example, encapsulation is the packaging of state and associated behavior, so it can be understood that hook means “give me some variables and methods, and I’ll wrap you a hook”. From the lowest common point of view, this translates to the following code:

export const useMethods = (initialValue, methods) = > {
  const [value, setValue] = useState(initialValue);
  const boundMethods = useMemo(
    () = > Object.entries(methods).reduce(
        (methods, [name, fn]) = > {
        const method = (. args) = > {
          setValue(value= >fn(value, ... args)); }; methods[name] = method;return methods;
      },
      {}
    ),
    [methods]
  )
  return [value, boundMethods];
}
Copy the code

The above is only the lowest level hook encapsulation method, to solve the business logic also need other general hook to call.

Since Hook is a reusable capability to solve state and associated behavior, we should start from both state and behavior: state is a variable, variable is a simple data structure, and behavior is actually a process, which is a combination of some more general hooks and methods.

Using the User array type used in this example to encapsulate the data structure, look at the code

const arrayMethods = {
    push(list, item) {
    return list.concat(item)
  },
  pop(list) {
    return list.slice(0, -1);
  },
  slice(list, start, end) {
    returnlist.slice(start, end); }... }export const useArray = (initialValue = []) = > {
    assert(Array.isArray(initialValue), 'initialValue must be an array');
  return useMethods(initialValue, arrayMethods);
}
Copy the code

Looking at behavior encapsulation, take the request data from the above example

const useTaskPending = task= > {
  const [pending, setPending] = useState(false);
  const taskWithPending = useCallback(
    async(... args) => { setPending(true);
      const result = awaittask(... args); setPending(false);
      return result;
    }, 
    [task, setPending]
  );
  
  return [taskWithPending, pending];
}
Copy the code

In addition to process encapsulation, there is state synchronization management for results

const useTaskPendingState = (task, state) = > {
    const [taskWithPending, pending] = useTaskPending(task);
  const callAndStore = useCallback(
    async() = > {const result = await taskWithPending();
      state(result);
    },
    [taskWithPending, state]
  )
  return [callAndStore, pending];
}
Copy the code

After wrapping the hook process in the above example with data structures and procedures, the code is modified to the following result:

const useUserList = () = > {
    const [users, {push, pop, slice}] = useArray([]);
  const [load, pending] = useTaskPendingState(getListUser, state);
  
  return [users, {pending, load, addUser: push}]
}
Copy the code

Take a look at the above expression, which is shockingly simple. And useArray and useTaskPendingState can be used repeatedly. The more common the underlying Hook is, the more reusable it is.

Taste here, you can deeply feel the “fragrance” of hook, as long as you understand what problem hook solves? The essence is reuse what? How extreme can we go with this reuse?

In fact, it is impossible to write the business code in accordance with the above analysis, but we can feel the principle of using Hook through the analysis, and how to think about the use and encapsulation of Hook in the work of business logic. At this point, it’s pretty good to be able to write the first example.

Implementation principle of Hook

There are two principles for using hooks

  • Do not use in looping, conditional, and nested functions
  • Only used in react functional components

The rationale section takes useState as an example: State management is all about arrays

How does useState work?

function renderFunction() {
    const [firstName, setFirstName] = useState('qingming');
  const [lastName, setLastName] = useState('dong');
  
  return (
    <Button onClick={()= > setFirstName('damu')}>SET</Button>)}Copy the code

How does React work? Note the yellow text above to simulate the implementation steps:

  • Create two empty arrays, one state, one setter, and one index variable cursor
  • First render
    • cursor = 0 state = [‘first’] setters = [setFirstName]
    • cursor = 1 state = [‘first’, ‘last’] setters = [setFirstName, setLastName]
  • The second rendering (the NTH rendering) is reset to 0 each time the cursor is re-rendered
    • cursor = 0 state = [‘first’] setters = [setFirstName]
    • cursor = 1 state = [‘first’, ‘last’] setters = [setFirstName, setLastName]
  • The event processing
    • setFirstName(‘damu’)
    • Find the corresponding method in the setter, record the current cursor
    • Find the state value of cursor in state
    • Replace the value of the corresponding position in state

The underlying implementation of useState might be as follows:

let state = []
let setters = []
let cursor = 0
let firstRun = true
// Implement cursor and setter association, call method directly find the state of the cursor object to modify
function createSetter(cursor) {
    return function setterWithCursor(newVal) {
    state[cursor] = newVal
  }
}
export function useState(initValue){
  // The first render updates the array. Otherwise, no manipulation of the array is required
    if (firstRun) {
    state.push(initValue)
    setters.push(createSetter(cursor))
    firstRun = false
  }
  
  const value = state(cursor)
  const setter = setters(cursor)
  
  cursor++
  return [value, setter];
}
Copy the code

Finally, explain why hooks cannot be used in loops, conditional statements, and function nesting. Look at conditional statements

let firstRender = true;
function RenderFunctionComponent() {
  let initName;
  
  if(firstRender){
    [initName] = useState("qingming");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("dong");
  return (
    <Button onClick={()= > setFirstName("damu")}>Fred</Button>
  );
}
Copy the code

To translate the above code logic using the useState underlying implementation, it looks like this:

  • First execution
    • cursor = 0 state = [‘first’] setters = [setter_0]
    • cursor = 1 state = [‘first’, ‘first’] setters = [setter_0, setFirstName]
    • cursor = 2 state = [‘first’, ‘first’, ‘last’] setters = [setter_0, setFirstName, setLastName]
  • The second time, instead of executing the conditional statement, the initialization is performed twice
    • cursor = 0 state = [‘first’, ‘first’, ‘last’] setters = [setter_0, setFirstName, setLastName]
    • cursor = 1 state = [‘first’, ‘first’, ‘last’] setters = [setter_0, setFirstName, setLastName]

At this point our firstName = lastName = ‘first’ and our code really means firstName = ‘first’! == lastName = ‘last’.

So, given the rigor of array management, why not manage these subscripts flexibly? Are there likely to be performance issues? Will it change in the future? I believe that combing here, for hook you should understand, more hook API and implementation of the bottom layer to practice and learn it.