React Hook take-off Guide

Author: An elegant collection of Yuan Xiao's benches

16.8 currently releases 10 built-in hook s, but you can do a lot of things just based on the two APIs below.So this article won't talk about many APIs or the basic usage of APIs, just make these two things clear, and read the full text for about 5-10 minutes.

  • State Management: useState
  • Side effects management: useEffect

These two APIs are sickles and hammers in the hook world. The seemingly simple two APIs actually represent a new programming model that is distinct from the previous one.

Foreword: There is already a class component, why is there another hook?

Dan mentioned on his blog:

We know that components and top-down data streams can help us organize large UI s into small, independent, reusable parts.However, we often cannot further destroy complex components because logic is stateful and cannot be extracted into functions or other components.Hooks allow us to organize logic within components into reusable isolation units.

So one sentence summarizing hook s brings about a change that further refines the minimum reusable units from the component level to the logical level.

Based on this advantage, we can see later that all components in hook-based applications will not become bloated as the business grows, because in the hook world, state logic and UI are decoupled.UI only needs to consume the final calculated state data, hook only focuses on the calculation and change of state.

1. Functions with State

The useState component is stateful:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Functions are stateless:

const Example = props => {
  const { count, onClick } = props;
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={onClick}>
        Click me
      </button>
    </div>
  );
}

hooks are stateful functions:

import { useState } from 'react';
const Example = () => {
    const [count, setCount] = useState(0);
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
      </div>
    );
}

Think: The setter produced by useState does not merge when updating the state, which is different from the setState method of the traditional class component. Why is this designed?

// Don't do such like this ↓
const [data, setData] = useState({ count: 0, name: 'zby' });
useEffect(() => {
  // data: { count: 0, name: 'zby' } -> { count: 0 }
  setData({ count: 1 });
}, []);

Our applications have evolved from small to large. Initial adequate component partitioning and state design is an important part of ensuring the subsequent maintainability of applications, because as applications grow, components inevitably become bloated.So sometimes we introduce state management like redux in place from the start.

But now, in our Pure Function component, each useState produces a pair of States and stateSetter s. We don't need to consider more state tree designs and component partitioning designs. Logical code is written directly from the root component, and progressive development is possible.

The so-called "progressive" development: Overview of the application development path can be roughly divided into the following three stages:

1. Pre-farm:
The vast majority of cases can be handled simply by combining the relevant States into several independent state variables.

2.Mid-term gank: As the state of the component gradually increases, it is easy to leave updates to reducer for management (detailed in Chapter 2 below);

3. Later regiment warfare: When not only the states are more and more complex, but also the logic of the States is more and more complex, we can separate the complicated state logic codes into custom hook s to solve the problems at almost zero cost (details are expanded in Chapter 3 below);

Based on hook s, our development process has become more resilient than ever.So this incremental development is feasible and efficient.

2. highly flexible redux, pure and independent

As mentioned above, it is natural for us to leave state updates to reducer when the state of a component is getting more and more.

Unlike true redux, hook offers a more flexible and pure pattern in practice.Now we can implement a global Redux with 10 lines of code, or a local Redux with 2 lines of code, anytime, anywhere.

A:10 lines of code implement a global redux:

import React from 'react';
const store = React.createContext(null);
export const initialState = { name: 'Yuan Xiao' };
export function reducer(state, action) {
  switch (action.type) {
    case 'changeName': return { ...state, name: action.payload };
    default: throw new Error('Unexpected action');
  }
}
export default store;

Provider Root Component Hang Up

import React, { useReducer } from 'react';
import store, { reducer, initialState } from './store';
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
     <store.Provider value={{ state, dispatch }}>
      <div/>
     </store>
  )
}

Subcomponent Call

import React, { useContext } from 'react';
import store from './store';
function Child() {
  const { state, dispatch } = useContext(store);
  ...
}

B: Implement a local redux anytime, anywhere

import React, { useReducer } from 'react';
const initialState = { name: 'Yuan Xiao' };
function reducer(state, action) {
  switch (action.type) {
    case 'changeName': return { ...state, name: action.payload };
    default: throw new Error('Unexpected action');
  }
}
function Component() {
  const [state, dispatch] = useReducer(reducer, initialState);
  ...
}
  • The nature of useState is a grammatical sugar for useReducer, so you can read about the type definition and implementation of hooks.

3. Custom hook s

As mentioned above, when a component reaches a certain level, not only are there more states, but the logic of States becomes more and more complex, we can solve problems by pulling complicated state logic codes out of custom hook s at almost zero cost.

When we want to share logic between two functions, we extract it into the third function.Components and hooks are functions, so the same applies.The difference is that a hook is a stateful function that enables a higher level of reuse than previously possible with pure functions - the reuse of state logic.

Let's start with the following two demo examples

A:class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: undefined
    };
  }
  componentDidMount() {
    service.getInitialCount().then(data => {
      this.setState({ count: data });
    });
    service.getInitialName().then(data => {
      this.setState({ name: data });
    });
  }
  componentWillUnmount() {
    service.finishCounting().then(() => {
      alert('Count Complete');
    });
  }
  addCount = () => {
    this.setState({ count: this.state.count + 1 });
  };
  handleNameChange = name => {
    this.setState({ name });
  };
  render() {
    const { count, name } = this.state;
    return (
      <div>
        <div>
          <p>You clicked {count} times</p>
          <button onClick={this.addCount}>Click me</button>
        </div>
        <Input value={name} onChange={this.handleNameChange} />
      </div>
    );
  }
}
B:function useCount(initialValue) {
  const [count, setCount] = useState(initialValue);
  useEffect(() => {
    service.getInitialCount().then(data => {
      setCount(data);
    });
    return () => {
      service.finishCounting().then(() => {
        alert('Count Complete');
      });
    };
  }, []);
  function addCount() {
    setCount(c => c + 1);
  }
  return { count, addCount };
}
function useName(initialValue) {
  const [name, setName] = useState(initialValue);
  useEffect(() => {
    service.getInitialName().then(data => {
      setName(data);
    });
  }, []);
  function handleNameChange(value) {
    setName(value);
  }
  return { name, handleNameChange };
}
const App = () => {
  const { count, addCount } = useCount(0);
  const { name, setName } = useName();
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={addCount}>Click me</button>
      <Input value={name} onChange={setName} />
    </div>
  );
};

A has two states: count and name, and a ticket logic associated with them, scattered throughout the life cycle and methods of the component.Although we can extract the States and change action s of a component into a common one, the side effects involved ultimately do not get around the component's life cycle.However, there is only one life cycle for a component, and it is inevitable that some entirely irrelevant logic will be written together.Thus, complete state logic reuse cannot be achieved.

In B, we encapsulate count-related logic and name-related logic in separate and enclosed logical units through custom hooks.The lifecycle of the previous class component no longer exists here.Lifecycle is a concept that is strongly coupled with UI and, while easy to understand, is naturally far away from data.Hooks encapsulate components side-effects with a data stream subscription similar to rxjs mode, which has the advantage that we only need to care about the data.So hooks do more than simplify the definition and packaging of state s.

this animation It shows the change of organization of state logic before and after A->B.

Business record:

This is the SKU selection component used in the commodity details page

We use hook to reconstruct the original class component, which is a process of extracting state logic.

We pulled the two states of the component core and related logic into two separate custom hooks (see figure below).The benefits of this are obvious, our components only need to consume the value and function of these two hook outputs, and the maintenance and update details of the state have been encapsulated in the hook.

const { specPath, handleSpecPathChange } = useSkuSpecPath({
    defaultSpecPath,
    dataSource
  });
const { skus, handleSkuAmountChange } = useSkuAmount({
  defaultSelectedSkus,
  dataSource
});

Here is a design paradigm for custom hook s:

const { state, handleChange, others } = useCustomHook(config, dependency?);

Where config declares the data required for a hook, it may be the initial value of the internal useState, or it may be structured data that summarizes the configuration of the hook

Dependencies typically only use API s such as useEffect, useCallback within a hook and need to be passed in when we need to declare dependencies.

On the left is the refactored code (desensitized code, just get the hang of it). After the original core common logic was pulled into two hook s, useSkuSpecPath and useSkuAmount, this component became their caller.Subsequently, regardless of how UI components change and extend, these logic can be reused anywhere and anytime as long as they conform to their interface format conventions, and a new sku selection component can be quickly implemented.

Summary: Custom hook separates state logic from UI and enables very high-level Abstract reuse of business logic through rational abstraction of custom hook.

Recommend one website , which collects some interesting custom hook s

4. A quotation from Dan in the future:

A hook can cover all usage scenarios for class components, while providing greater flexibility in extracting, testing, and reusing code.That's why Hooks represents our vision for the future of React.

In applications above react16.8, you can use them immediately.

Eggs

hook principle: Not magic, just array

let hooks, i;
function useState() {
  i++;
  if (hooks[i]) {
    // When rendering again
    return hooks[i];
  }
  // First Rendering
  hooks.push(...);
}
// Preparing to render
i = -1;
hooks = fiber.hooks || [];
// Invoke Component
Component();
// Cache the status of Hooks
fiber.hooks = hooks;

Keywords: Javascript React Programming

Added by Stripy42 on Thu, 15 Aug 2019 06:25:27 +0300