React Hook has been around for a long time. At present, most projects in the team have adopted React Hook scheme. After several rounds of project iteration, here are some tips on React Hook

React HookTypeScript

How to use TypeScript in React Hook The majority of React Hook hooks use stereotypes.

Declare variable types explicitly

// User is identified as any
const [user, setUser] = useState(null);
// idx will be recognized as number, but it is recommended to write number explicitly
const [idx, setIdx] = useState(0);
const [list, setList] = useState<PaperItem[]>(initList);
const [paperIdx, setPaperIdx] = useState<number> (0);
const [screenLoading, setScreenLoading] = useState<boolean> (false);
const [pageState, setPageState] = useState<PaperPageState>(
  PaperPageState.preview
);
const listRef = useRef<PaperItem[]>(list);
const paperIdRef = useRef<string> (' ');
const entryRef = useRef<string> ('default');
// If you do not write this, the program does not know that you want to write the style...
const iconStyle = useMemo<React.CSSProperties>(() = > {
  if(! visible) {return { display: 'none' };
  }
}, [visible]);
Copy the code

HookComponent declarations

Class declaration components can be react.component.ponent

to implement the declaration of component properties and states for example:

={},>

import React from 'react';

interface TextComponentProps {
  initValue: string;
}

interface TextComponentState {
  length: number;
  value: string;
}

class TextComponent extends React.Component<
  TextComponentProps.TextComponentState
> {
  constructor(props: TextComponentProps) {
    const value = props.initValue;
    const length = value.length;
    this.state = { value, length };
  }

  render(){}}Copy the code

React Hook uses function components. You can rely on react. FunctionComponent

or alias react. FC

.

interface CarouselComponentProps {
  onIdxChange: (idx: number) = > void;
  currPage: number;
  listLength: number;
}

const CarouselComponent: React.FC<CarouselComponentProps> = ({ onIdxChange, currPage, listLength, }) = > {};
Copy the code

If you need Ant Design’s subcomponent declaration, you can change it

interface CarouselComponentProps {
  onIdxChange: (idx: number) = > void;
  currPage: number;
  listLength: number;
}

export interface CarouselItemProps {
  src: string;
}

interface Carousel extends React.FC<CarouselComponentProps> {
  Item: React.FC<CarouselItemProps>;
}

const CarouselComponent: Carousel = ({ onIdxChange, currPage, listLength, }) = > {};

const CarouselItem: React.FC<CarouselItemProps> = ({ src }) = > {};

CarouselComponent.Item = CarouselItem;

export default CarouselComponent;
Copy the code

HTMLAttributes

for example

// The component gets all the attributes of the HTML div element
interface CarouselComponentProps extends React.HTMLAttributes<HTMLDivElement> {
  onIdxChange: (idx: number) = > void;
  currPage: number;
  listLength: number;
}
Copy the code

useStateUse skills

This method returns a tuple. The first item of the tuple is the current state and the second item is the method to change the state. The function is signed as follows

useState<T>(initialState: T | (() = > T)): [T, React.Dispatch<React.SetStateAction<T>>]
Copy the code

The basic use

Although this Hook is consistent with the class-like this.setState most of the time, it is actually somewhat inconsistent because of the function component closure mechanism. A simple question: What does the console output after three quick clicks on the following two components?

function Counter() {
  const [count, setCount] = useState(0);
  const log = () = > {
    setCount(count + 1);
    setTimeout(() = > {
      console.log(count);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}

class Counter extends Component {
  state = { count: 0 };
  log = () = > {
    this.setState({
      count: this.state.count + 1});setTimeout(() = > {
      console.log(this.state.count);
    }, 3000);
  };
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.log}>Click me</button>
      </div>); }}Copy the code
  • The class component outputs 3, 3, 3, which is fine, because after 3 clicks this.state.count becomes 3. The state of the function addressed by this is {count: 3}, so the output is 3, 3, 3

  • The output of the functional component is 0, 1, 2, and each execution of the functional component has a separate closure environment. Retain the state and props of this rendering, and with three clicks render disassembly as follows:

    • The first time the page is rendered, the page sees count = 0

    • On the first click, the event handler gets count = 0, and count becomes 1. On the second rendering, the rendered page sees count = 1

    • On the second click, the event handler gets count = 1, and the count becomes 2. On the third rendering, the page is rendered with count = 2

    • On the third click, the event handler gets count = 2, and the count becomes 3. On the fourth rendering, the page sees count = 3

tip

If the function execution requires the latest state, but the function is defined in the initial closure, the latest state is not available, for example

const [count, setCount] = useState(0);
useEffect(() = > {
  const id = setInterval(() = > {
    setCount(count + 1);
  }, 1000); } []);Copy the code

The count is always 0, but if you change it, it’s correct

const [count, setCount] = useState(0);
useEffect(() = > {
  const id = setInterval(() = > {
    setCount((c) = > c + 1);
  }, 1000); } []);Copy the code

The callback function that uses the callback returned by useState must return the latest state, which can be used to get the latest state, and does not cause the child component to re-render if setState has the same value

// If fn is running in a closure, you can get the latest count by doing so without causing child components to render
const fn = () = > {
  let lastestCount = null;
  setCount((c) = > {
    lastestCount = c;
    return c;
  });
};
Copy the code

This is about getting the latest state in a closure, but it’s not recommended. Two questions

  • Semantics are too bad to read. CallsetStateNot for changestate
  • Set an identical one thoughstateDoes not cause the child components to rerender, but the components themselves will

In this case, you need to work with useRef

useRef

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component. `

The name useRef, which is similar to this, allows you to cache the latest state or get a DOM reference, which is easy to use, like this

const Component: React.FC = () = > {
  const [count, setCount] = useState<number> (0);
  const countRef = useRef<number>(count);
  / /
  countRef.current = count;
  // Or so
  useEffect(() = > {
    countRef.current = count;
  }, [count]);
};
Copy the code

There are two ways to bind countRef to the latest state, depending on personal habits

useEffect

React Hook (componentDidMount, componentDidUpdate, componentWillUnmount) There is also the problem of closures.

useCallback & useMemo & React.memo

UseCallback returns a mnemonized function, useMemo returns a mnemonized value, and useMemo returns a mnemonized value. If the function returned by useMemo returns a function, it is the same as useCallback

useCallback(fn, []) === useMemo(() = > fn, []);
Copy the code

These can be combined with react. Memo to do some optimizations, such as callbacks passed to child components

/ / the parent component
const Parent = () = > {
  const callback = () = > {
    /* Do something to change the state of the parent component */
  };
  return <Child calback={callback} />;
};
/ / child component
const Chlid = ({ callback }) = > {
  return <div onClick={callback}>test</div>;
};
Copy the code

The rerender mechanism of React shows that the parent component is re-rendered, and the child component is re-rendered, so we can add a layer of React.memo to the child component, but this is not enough. Callback also changes, so you can wrap callback with a useCallback layer

/ / the parent component
const Parent = () = > {
  const callback = useCallback(() = > {
    /* Do something to change the state of the parent component */} []);return <Child calback={callback} />;
};
/ / child component
const Chlid = React.memo(({ callback }) = > {
  return <div onClick={callback}>test</div>;
});
Copy the code

Note that the useCallback must work with React. Memo. This example uses only one of them, not only does it not prevent child components from re-rendering, but it also adds a shallow comparison

useContextuseReducer

You can refer to my previous article on useContext and useReducer implementation of Redux and React-redux

useImperativeHandleReact.forwardRef

These two can achieve the effect of the class component ref and specify the methods and properties that the parent component can reference, in conjunction with useRef

/ / the parent component
const Parent = () = > {
  const listRef = useRef(null);
  const fn = () = > {
    if (listRef.current) {
      // Only the four methods of the child component instance can be accessedlistRef.current.push(item); listRef.current.pop(); listRef.current.shift(); listRef.current.unshift(item); }};return <List ref={listRef} />;
};
/ / child component
const List = React.forwardRef((props, ref) = > {
  useImperativeHandle(ref, () = > ({
    push(item) {},
    pop() {},
    shift() {},
    unshift(item){},})); });Copy the code

conclusion

Here are some tips on React Hook that I hope will help you