All the code for this section is in the 👉 repository

Portal 🤖

  • React Brochure – Get Started JSX ✅ ✅

  • React Brochure – Set sail ✅ ✅

  • React Small volume – Hooks ✅ ✅

  • React Brochure – CSS solution

  • React Brochure – Life Cycle ✅ ✅

  • React Volume – State Management Redux

  • React Volume – State Management Redux middleware

  • React Volume – State Management Mobx

  • React brochure – Router

  • React Volume – Performance optimization

  • React Booklet – SSR

  • React Brochure – State Management Redux ✅ ✅

background

Before React 16.8, functional components were also called stateless components

A function cannot maintain its own internal state because it re-executes its internal code each time it executes

function add(n) {
  const result = 0;
  return result + 1;
}

add(1); / / -- > 1
add(1); / / -- > 1
Copy the code

We can’t save the state of Result in the function because result is reinitialized every time we execute the function

Let’s take a look at the differences between class and functional components prior to 16.8

  • Class components need to inherit from class; function components do not

  • Class components have access to lifecycle methods; function components do not

  • Class components can get instantiated this and do all sorts of things based on this, whereas function components can’t

  • State can be defined and maintained in class components, but not in function components

  • .

The state of a class component is generally maintained within the component, which makes the component less reusable

I mentioned HOC and Render Props in previous chapters

Fundamentally, they are all as elegant as possible to implement code reuse

But behind these terms is undoubtedly the heavy cost of learning

Functional components cannot store state

That’s when hooks came into being and the problem they want to solve is

Let functional components have the same functionality as class components

Let’s look at a simple demo

import React, { Component } from 'react';

class ProfilePageClassComponent extends Component {
  showMessage = () = > {
    alert('Followed ' + this.props.user);
  };

  handleClick = () = > {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>; }}function ProfilePageFunComponent(props) {
  const showMessage = () = > {
    alert('Followed ' + props.user);
  };

  const handleClick = () = > {
    setTimeout(showMessage, 3000);
  };

  return <button onClick={handleClick}>Follow</button>;
}

export default class App extends React.Component {
  state = {
    user: 'nanshu'};render() {
    return (
      <>
        <label>
          <b>Choose profile to view: </b>
          <select
            value={this.state.user}
            onChange={(e)= > this.setState({ user: e.target.value })}
          >
            <option value="nanshu">nanshu</option>
            <option value="chou">chou</option>
          </select>
        </label>
        <h1>Welcome to {this. State. User} 's profile!</h1>
        <p>
          <ProfilePageFunComponent user={this.state.user} />
          <b> (function)</b>
        </p>
        <p>
          <ProfilePageClassComponent user={this.state.user} />
          <b> (class)</b>
        </p>
        <p>Can you spot the difference in the behavior?</p>
      </>); }}Copy the code

The code is a little long but what it does is very simple

Either a class component or a functional component simply prints props given to it by the parent component

But we broke this timing connection between this.props and the render action by delaying the expected render by 3s with setTimeout

This results in the class component capturing an incorrect, modified this.props at render time

Because while props is immutable, this is mutable, and the data on this can be modified

This. Props gets the latest props each time to ensure that the data is in real time

A functional component ensures that at any time, the props read is the one that was originally captured

Therefore, the functional component is a more consistent with its design concept, and is more conducive to logical separation and reuse of the component expression

👀 : React has hooks packaged for us


useState

  • UseState fills the void where state cannot be saved inside functional components

  • Updates to the state stored in useState are asynchronous updates. Multiple updates to the same property are merged and only the latest one is fetched

  • You can get the latest state when you update the state using the callback function

Let’s compare setState in a class component

If we execute this.setState once, the component will probably follow the following process

this.setState –> shouldComponentUpdate –> componentWillUpdate –> render –> componentDidUpdate

Imagine if we responded like this every time we executed this.setState React

There are probably not many times when your page will be stuck, so React did a batch update

But in hooks all updates are asynchronous

However, in a class component if you wrap setState with asynchronous methods like setTimeout/Promise it will take those updates out of React control and make it look synchronous

Note, however, that ⚠ī¸ always updates state asynchronously, whether in a functional component or a class component

This means that you can’t get the latest status immediately. Use callbacks if necessary

For batch updates, refer to 📝 Discussions on Github

It was mentioned that batch updates will start by default in the upcoming Update 18

React can now control all of the situations that are included in class components if we use setTimeout/Promise so that class and functional components behave the same way

import React, { useState } from 'react';
import { Button } from 'antd';

const Counter: React.FC = () = > {
  const [count, setCount] = useState<number> (0);
  const [otherState, setOtherState] = useState<number> (0);

  // Plain call
  const addCount = () = > {
    setOtherState(otherState + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
  // The callback function
  const addCountByCallback = () = > {
    setOtherState((preState) = > preState + 1);
    setCount((preState) = > preState + 1);
    setCount((preState) = > preState + 1);
    setCount((preState) = > preState + 1);
  };

  return (
    <>
      <p>{`count:${count}`}</p>
      <p>{`other:${otherState}`}</p>{/* will merge into a single +1 */}<Button onClick={addCount}>I'm going to type three</Button>{/* the latest value is fetched every time +3 */<Button onClick={addCountByCallback}>I'm going to hit three, too</Button>
    </>
  );
};
export default Counter;
Copy the code

The above code indicates that different states do not affect each other but that multiple operations on the same state will result in batch updates

useReducer

Let’s take a look at useReducer

Both are means of managing state in functional components

The former can be regarded as a realization of the latter

  • Components that are not related to Redux do not share data

  • Is an alternative to useState

  • Using useReducer for complex businesses is much more readable than using useState

import React, { useReducer } from 'react';
import { Button } from 'antd';

enum Actions {
  ADD_COUNT_VALUE = 'ADD_COUNT_VALUE',
  SUB_COUNT_VALUE = 'SUB_COUNT_VALUE',}interface IState {
  count: number;
}

interface IAction {
  type: Actions; payload? :number;
}

const myReducers = (state: IState, action: IAction) = > {
  const { payload = 1 } = action;
  switch (action.type) {
    case Actions.ADD_COUNT_VALUE:
      return {
        ...state,
        count: state.count + payload,
      };
    case Actions.SUB_COUNT_VALUE:
      return {
        ...state,
        count: state.count - payload,
      };
    default:
      returnstate; }};const Count = () = > {
  const [state, dispatch] = useReducer(myReducers, { count: 0 });

  return (
    <div>
      <p>Current count: {state.count}</p>
      <Button
        onClick={()= > dispatch({ type: Actions.ADD_COUNT_VALUE, payload: 2 })}
      >
        +2
      </Button>
      <Button onClick={()= > dispatch({ type: Actions.SUB_COUNT_VALUE })}>
        -1
      </Button>
    </div>
  );
};

export default function App() {
  return (
    <>
      <Count />
      <Count />
    </>
  );
}
Copy the code

After executing the code, you’ll notice that the states of different components do not affect each other

The two Count components each maintain a copy of their own Count

useEffect

  • Allowing function components to perform side effects partially compensates for the absence of life cycles

  • UseEffect (callBack, []);

  • The first argument function form can be implemented equal to componentDidMount shouldComponentUpdate componentWillUnmount

  • And can return a function used to eliminate side effects like componentWillUnmount can do some events such as untying timer off

  • The second parameter array state dependency implements performance optimization if passed [] which is equivalent to shouldComponentUpdate not enabled

import React, { FC, useEffect, useState } from 'react';

import { Button, message } from 'antd';

const App: FC = () = > {
  const [state, setState] = useState<number> (0);

  useEffect(() = > {
    message.info('render');
    const timer = setTimeout(() = > {});

    return () = > {
      clearTimeout(timer);
      message.info('Eliminate side effects');
    };
  }, [state]);

  return (
    <div>
      <Button onClick={()= > setState(state + 1)}>{state}</Button>
      <br />
      <br />
    </div>
  );
};

export default App;
Copy the code

useContext

  • Data can be shared between child components

  • Const AppContext = react.createconText ({})

  • The parent component uses the Provider in the Context object and assigns values

  • Pass the context object react.usecontext (AppContext) in the child component

import React from 'react';

interface ITheme {
  theme: string;
}

const AppContext = React.createContext<ITheme>({
  theme: 'red'});const NavBar = () = > {
  const { theme } = React.useContext(AppContext);
  return (
    <div>
      <h2 style={{ color: theme}} >NavBar</h2>
    </div>
  );
};

const Message = () = > {
  const { theme } = React.useContext(AppContext);
  return (
    <div>
      <h2 style={{ color: theme}} >Message</h2>
    </div>
  );
};

export default function App() {
  return (
    <AppContext.Provider value={{ theme: 'red' }}>
      <NavBar />
      <Message />
    </AppContext.Provider>
  );
}
Copy the code

useRef

  • Get element node

  • Save data (always point to the original value)

import React, { FC, useRef, useState } from 'react';
import { Button, Input } from 'antd';

const App: FC = () = > {
  const [count, setCount] = useState<number> (0);
  const numRef = useRef<number>(count);
  const domRef = useRef<HTMLInputElement | null> (null);

  return (
    <div>
      <input defaultValue="useRef" ref={domRef} />
      <p>count : {count}</p>{/* numRef always refers to the original count and does not change as count changes */}<p>numRef : {numRef.current}</p>
      <Button onClick={()= > setCount(count + 1)}>count+1</Button>
    </div>
  );
};

export default App;
Copy the code

useCallback && useMemo

Using the Memo wrapped around our component in a functional component helps us do a shallow comparison of the props before and after

const App = memo((props) = > {
  return 'UI';
});
Copy the code

But if props is a reference then the memo is invalid

We can use useCallBack and useMemo below to make our reference types memorable

The relationship between the two is as follows: useMemo has a greater capability boundary than useCallBack, but the two can be interchangeable under certain circumstances

UseCallback (fn, deps) is equivalent to useMemo(() => fn, deps) — React 厘įŊ‘

useCallback(() => {}, [])

UseCallback accepts two such parameters

The first argument is a function

The second argument is an array which is a dependency of this function and only the dependency update function is re-executed

import React, { useState, useCallback, memo, useEffect } from 'react';
import { Button, message } from 'antd';

interface IProps {
  getSum: () = > number;
}

const ComponentWithoutUseCallback = memo(({ getSum }: IProps) = > {
  useEffect(() = > {
    message.info('ComponentWithoutUseCallback');
  }, [getSum]);

  return null;
});

const ComponentWithUseCallback = memo(({ getSum }: IProps) = > {
  useEffect(() = > {
    message.info('ComponentWithUseCallback');
  }, [getSum]);

  return null;
});

const Parent = () = > {
  const [num, setNum] = useState(0);
  const [max, setMax] = useState(100);

  // The child is updated whenever the parent is updated
  const methodsWithoutUseCallback = () = > {
    message.info('methodsWithoutUseCallback');
    let sum = 0;
    for (let i = 0; i < max; i++) {
      sum += i;
    }
    return sum;
  };

  // The function is re-executed only when Max is updated and the sub-components are updated
  const methodsWithUseCallback = useCallback(() = > {
    message.info('methodsWithUseCallback');
    let sum = 0;
    for (let i = 0; i < max; i++) {
      sum += i;
    }
    return sum;
  }, [max]);

  return (
    <div>
      <p>num : {num}</p>
      <Button onClick={()= > setNum(num + 1)}>change num</Button>
      <Button onClick={()= > setMax((pre) => pre * 2)}>change max</Button>
      <ComponentWithoutUseCallback getSum={methodsWithoutUseCallback} />
      <ComponentWithUseCallback getSum={methodsWithUseCallback} />
    </div>
  );
};

export default Parent;
Copy the code

Customize the hook

Custom hook function names must start with use. For example, here is a useLogger hook that prints component creation/destruction

import React, { useEffect, useState } from 'react';

const useLogger = (componentName: string) = > {
  useEffect(() = > {
    console.log(`${componentName}The component was created);

    return () = > {
      console.log(`${componentName}The component was destroyed);
    };
  });
};

const Header = () = > {
  useLogger('Header');

  return <h2>Header</h2>;
};

const Footer = () = > {
  useLogger('Footer');

  return <h2>Footer</h2>;
};

export default function App() {
  const [show, setShow] = useState(true);
  return (
    <div>
      <button onClick={()= >setShow(! show)}>{show ? 'hidden' : 'show'}</button>
      {show && <Header />}
      {show && <Footer />}
    </div>
  );
}
Copy the code

X. Use notes

React Hooks are executed in sequence

Placing a hook in a conditional/judgment statement can break its execution order and produce unexpected results

So use your hooks at the top

But most projects inherit from ESLint and if you insist on doing so, chances are you’ll get ⚠ī¸

conclusion

  • Say goodbye to the incomprehensible Class

    • this

    • The life cycle

  • Solve problems where business logic is difficult to split

    • Logic was once coupled to the lifecycle
componentDidMount() {
// 1. Make an asynchronous call here
// 2. Here we get some data from props and update the DOM based on this data
// 3. Set a subscription here
// 4. What else can I do here
// ...
}
componentWillUnMount() {
  // Uninstall the subscription here
}
componentDidUpdate() {
  // 1. Update the DOM here based on the asynchronous data obtained by DidMount
  // 2. Here we get some data from props and update the DOM based on this data (same as DidMount step 2)
}
Copy the code
  • Make state logic reuse easy and feasible

    • HOC (High-level component)

    • Render Props


Finally, neither functional nor class components are superior or inferior

Both are paradigm shifts

Class components are object-oriented patterns

Functional components are the paradigm of functional programming

Remember that famous formula

React = render(UI)

React is just a function that eats data and spit it out of the UI

Therefore, function components are more in line with React concept in terms of design philosophy

But for now, class components have stronger capability boundaries than function components

For example, componentDidCatch, which handles error boundaries mentioned in the previous lecture, is strongly dependent on class components