1 the introduction

React Hooks are new features in React 16.7.0-alpha. Install them if you want to try them out.

The React Hooks fix is state sharing, which is the third state sharing solution after render-props and higher-order components, and does not cause JSX nesting hell.

State sharing may be misdescribed as more appropriately called state logic reuse because only the data processing logic is shared, not the data itself.

Epitath (Epitath) : renderProps (renderProps) : renderProps (renderProps) : renderProps (renderProps)

To get a quick idea of what React Hooks are, look at the following renderProps code I quoted:

function App() {
  return (
    <Toggle initial={false}>
      {({ on, toggle }) => (
        <Button type="primary" onClick={toggle}> Open Modal </Button>
        <Modal visible={on} onOk={toggle} onCancel={toggle} />
      )}
    </Toggle>
  )
}
Copy the code

As it happens, React Hooks address this problem:

function App() { const [open, setOpen] = useState(false); return ( <> <Button type="primary" onClick={() => setOpen(true)}> Open Modal </Button> <Modal visible={open} onOk={() =>  setOpen(false)} onCancel={() => setOpen(false)} /> </> ); }Copy the code

As you can see, React Hooks act like a built-in leveling renderProps library, and we can create a value at any time, along with methods that modify that value. Looking like a setState of function form, this is actually equivalent to dependency injection. Compared to using setState, the component is stateless.

2 an overview

React Hooks benefit not only from being “more FP, more granular updates, and cleaner code,” but also from three features:

  1. Multiple states are not nested, and the writing is tiled (renderProps can be used for compose, but it’s a bit cumbersome to use, and it increases the number of entities by forcing a new object to be encapsulated).
  2. Hooks can refer to other Hooks.
  3. It is easier to separate the UI of a component from its state.

I want to expand on that: Hooks can refer to other Hooks. We can do this:

import { useState, useEffect } from "react"; Function useFriendStatusBoolean(friendID) {const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } // upper Hooks return a string based on online status: Loading... or Online or Offline function useFriendStatusString(props) { const isOnline = useFriendStatusBoolean(props.friend.id); if (isOnline === null) { return "Loading..." ; } return isOnline ? "Online" : "Offline"; Function FriendListItem(props) {const isOnline = useFriendStatusBoolean(props.friend.id); return ( <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li> ); Function FriendListStatus(props) {const statu = useFriendStatusString(props.friend.id); return <li>{statu}</li>; }Copy the code

In this example, there are two Hooks: UseFriendStatusBoolean and useFriendStatusString, useFriendStatusString is a new Hook generated by useFriendStatusBoolean, These two Hooks can be used for different UIs: FriendListItem, FriendListStatus, and because the two Hooks data are linked, the states of the two UIs are also linked.

By the way, this example can also be used to understand the statement in the React Hooks article: “Stateful components do not render, rendered components do not state” :

  • useFriendStatusBooleanuseFriendStatusStringIs a stateful component (usinguseState), no render (returns a non-UI value), so it can be used asCustom HooksIs called by any UI component.
  • FriendListItemFriendListStatusIs a rendered component (which returns JSX) and has no state (which is not useduseState), this is a purely functional UI component,

Create a Redux using useState

The essence of Redux is Reducer, and you can easily create a Redux mechanism using React Hooks:

Redux function useReducer(Reducer, initialState) {const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; }Copy the code

The value part of this custom Hook is treated as the state of redux, and the setValue part is treated as the Dispatch of Redux, which together makes a redux. The connect part of React-Redux does the same thing as the Hook call:

// An Action function useTodos() {const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: "add", text }); } return [todos, { handleAddClick }]; } function TodosUI() {const [Todos, actions] = useTodos(); return ( <> {todos.map((todo, index) => ( <div>{todo.text}</div> ))} <button onClick={actions.handleAddClick}>Add Todo</button> </> ); }Copy the code

UseReducer already works as a built-in Hooks. You can view all the built-in Hooks here.

It is important to note that useReducer or its own Custom Hooks do not persist data each time, so let’s create two apps, App1 and App2:

function App1() {
  const [todos, actions] = useTodos();

  return <span>todo count: {todos.length}</span>;
}

function App2() {
  const [todos, actions] = useTodos();

  return <span>todo count: {todos.length}</span>;
}

function All() {
  return (
    <>
      <App1 />
      <App2 />
    </>
  );
}
Copy the code

Instead of sharing a single Todos list when both instances are rendered together, there are two separate Todos lists. That is, React Hooks only provide state handling methods, they do not persist state.

If you really want to implement a Redux function, that is, maintain a state globally, any component useReducer will access the same data, and can be used with useContext.

The general idea is to use useContext to share a piece of data as a data source for Custom Hooks. Specific implementation can refer to redux-React-hook.

UseEffect instead of some life cycles

Near the useState location, you can use useEffect to handle side effects:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Clean up the subscription
    subscription.unsubscribe();
  };
});
Copy the code

The useEffect code is executed both at initialization and every subsequent Rerender, and the return value is executed at destructor time. This is more of a convenience, compared to React version G2 call process:

class Component extends React.PureComponent<Props, State> { private chart: G2.Chart = null; private rootDomRef: React.ReactInstance = null; componentDidMount() { this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement; this.chart = new G2.Chart({ container: document.getElementById("chart"), forceFit: true, height: 300 }); this.freshChart(this.props); } componentWillReceiveProps(nextProps: Props) { this.freshChart(nextProps); } componentWillUnmount() { this.chart.destroy(); } freshChart(props: Props) { // do something this.chart.render(); } render() { return <div ref={ref => (this.rootDomRef = ref)} />; }}Copy the code

You can do this with React Hooks:

function App() {
  const ref = React.useRef(null);
  let chart: G2.Chart = null;

  React.useEffect(() => {
    if (!chart) {
      chart = new G2.Chart({
        container: ReactDOM.findDOMNode(ref.current) as HTMLDivElement,
        width: 500,
        height: 500
      });
    }

    // do something
    chart.render();

    return () => chart.destroy();
  });

  return <div ref={ref} />;
}
Copy the code

You can see the combination of small pieces of code into a complete code block, which is more maintainable.

Now we have introduced the useState, useContext, useEffect, useRef, and other common hooks.

3 intensive reading

Hooks

Hook functions must start with the name “use” because this makes it easy for ESLint to check that the condition is not used to wrap the useHook statement.

The condition cannot be used to wrap the useHook statement. For details, see the official documentation. Here is a brief introduction.

React Hooks are not implemented via proxies or getters (see React Hooks: If the useState is wrapped in the condition, then the subscripts performed each time may not match. The setter exported by useState updated the wrong data.

This is the first time that the “convention first” concept has been introduced into the React framework, despite the eslint-plugin-React-hooks plugin, resulting in unprecedented code naming and ordering restrictions (function naming is officially restricted, to the fury of JS libertarians), But the convenience is also unprecedented (there are no better state-sharing schemes than React Hooks, convention gives improvement, and the price of freedom is back to renderProps or HOC, where teams can evaluate for themselves).

In my opinion, React Hooks were born from this inspiration: “Let’s fix the state sharing problem completely by adding conventions.”

Nextjs UMI and my PRI both greatly reduce the complexity of routing configurations by having “convention routing” features, so React Hooks act like code-level conventions, which greatly reduce code complexity.

The boundaries between state and UI will become clearer

Because of the React Hooks feature, if a Hook does not generate UI, it can always be enclosed by another Hook. Although side effects are allowed, they are wrapped in useEffect, which is generally functional. It’s also easy to get into the habit of writing stateless UI components. It’s easier to practice the idea of “separating states from UI”.

Where this idea is a little lame, though, is what “state” actually is.

function App() {
  const [count, setCount] = useCount();
  return <span>{count}</span>;
}
Copy the code

We know that useCount is stateless, because React Hooks are essentially renderProps or HOC, so it makes sense to use renderProps:

<Count>{(count, setCount) => <App count={count} setCount={setCount} />}</Count>;

function App(props) {
  return <span>{props.count}</span>;
}
Copy the code

As you can see, the App component is stateless, and the output is completely determined by the input (Props).

The stateless UI component is useCount:

function useCount() {
  const [count, setCount] = useState(0);
  return [count, setCount];
}
Copy the code

UseState (0) is an official Hook, but it’s similar to useCount() in a stateless UI component App. Since React calls useCount a custom Hook, useState is an official Hook. UseState is also a layer of renderProps. The final state of useState is the component built-in in React.

Let’s look at the nested expression renderProps:

<UseState> {(count, setCount) => (<UseCount> {" "} {/** * */} {(count, setCount) => <App count={count} setCount={setCount} />} </UseCount>)} </UseState>Copy the code

For sure, the App must have a UI, and the top two parent components must not have a UI. As a best practice, we try to avoid maintaining state for the App itself, whereas its parent, the RenderProps component, can maintain state (or act as a setter without maintaining state). So consider adding to the statement “stateful components are not rendered and rendered components are not stateful” : components that are not rendered can also be stateful.

4 summarizes

Use React Hooks as a convenient RenderProps. Although they appear to internally maintain a state, they are equivalent to injection, Connect, HOC, or RenderProps. The barrier to use renderProps is much lower because it’s so easy to use Hooks, and we can abstract a lot of Custom Hooks to make our code more FP without adding more nesting levels.

5 More discussion

The address is: React Hooks intensive Reading · Issue #111 · DT-FE/Weekly

If you’d like to participate in the discussion, pleaseClick here to, each week has a new theme, released on the weekend or Monday. Front end intensive reading – to help you filter the right content.