React HookEmerging background

Class component

  • Difficult to reuse component state, high order component + render propertiesproviders customersA bunch of tools are designed to solve this problem, but create a hell of a cost of understanding and component nesting
  • Life cycle brings negative effects and logic splits seriously
  • This points to the problem

Limitations of functional components

  • There was no function component beforestateAnd life cycle, resulting in limited use scenarios

React Hook

Hooks are a new feature added in React 16.8 that lets you use state and other React features without having to write classes, without converting them to class components

HookThe use and practice of

useStateHookThe closure mechanism of

/ / hook component
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>
  );
}

// Equivalent class component
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

In the case of a quick click, think Hook components and functional components console print out what?

  • Class component prints out3 3 3

    ClassThe component’sstateIs immutable throughsetStateReturns a new reference,this.statePoint to a new reference

    setTimeoutWhen executing, passthisGet the lateststateReference, so this output is all3
  • The output of the function component is0 1 2

    Closure mechanism for function components. Function components have independent closure for each renderingpropsstate

    Each rendering has its own event handler

    The state of each render is not affected by subsequent event processing

Function component rendering disassembly

Since each rendering is a separate closure, you can try code dismantling the rendering process for functional components

// First click
function Counter({
  const [0, setCount] = useState(0);  
  const log = () = > {
    setCount(0 + 1);
    // Only get the state of the button that was clicked this time
    setTimeout(() = > {
      console.log(0);
    }, 3000);
  };
}
// Second click
function Counter({
  const [1, setCount] = useState(0);  
  const log = () = > {
   setCount(1 + 1);
    setTimeout(() = > {
      console.log(1);
    }, 3000);
  };
}
// Click the third time
function Counter({
  const [2, setCount] = useState(0); 
  const log = () = > {
    setCount(2 + 1);
    setTimeout(() = > {
      console.log(2);
    }, 3000);
  };
}
Copy the code
  • Three clicks, four render times, and the count changes from 0 to 3

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

  • On the first click, the event handler gets count = 0, and the count becomes 1. On the second rendering, the page sees count = 1 after rendering, which corresponds to the first click of the above code

  • The second click, the event handler gets the count = 1, the count becomes 2, the third rendering, the page after rendering, the count = 2, corresponding to the above code second click

  • On the third click, the event handler gets the count = 2, and the count becomes 3. On the fourth render, the page sees the count = 3, corresponding to the above code on the third click

Let functional components also output3 3 3

A simpler solution to the problem is to borrow useRef

  • useRefReturns a variablerefObject, itscurrentProperties are initialized to the parameters passed inThe initialValue ()
  • useRefThe returnedrefObject remains the same throughout the lifetime of the component, that is, each time the function component is rerenderedrefThe object is the same
  • useRefThis is analogous to the instantiation of a class componentthisThe returned reference is the same when the component is not destroyed
function Counter() {
  const count = useRef(0);
  const log = () = > {
    count.current++;
    setTimeout(() = > {
      console.log(count.current);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count.current} times</p>
      <button onClick={log}>Click me</button>    
    </div>
  );
}
Copy the code
  • With this modification, the console output is indeed3 3 3
  • ? sinceRefThe object remains the same throughout its lifetimecurrentProperties are just modifying properties, except for printing, hereYou clicked 0 timesIf you click it three times, it becomes3?
  • Obviously not, this component does not have any properties or state changes, so it will be rerendered, so click here3Times, but not likeuseStateSame thing, render4Second, this will only render1And then all I see isYou clicked 0 times
  • Fixing one problem introduces a bigger problem, which is very programmers…

useEffect

Although useRef can solve the problem of printing, but the page rendering is not right, here we still use useState solution, with useEffect can achieve the effect we want

function useEffect(effect: EffectCallback, deps? : DependencyList) :void;
Copy the code
  • Look at theuseEffectThe signature,effectIs a function type and is required. There is a second optional argument of type read-only array
  • useEffectIs to deal with side effects, which are executed at each timeRenderAfter rendering, in other words each rendering will be performed in realDOMAfter the operation is completed.

With this hook, if the value in ref is updated after rendering after each state change, then the console prints the value of ref

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

This is what we want it to look like. The page goes from 0, 1, 2, 3, and then the console outputs 3, 3, 3, and then we break down the rendering process.

  • Three clicks, total4Time to render,count0into3
  • Page initialization rendering,count = 0.currentCount.current = 0, page display0“, rendering completed, triggeruseEffect.currentCount.current = 0
  • The first time I click,count = 0, after rendering,count = 1, page display1To triggeruseEffect.currentCount.current = 1
  • The second time I hit it,count = 1, after rendering,count = 2, page display2To triggeruseEffect.currentCount.current = 2
  • On the third click,count = 2, after rendering,count = 3, page display3To triggeruseEffect.currentCount.current = 3
  • Three clicks done,currentCount.current = 3On the fourth render, the page is seencount = 3.setTimeoutIs called incurrentCountThis object, the output is both3

useEffectThe return value of the function

type EffectCallback = () = > void | (() = > void | undefined);
Copy the code

The useEffect callback can return null or a function. If a function is returned, the function returned by the previous effect callback will be executed first

useEffect(() = > {
  console.log('after render');
  return () = > {
    console.log('last time effect return');
  };
});
Copy the code

With this useEffect, the console will output last time effect return and then after render

UseEffect and the class component lifecycle

As mentioned earlier, useEffct takes two arguments. The second argument is an optional list of effects’ dependencies. React decides whether to execute the callback after rendering based on whether or not the values of the list have changed

If you do not pass this parameter, React will assume that the effect is executed after each rendering, which is equivalent to the unconstrained componentDidUpdate lifecycle

useEffect(() = > {
  currentCount.current = count;
});
componentDidUpdate() {
  currentCount.current = this.state.count;
}
Copy the code

If this parameter is an empty array, React will assume that any state or property changes in the component will not trigger the effect, which means that the effect is only performed once after the component is rendered, and will not trigger any subsequent updates to the component. Equivalent componentDidMount

useEffect(() = >{ currentCount.current = count; } []);componentDidMount() {
  currentCount.current = this.state.count;
}
Copy the code

When combined with the return function of the useEffect callback, an effect like componentWillUnmount can be achieved, because if the array is empty, any update to the component will not trigger effect, so the return function of the callback can only be executed when the component is destroyed

useEffect(() = > {
  return () = > {
    console.log('will trigger ar willUnmount')}}, []);componentWillUnmount() {
  console.log('will trigger ar willUnmount')}Copy the code

If there is a value in the dependency list, then something like componentDidMount is conditionally updated, only if the last status is different from this one

useEffect(() = > {
  currentCount.current = count;
}, [count]);
componentDidUpdate(prevProps, prevState) {
  if(prevState.count ! = =this.state.count) {
    currentCount.current = this.state.count; }}Copy the code

useEffectAnd closure problems

Suppose that the component needs to define a timer that increments the count at initialization time, it would be natural to write the following code

// Initialize count = 0
useEffect(() = > {
  const id = setInterval(() = > {
    setCount(count + 1);
  }, 1000); } []);componentDidMount() {
  setInterval(() = > {
    this.setState({ countthis.state.count + 1 });
  }, 1000);
}
Copy the code

But in practice, the class component is displayed correctly, and after the function component increments from 0 to 1, the page rendering is never changed

  • As I mentioned before, class components because there arethisThis reference, it’s easy to passstateGet the latest value
  • Function components are independent closures each time they are rendered, in this case because the write dependency value is[], so only after the first render, this will be doneeffect, after the first rendering,countis0, sosetCount(count + 1)Every time it’s executionsetCount(0 + 1), so the timer works normally, but the value is wrong.

The pointcut and occurrence scenario of the closure problem

Closure problems mostly occur when some callback execution depends on some component state that is not written to the useEffect dependency list. The component state is not up to date when the callback is executed. The main scenes are:

  • The timer
  • Event listener callback
  • A variety ofObserverThe callback

In these scenarios, it is usually only necessary to define a callback function once the component has been initialized and rendered, but if the callback function depends on a component’s transition or property, be careful about closures

function Router() {
  const [state, setState] = useState<string> (' ');
  useEffect(() = > {
    window.addEventListener<'hashchange'> ('hashchange'.() = > {
        // Listen for hash changes, depending on state
      },
      false); } []); }Copy the code

For example, after the component is rendered and listens for hashChange, the callback function does not get any subsequent updates to the state, only the empty string that was initialized.

Try to solve the closure problem – listenstatechange

Since the callback function needs to get the latest state every time, it can listen for changes in state. When the state changes, it can redefine the event listener

function Router() {
  const [state, setState] = useState<string> (' ');
  useEffect(() = > {
    window.addEventListener(
      'hashchange'.() = > {
        // Listen for hash changes, depending on state
      },
      false
    );
  }, [state]);
}
Copy the code

The code above works, but each time state changes, a hashChange callback is redefined, but the last hashChange event listener is not cleared. The code runs, but the memory leak is too bad. You can use the function returned by the useEffect callback to clear the last event listener

function Router() {
  const [state, setState] = useState<string> (' ');
  useEffect(() = > {
    const callback = () = > {};
    window.addEventListener('hashchange', callback, false);
    return () = > window.removeEventListener('hashchange', callback, false);
  }, [state]);
}
Copy the code

So the memory leak problem is solved, but this kind of thing listens, normally it is good to set once, there is no need to redefine, what other better way?

Try to solve the closure problem –setStateAnother way to update the state of a component

In addition to passing a value, the function that useState returns to update the state can also pass a callback function. The callback function takes an argument, and the argument is the latest state. In this case, the previous timer example can be modified to look like this.

function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() = > {
    const id = setInterval(() = > {
      // setCount(count + 1)
      setCount((c) = > c + 1);
    }, 1000);
    return () = > clearInterval(id); } []);return <h1>{count}</h1>;
}
Copy the code

SetCount ((c) => c + 1)); setCount((c) => c + 1) Return the latest value of count, which is setCount((c) => c + 1), c inside.

Similarly, for event listeners, we can use this method to get the latest state, but there are a few problems

  • This callback function, in fact, only needs to get the lateststate, so in the callsetStateWhen you get the latest value, remember tosetStateIs set to the same value as the current one. If there is no return, then callsetStateAfter that,stateThe value of theta is going to be thetaundefined
  • setStateDoes returning the same value cause the component and its children to be rerendered? When you call the update function of the State Hook and pass in the current State, React skips the subcomponent rendering and effect execution. Note that React may still need to render this component before skipping the render. Don’t worry, though, since React doesn’t unnecessarily render the “deep” nodes of the component tree
  • It looks like it’s going to work, but with a few simple changes it could actually work like this
function Router() {
  const [state, setState] = useState<string> (' ');
  useEffect(() = > {
    const callback = () = > {
      letLatestState = ' '; setState((stateCallback) = > {
        // stateCallback is the latest state
        latestState = stateCallback;
        // Remember to return stateCallback, otherwise state will be changed to undefined
        return stateCallback;
      });
      // latestState has been assigned to the latestState};window.addEventListener<'hashchange'> ('hashchange', callback, false); }}, [])Copy the code

That should be fine. You can only define a callback once, and then you can get the latest state, but there’s still a problem

  • If the setState callback does not write return stateCallback; This code, which causes state to be set to undefined for no apparent reason, is very hard to find and poorly maintainable

  • SetState is for changing the state of a component, it’s not for you to do that, although it’s perfectly fine to do that. But maintainability is so bad, if your code gets taken over, people are wondering why it’s being written this way, and with no comments and bad variable names, it’s basically zero maintainability, right

  • Setting the same state does not cause the subcomponent to be rerendered, but it is possible for this component to be rerendered, according to the official website

The plan is not perfect. Let’s think outside the box, okay? When executing the callback function, we need to get the latest state. Can we cache state with an unchanging value? Etc?? Constant value??

Best practices for solving closures –useStateanduseRef

The return of useRef is an object that remains constant throughout the lifetime of the component, and you can use useRef to get the latest state. For example, the example could look like this:

function Router() {
  const [state, setState] = useState<string> (' ');
  const stateRef = useRef<string>(state);
  // This allows you to bind stateRef to the latest state
  stateRef.current = state;
  // Alternatively, you can bind stateRef to the latest state
  useEffect(() = > {
    stateRef.current = state;
  }, [state]);
  useEffect(() = > {
    const callback = () = > {
      const latestState = stateRef.current;
    };
    window.addEventListener<'hashchange'> ('hashchange', callback, false); } []); }Copy the code

Stateref. current = stateref. current = stateref. current = stateref. current = stateref. current = stateref. current = stateRef.

useRefuseStateBest practices for

  • useStateuseRefWhat animal is similar to a class component? Whether andthis.statethisProperties of
  • In the class component, if there is no rendered property, directly hangthisIf you need to participate in rendering properties, hang onthis.state
  • Similarly, in theHook,useRefuseStateA similar effect can be achieved

Take the following example

// Function component
const Child = React.memo(() = > {
  // Count to participate in page rendering
  const [count, setCount] = useState(0);
  // userInfo does not render
  const userInfo = useRef(null);
});
/ / class components
class Child extends React.PureComponent {
  constructor(props) {
    super(props);
    // Do not render
    this.userInfo = null;
    // Attributes that participate in rendering
    this.state = {
      count: 0}; }}Copy the code

Look again atuseEffectThe return value of the callback function

type EffectCallback = () = > (void | (() = > void | undefined));
return void | (() = > void | undefined)
Copy the code

This callback is not an asynchronous function that returns a Promise object by default, so this is not recommended

const [data, setData] = useState({ hits: []}); useEffect(async() = > {const result = await axios(
      'url'); setData(result.data); } []);Copy the code

To get around this problem, let’s rewrite it

useEffect(() = > {
  const fetchData = async() = > {const result = await axios('url'); setData(result.data); }; fetchData(); } []);Copy the code

useCallback

It’s ok to write a function in it, it’s recommended, but if I have a side effect that I need to handle multiple functions or a long function, one that’s ugly or one that’s too hard to maintain, you can use useCallback to pull the function out. UseCallback returns a memorized function, The dependency list returns a new function if and only if any of its properties change, so this feature is suitable for callbacks passed to child components

function Counter() {
  const [count, setCount] = useState(0);
  const getFetchUrl = useCallback(() = > {
    return 'https://v? query=' + count;
  }, [count]);
  useEffect(() = > {
    getFetchUrl();
  }, [getFetchUrl]);
  return <h1>{count}</h1>;
}
Copy the code

Here, if the count changes, the value of getFetchUrl changes, causing the useEffect to fire

React.memo

React.memo() returns a memorized value. If React internally determines that if the props’ are not the same as the props’, it will be rerendered. If there are no changes, it will not trigger component rendering. Using this feature can reduce unnecessary subcomponent re-rendering

const Child = memo((props) = > {
  useEffect(() = >{}, [])return (
    // ...)},(prevProps, nextProps) = > {
  // Logic for determining equality
  // If some property changes do not require rerendering
  // You can write this function
})

Copy the code

React.useCallbackReact.memo

  • UseCallback returns a cached function in the function component. Each time the component function is executed, the function inside the component is redefined. In this case, The callback function passed by the parent component to the child component changes each time the rendering is performed

  • From the memo point of view, every time the parent component is rendered, the child function will be rerendered without the memo, even if the child has no dependencies and no properties

  • If you add a useCallback to the parent component separately for the child component’s callback function, you can avoid redefining the callback function. However, if the child component is not wrapped in a memo, it will still cause the child component to be rerendered even if any of the child component’s properties have not changed.

  • Similarly, if the child component is wrapped in a memo separately, the parent component will still be redefined every time it renders the callback function

  • Therefore, both Memo and useCallback must be used, otherwise it will be useless, not only fail to achieve the optimization effect, but also increase the burden of React comparison. Either don’t use it, or use it all.

React.useCallbackReact.memoBest practices

The parent wraps functions with useCallback, and the child wraps components with Memo, or none at all

/ / child component
// Callback is the callback passed by the parent component
const Child = ({ callback }) = > {}
// The child components are wrapped in React. Memo
export default React.memo(Child);

Const Parent = () => {
  // Subcomponent callbacks are wrapped in usecallBacks
  const callback = React.useCallback(() = >{} []);return <Child callback={callback} />
};
Copy the code

Raect.memoThe limitations of

  • If the parent component causes the child component to rerender, the memo component will determine whether the property has changed from the last rendering time. If it has changed, the child component will rerender, otherwise it will not rerender.

  • React.memo has a limitation that it can only prevent properties from coming from outside the component. If properties are from inside the component, react.memo has no effect. For example, it cannot prevent properties from being injected directly into the component by useContext

export const Store = React.createContext(null);
export default function Parent() {
  const [state, dispatch] = useReducer(reducer, { count: 0.step: 0 });

  return (
    <Store.Provider value={{ dispatch.state}} >
      <Step />
      <Count />
    </Store.Provider>
  );
}

export const Count = memo(() = > {
  const { state, dispatch } = useContext(Store);
  const setCount = () = > dispatch({ type: 'changeCount' });
  return (
    <>
      <span>count: {state.count}</span>
      <button onClick={setCount}>change count</button>
    </>
  );
});

export const Step = memo(() = > {
  const { state, dispatch } = useContext(Store);
  const setCount = () = > dispatch({ type: 'changeStep' });
  return (
    <>
      <span>count: {state.step}</span>
      <button onClick={setStep}>change step</button>
    </>
  );
});
Copy the code
  • The components above,countorstepAny change in this property will cause the two subcomponents to be rerendered, which is clearly incorrect.

React.useMemoInstead ofReact.momo

The difference between useMemo and useCallback is that it returns a mnemonized function and a mnemonized value. If useMemo’s callback function is executed and returns a function, It will work just as well as useCallback. Therefore, the above component can be changed. This way of writing prevents any property change from causing two subcomponents to be rerendered

export const Count = () = > {
  const { state, dispatch } = useContext(Store);
  const setCount = () = > dispatch({ type: 'changeCount' });
  return useMemo(
    () = > (
      <>
        <span>count: {state.count}</span>
        <button onClick={setCount}>change count</button>
      </>
    ),
    [state.count]
  );
};
export const Step = () = > {
  const { state, dispatch } = useContext(Store);
  const setStep = () = > dispatch({ type: 'changeStep' });
  return useMemo(
    () = > (
      <>
        <span>step: {state.step}</span>
        <button onClick={setStep}>change step</button>
      </>
    ),
    [state.step]
  );
};
Copy the code

React.momoReact.useMemo

  • React.momoIn terms of preventing subcomponents from being rerendered, there is one in the class componentReact.PureComponent, its function is also. But it does not detect state changes inside the function and prevents re-rendering, for exampleuseContextThe state of the injection. However, it automatically compares all properties and uses aspects.
  • React.memoDepending on whether the dependency list has changed properties, decide whether to return the new value, in some degree andVueIs similar to the computed properties of, but requires that the declared dependent properties be invoked. Compared with theReact.momoThe granularity of its control is more granular, but the general external attribute changes that use this one are obviously notReact.memoconvenient

useReducer useContext

  • useReduceruseStateAs an alternative to,useStateThe internal implementation ofuseReducer
  • It takes two parameters, alpha and betareduxSame thing, one isreducerOne is the initial value, and two are returned, always the currentstate, one isdispatch
  • throughdispatchcallactionI can change itstateThe data inside
  • Essentially, it decouples the data from the function components so that the function components just emitAction, you can modify the data, since the data is not inside the component and does not have to deal with the internalstateChange brought abouteffect.
  • useContextuseReducerCombination, to a certain extent can achieve aReact Redux.Nuggets article linksThis is the previous article, I won’t expand it here

otherHook

  • UseImperativeHandle, combined with useRef and forwardRefs, allows a custom parent component to refer to properties and methods of the child component instead of referring to the entire instance of the child component. But you don’t want all the properties and methods to be invoked by the parent component

  • UseLayoutEffect is not used much, and its function is the same as useEffect. However, this hook is called after component changes, after DOM node generation, and before rendering. Different from useEffect, which is called after rendering, it is not recommended to use, because it will block rendering

  • UseDebugValue can be used to display a label for custom hooks in the React developer tools. Similar to Vue’s name or React’s displayName, it does not affect code running

Component reuse

React hooks have custom hooks, React class components have higher-order components or render attributes. There is a common scenario that the back-end interface needs to be called to enter the page. If each component is written once, it will be tedious, assuming that the interface for processing data is like this

interface Response<T> {
  /** Whether there is an error */
  hasError: boolean;
  /** Whether there is data */
  hasData: boolean;
  /** Whether the request is in */
  Loading: boolean;
  /** Request back data */
  data: T;
}
Copy the code

High order component

High Order Component (HOC) is an advanced technique used in React to reuse component logic. Instead of being part of the React API, HOC is a design pattern based on the React combinatorial features. Reusing components like Connect in react-Redux or withRouter in React Router is common

It can do:

  • Property brokers, such as common properties used by multiple components, inject properties
  • Wrap a component, such as wrapping it in a written container
  • Render hijacking, such as permission control

use

  • Code reuse
  • Performance monitoring
  • Permission control, according to do not understand the permission level, rendering different pages

High-level component writing and use

As soon as a component is rendered according to the requirements requested above, start requesting the initial data

function requestWrapper(options) {
  return (Component) = > {
    return class WapperComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          loading: false.hasError: false.hasData: false.data: null}; } httpRequest =async() = > {this.setState({ loading: true });
        // Do some request work here, I will not write here, it is not necessary
        this.setState({ hasData: true });
        this.setState({ hasError: false });
        this.setState({
          data: {
            /** Here is the data */}});this.setState({ loading: false });
      };
      componentDidMount() {
        this.httpRequest();
      }
      render() {
        // Pass through the attributes passed from the outside, then merge this.state and pass it to the package component
        constcombineProps = { ... this.props, ... this.state };return this.state.loading ? (
          <Loading />
        ) : (
          <Component {. combineProps} / >); }}; }; }Copy the code

Using aspects, higher-order components can decorate class components or function components

function Page(props) {
// props includes loading hasError hasData data prop1
Prop1 comes from the external attribute and the other attributes come from the component of the package
}
// Class component use, use decorators
@requestWrapper({url: ' '.param: {}})class ClassComponent extends React.PureComponent {}// class component. Decorators are not applicable
export default requestWrapper({url: ' '.param: {} })(ClassComponent);
// Use a functional component
const WrapPage = requestWrapper({ url: ' '.param: {} })(Page);
export default WrapPage;
/ / use
<WrapPage prop1="1" />;
Copy the code

The customHookWrite and use

Custom Hook, a simple data request Hook

function useRequest(options) {
  const [loading, setLoading] = useState(false);
  const [hasError, setHasError] = useState(false);
  const [hasData, setHasData] = useState(false);
  const [data, setData] = useState(null);
  useEffect(() = > {
    async function reqeust() {
      setLoading(true);
      /** this is a request
      setHasError(false);
      setHasData(true);
      setData({
        /** Request back data */
      });
      setLoading(false); } reqeust(); } []);return { loading, hasError, hasData, data };
}
Copy the code

Custom hooks can only be used in functional components, not in class components

function Page(props) {
  // Props for this time is prop1 only
  const { loading, hasError, hasData, data } = useRequest({
    url: ' ',param: {},}); } <Page prop1={1} / >;Copy the code

Default properties for functional components and class components

class Button extends React.PureComponent {
  static defaultProps = { type: "primary".onChange: () = >{}}; }// This can be used for both functions and classes. This is another way to write static properties of a class
Button.defaultProps = {
  type: "primary".onChange: () = >{}};function Button({ type, onChange }) {}

// This looks fine, but in fact, if the parent component does not pass onChange, onChange
// Each time the component is rendered, a new function is generated. Reference types have this problem
function Button({ type = "primary", onChange = () => {} }) {}

// That's OK. You are really a dodger
const changeMet = () = > {}
function Button({type = "primary", onChange = changeMet}) {}
Copy the code

Has the class component problem been resolved?

It is difficult to reuse component state logic

  • Relying on custom hooks solves the problem of component state and logic reuse, but customHookWriting needs to matchHookThe operating mechanism is well understood and the threshold is no lower than for higher-order components

Life cycle brings negative effects and logic splits seriously

  • Lifecycle split logic issues inHookInside is actually solved, there is no same logic to be split inNIn the entire life cycle

This points to the problem

  • This problem is also solved in the Hook, because the function does notthis, there won’t bethisIf you need an immutable object, useuseRef

Simple summary

  • UseState can implement effects similar to state and setState

  • UseEffect componentDidMount componentDidUpdate componentWillUnmount this life cycle function, and write more simple, after each rendering will trigger, trigger the condition is that the dependency has changed

  • UseRef returns a reference that returns the same object each time it is rendered, consistent with the this property of the class component

  • UseCallback returns a memorized callback function. If a dependency changes, the callback function is changed. Otherwise, the previous callback function is returned

  • UseMemo returns a mnemonized value, which changes only when dependencies change. It can be used to mnemonize values, similar to Vue properties, to avoid double computation and to avoid repeated rendering

  • Custom hooks enable state and logic reuse, much like higher-order components and render properties

  • UseReducer is the underlying implementation of useState that manages multiple states and pulls them out of the components

  • UseContext can be used to batch pass values, inject multiple components, and useReducer useMemo can use the function of Redux

Use feeling

Personal use

  • Functional components themselves write a lot less code than class components

  • Closure issues have a significant impact on development and debugging, raising debugging costs and making it difficult to detect problems if you are not familiar with closure mechanisms. Most of the closure problems in hooks are due to unfilled dependencies

  • Closures are more annoying than the This component, and major debugging is not easy to find, and filling in dependencies can be a tricky chore

  • The dependency of Hook cannot be identified automatically and must be declared manually. Although the plug-in is added, it is still not as good as Vue’s Hook

  • When you are familiar with the mechanism of hooks, the development experience of hooks is much better than that of class components

Team work

  • It’s actually promotingHookWhen the team membersHookAs mentioned above, there are various ways to solve the closure problem. In fact, I found out by myself. Then I saw that the team members were almost still usersstateAfter the update, we reset the way we listen, which is not very good, but the closure problem is resolved
  • In contrast,ReactThe official also did not summarize many best practices, many rely on their own practices, so the team members are just in contactHook“, bothuseEffect useStatetwoAPIAnd even inReact HookIn the official documentHook profileFor both of theseHookA lot of introductions
  • But for other commonly usedHook, such asuseRefuseCallbackUsage scenarios don’t really have good examples to support theseAPIThe use of. In fact, many members of the team do not participate in the rendering of the property, but also useuseStateInstead of usinguseRef. It’s a lot of new contactHookAn easy mistake to make.
  • There are many students some plug-ins did not install, resulting inReactA plug-in that automatically detects dependencies does not work, which frosts a closure problem that is already hard to find
  • So I also regularly share what I think is a good practice in the team to guide the students in the team
  • For people who don’t like to useReact HookStudents, directly use class components, class components although the code is cumbersome to write, but at least there is no closure problems, and the code is easy to read after taking over,React HookIt’s just a tool, and if you use it, you’ll get points, but if you don’t use it, you’ll only use class components, and it won’t affect other people’s code. Compare class components and function components can coexist