Nolan’s React Hook + TS in Action: www.notion.so/Hook-fd2547…

1. What problem is Hook used to solve

In short, the Hook allows us to reuse React state logic code better. Note that this is not template code, which can be reused with components; Pure state logic code cannot be reused by components

Some students may say, ordinary functions can not achieve logic code reuse? The answer is: normal functions can reuse logic code, but they cannot reuse logic code with state.

What is the React state?

Here’s an example:


const Comp = () => {
  const [id, setId] = useState(0)
  const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])

  return <div>{assets.map(a => a.name)}</div>
}
Copy the code

The id, assets, is the state, which is defined by a specific API(useState), and the component reacts accordingly when it changes (such as rerender).

const sum = (a, b) => a + b
Copy the code

This normal function has no state, and no matter how sum changes, it will not render any component again

The React team is very focused on the reusability of React state code. Since React was created, they have been optimizing the solution for code reuse, going through Mixin, HOC, Render Props, and now Hook

Therefore, Hook is not a product that comes out of nowhere, and it is impossible to fully understand Hook without understanding this line of thought

I’m going to post a lot of code screenshots below, but to get you up to speed, you just need to browse through the code screenshots based on what I’m talking about, you don’t need to pay too much attention to the details

1. Mixin

Mixin was the original React code reuse scheme

var SubscriptionMixin = { getInitialState: function() { return { comments: DataSource.getComments() }; }, componentDidMount: function() { DataSource.addChangeListener(this.handleChange); }, componentWillUnmount: function() { DataSource.removeChangeListener(this.handleChange); }, handleChange: function() { this.setState({ comments: DataSource.getComments() }); }}; var CommentList = React.createClass({ mixins: [SubscriptionMixin], render: function() { // Reading comments from state managed by mixin. var comments = this.state.comments; return ( <div> {comments.map(function(comment) { return <Comment comment={comment} key={comment.id} /> })} </div> ) } });Copy the code

It has the advantage of being simple, intuitive, and actually reusing code; But the downside is obvious: implicit dependencies, name conflicts, no class Component support, difficult to maintain, and all in all, now completely obsolete

Give you a look at the official verdict: reactjs.org/blog/2016/0…

2. HOC (higher-order component)

In 2015, after the React team sentenced Mixin to death, they recommended using HOC mode, which uses the decorator mode in design mode

function withWindowWidth(BaseComponent) { class DerivedClass extends React.Component { state = { windowWidth: window.innerWidth, } onResize = () => { this.setState({ windowWidth: window.innerWidth, }) } componentDidMount() { window.addEventListener('resize', this.onResize) } componentWillUnmount() { window.removeEventListener('resize', this.onResize); } render() { return <BaseComponent {... this.props} {... this.state}/> } } return DerivedClass; } const MyComponent = (props) => { return <div>Window width is: {props.windowWidth}</div> };Copy the code

This is where the classic separation of container components and presentation components begins

// components/AddTodo.js import React from 'react' import { connect } from 'react-redux' import { addTodo } from '.. /redux/actions' class AddTodo extends React.Component { // ... handleAddTodo = () => { // dispatches actions to add todo this.props.addTodo(this.state.input) // sets state back to empty string this.setState({ input: '' }) } render() { return ( <div> <input onChange={(e) => this.updateInput(e.target.value)} value={this.state.input} /> <button className="add-todo" onClick={this.handleAddTodo}> Add Todo </button> </div> ) } } export default connect(null, { addTodo })(AddTodo)Copy the code

www.jianshu.com/p/ddbbbb16f…

This is where the classic separation of container components and presentation components begins

A classic use of HOC is the Connect method in React Redux. The AddTodo component is like an innocent white rabbit, and its AddTodo method is injected into it by connect

  1. Can work in any Component including Class Component
  2. Its principle of separating container components from presentation components does just that: separation of concerns

Disadvantages:

  1. It’s not intuitive and hard to read
  2. Name conflict
  3. Components are nested layer upon layer

3. Render Props

In 2017, render props caught on

class WindowWidth extends React.Component {
  propTypes = {
    children: PropTypes.func.isRequired
  }

  state = {
    windowWidth: window.innerWidth,
  }

  onResize = () => {
    this.setState({
      windowWidth: window.innerWidth,
    })
  }

  componentDidMount() {
    window.addEventListener('resize', this.onResize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onResize);
  }

  render() {
    return this.props.children(this.state.windowWidth);
  }
}

const MyComponent = () => {
  return (
    <WindowWidth>
      {width => <div>Window width is: {width}</div>}
    </WindowWidth>
  )
}
Copy the code

In 2017, render props became popular. The downside is that it’s hard to read and hard to understand. Here’s a use case

4. Hook

You see the two methods above, what are their ultimate goals? In order to inject the windowWidth state into the component, they use a complicated and unintuitive method for this purpose. Is there any way to make it intuitive? That’s our Hook.

I will use Hook to realize the same requirement as above

import { useState, useEffect } from "react"; const useWindowsWidth = () => { const [isScreenSmall, setIsScreenSmall] = useState(false); let checkScreenSize = () => { setIsScreenSmall(window.innerWidth < 600); }; useEffect(() => { checkScreenSize(); window.addEventListener("resize", checkScreenSize); return () => window.removeEventListener("resize", checkScreenSize); } []); return isScreenSmall; }; export default useWindowsWidth;Copy the code
import React from 'react'
import useWindowWidth from './useWindowWidth.js'

const MyComponent = () => {
  const onSmallScreen = useWindowWidth();

  return (
    // Return some elements
  )
}
Copy the code

Advantages of Hook compared with other schemes:

  1. Extracting the logic is easy
  2. Very easy to put together
  3. Very readable
  4. There are no name conflicts

React built-in Hook and custom Hook. Custom Hook is a combination of built-in Hook. So let’s talk about built-in Hook first

React has its own Hook

1. useState

UseState is the most basic Hook, because it is a state producer. What’s the difference between the state it produces and a normal variable?

const [count, setCount] = useState(initialCount);
---------
const count = 1
const setCount = (value) => count = value
Copy the code

What’s the difference between these two? The difference is that the first useState generates state, and the component rerenders when the state changes. It is reactive; The second one is an ordinary variable that changes and nothing happens, doesn’t that sound a little pathetic

2. useEffect

With the state generated by useState, we can write simple components such as

const Count = () => {
  const [count, setCount] = useState(0)
  const add = setCount(count + 1)
  return <button onClick={add}>add</button>
}
Copy the code

Such a simple counting component

But ultimately, it’s our own fun, because the code we write has to be in touch with the world outside of this component, and our status has to be in sync with the world outside of this component to generate industrial value. We call all the things that happen out there side effects

For example, if you want to synchronize count with the server code, if you want to synchronize count with the vibration of your phone, useEffect. To discard the previous concept of a lifecycle, useEffect’s only purpose is to synchronize side effects.

3. useContext

The componentization of React allows us to split up different business code, but it also creates a problem, which is that it is very inconvenient to share state between components. For example, if you have a state that many components use, the app theme state, how do you make that state available to one component at any time? You’ve probably heard of state promotion, it relieves but it doesn’t solve the problem. And context is a way to solve that problem, so you can think of it as Redux that comes with React, but Redux is actually implemented using context

4. useReducer

While useState is known as the primary state producer, this useReducer is another less commonly used state producer. It is suitable when the state logic is complex, or when the next state value depends on the previous state value, such as

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
Copy the code

Use useState for this, of course, but useReducer makes the code look cleaner

5. useCallback/useMemo

The React documentation is very unclear.

If the parent component is refreshed, all child components will be refreshed.

The parent component refreshes, and all of its children refresh. This may sound performance expensive, but for most components, performance is fine because React is really fast.

This can be a big problem for performance-hungry components, which don’t want to be refreshed very often, so we can wrap them in React. Memo so that they refresh only when their props change.

This raises another question, such as:

const TestComp = () => {
  const value = {name: 'Jack'}
  return <MemoExpensiveList value={value}/>
}
Copy the code

Memosivelist is expensivelist processed by react. memo, it is not refreshed until its props changes. Expensivelist is expensivelist. Expensivelist is expensivelist. {name: ‘Jack’} ≠= {name: ‘Jack’}

This is where useMemo comes in. We can wrap useMemo around:

const value = useMemo(() => {}, [])
Copy the code

This will only generate one instance and will not bother MemoExpensiveList

UseCallback is a special version of useMemo that deals with functions

6. useRef

The concept of state was explained in detail above, and sometimes we want to create a value of a type that is not a state but can exist as the same instance between different render versions. It is similar to this.xxx in class Component

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
Copy the code

3. Custom Hook

Custom hooks are the best way to reuse React logic. They are very similar to normal functions. Custom hooks are special in that they are stateful and return states. So when should we use custom hooks? That is, when we want to abstract away the logic for dealing with states

Let me give you an example

const Comp = () => {
  const [arr, setArr] = useState([1, 2])
  return <button onClick={() => setArr([...arr, value])}>add</button>
}
Copy the code

If you find that you have a couple of these array handlers in your app, you can

export const useArray = <T>(initialArray: T[]) => { const [value, setValue] = useState(initialArray); return { value, setValue, add: (item: T) => setValue([...value, item]), clear: () => setValue([]), removeIndex: (index: number) => { const copy = [...value]; copy.splice(index, 1); setValue(copy); }}; };Copy the code

With such a custom Hook, you return not only the state, but also the method to handle the state

This example also shows that custom hooks can be state centric and encapsulate it with its dependencies. This is also consistent with the principle of seperation of concert that we program, which is separation of concerns. Separation of concerns is one of the most important things you should be aware of when writing code. This means that unrelated code should not be placed together. Otherwise, concerns will be mixed and maintenance will be greatly difficult.

If you take a look at your code tomorrow, you will probably find that some spaghetti code can actually be abstracted by hook

const Comp = () => {
  const [id, setId] = useState(0)
  const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])

  return <div>{assets.map(a => a.name)}</div>
}
Copy the code

Does the contents of the fetch here have much to do with this component? Not really, because this component doesn’t really care about the details of fetch, it only cares about getting result.data, so we can use hook abstraction

// util.ts
const useAssets = (id) => {
    const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])
    return assets
}

// comp.tsx
const Comp = () => {
  const [id, setId] = useState(0)
  const assets = useAssets(id)

  return <div>{assets.map(a => a.name)}</div>
}
Copy the code

You see, this is the separation of logic