This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Sorry, I ditched Vue and moved to the React camp. Not because of anything else, but because I get paid more to use React than I do to use Vue.

Before June, I memorized hundreds of React interview questions. When I joined a company using React, my salary increased by 120%.

As time went on, I found that getting started with React wasn’t that difficult. Is he born suitable for eating this bowl of rice …………

Today has been six months, here would like to share this period of harvest with digg friends, please digg friends more advice, progress together ha.

I started with React16.8, so I mostly used functional components and React Hooks for development.

And for the record, there’s a raffle at the end.

Lesson learned about functional components

A functional component can be thought of as a function that returns the React element, which takes an argument representing the component property, props.

Before Act 16.8, before React Hooks, functional components were only UI components whose output was completely controlled by parameter props. They had no state of their own, no business logic code, and were pure functions. Functional components have no instances, no life cycle and are called stateless components.

With the advent of React Hooks, functional components can be used as business components by attaching state and life cycles to them.

During the development process, both class components and functional components were used. After six months of development, FUNCTIONAL components were better than class components. The deepest feelings were the following two points:

  • You don’t have to learn class, you don’t have to worry about the annoying this pointing problem;
  • With high reusability, it is easy to extract common components and write custom hooks instead of higher-order components.

There is one very important difference between a functional component and a class component: the functional component captures the values used for rendering.

I didn’t know the difference until I met a BUG and understood the meaning of the difference in the process of solving the BUG.

The scene of that BUG is like this, an input box, after the input content, click the button to search, search first request an interface, get a type, and then use the type and input box value to request the search interface. Describe it briefly in code.

import React, { Component } from "react"; import * as API from 'api/list'; class SearchComponent extends Component { constructor() { super(); this.state = { inpValue: "" }; } getType () { const param = { val:this.state.inpValue } return API.getType(param); } getList(type){ const param = { val:this.state.inpValue, type, } return API.getList(param); } async handleSearch() { const res = await this.getType(); const type = res? .data? .type; const res1 = await this.getList(type); console.log(res1); } render() { return ( <div> <input type="text" value={this.state.inpValue} onChange={(e) => { this.setState({ inpValue: e.target.value }); }} /> <button onClick={() => { this.handleSearch(); </button> </div>); } } export default SearchComponent;Copy the code

The above code logic seems to be fine, but QA picked out a BUG for me. After I entered the content to search in the input box, I clicked the search button to start the search, and then I entered the content again in the input box soon, the result of which the search interface getList reported an error. GetType and search getList accept different parameters val.

In the process of checking, I was puzzled because val was reading this.state.inpValue in both requests. My colleague instructed me to change to functional components to solve this BUG.

import React, { useState } from "react"; import * as API from 'api/list'; export const SearchComponent = () =>{ const [inpValue,setInpValue] = useState(''); const getType = () =>{ const param = { val:inpValue } return API.getType(param); } const getList = (type) =>{ const param = { val:inpValue, type:type, } return API.getList(param); } const handleSearch = async ()=>{ const res = await getType(); const type = res? .data? .type; const res1 = await getList(type); console.log(res1); } return ( <div> <input type="text" value={inpValue} onChange={(e) => { setInpValue(e.target.value); }} /> <button onClick={() => { handleSearch(); </button> </div>); } export default SearchComponent;Copy the code

After changing to functional component, try again, no error reported, BUG fixed. Later I looked it up and learned that the values for the events in the functional component were the values for the state and props of the page rendering at the moment the events were triggered. When the search button is clicked, the value of val is the value in the input field at that moment, no matter how the value behind the input field changes, the latest value is not captured.

So why does the class component get the latest state value? The key is that the class component gets state through this, which is always the latest component instance.

This BUG can also be fixed by modifying the class component as follows:

getType (val) { const param = { val, } return API.getType(param); } getList(val,type){ const param = { val, type, } return API.getList(param); } async handleSearch() { const inpValue = this.state.inpValue; const res = await this.getType(inpValue); const type = res? .data? .type; const res1 = await this.getList(inpValue,type); console.log(res1); }Copy the code

When the search event handleSearch is triggered, the value of this.state.inpValue of the input box is stored in the inpValue variable, and the value of the inpValue variable is not affected by the subsequent input into the input box. Unless the search event handleSearch is triggered again. This modification also fixes the BUG.

Ii. Harvesting of controlled and uncontrolled components

It is important to understand the concepts and roles of controlled and uncontrolled components in React, as they are used in so many places.

  • The controlled components

React has a V-model directive that makes it easy to associate components with data. React has no such directive. How to associate components with data requires the concept of controlled components.

A controlled component, as I understand it, means that the state of the component is controlled by data, and the method of changing that data is not component. The component here is not just a component, but can also represent a native DOM. Like an input field.

The state of the input box (the value of the input box) is controlled by the value of the data.

import React, { useState } from "react";
const Input = () =>{
  const [value,setValue] = useState('');
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />
    </div>
  );
}
export default Input;
Copy the code

For example, when the Form component in Ant Design UI customizes a Form control, the custom Form control is required to accept the property Value and onChange, where value is used as the value of the custom Form control, and the onChange event changes the property value.

import React from 'react'; import { Form, Input } from 'antd'; const MyInput = (props) => { const { value, onChange } = props; const onNameChange = (e) => { onChange? .(e.target.value) } return ( <Input type="text" value={value || ''} onChange={onNameChange} /> ) } const MyForm = () => { const onValuesChange = (values) => { console.log(values); }; return ( <Form name="demoFrom" layout="inline" onValuesChange={onValuesChange} > <Form.Item name="name"> <MyInput /> </Form.Item> </Form> ) } export default MyForm;Copy the code

I think controlled components is the biggest role in the state of the third-party components often inexplicably wonderful changes, it is wrapped with a parent component by passing a change state method and control the state of the props, to control the state of the components mentioned parent component, such as component states change is very clear to know which place to change the state of the components.

  • Uncontrolled component

An uncontrolled component is a component whose state is completely controlled by itself, relative to a component. For example, if the input field is controlled by the input component, but the input component is not controlled by the Demo component, then the input field is relative to the Demo component.

import React, { useState } from "react";
const Input = () =>{
  const [value,setValue] = useState('');
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
      />
    </div>
  );
}
const Demo = () =>{
 return (
     <Input/>
 )
}
export default Demo;
Copy the code

Uncontrolled components can also be understood as components whose values can only be set by the user, not controlled by code. Also keep in mind that there is a very special DOM element that cannot set the uploaded file in code.

Iii. Harvest of useState

UseState is one of the most frequently used hooks. Here are three tips to share.

  • useStateYou can take a function as an argument and call it the state initializer

    UseState can be passed a value as the initial value for the state, but what if it takes some calculation to get to that value? At this point, you can pass in a function that returns an initial value when the calculation is complete.

    import React, { useState } from 'react';
    export default (props) => {
      const { a } = props;
      const [b] = useState(() => {
        return a + 1;
      })
      return (
        <div>{b}</div>
      )
    };
    Copy the code
  • How does state update use the old state

    This is how I used it in the beginning

    const [a , setA] = useState(1);
    const changeA = () =>{
       setA(a + 1);
    }
    Copy the code

    Then an error was encountered with the following code

    const [a , setA] = useState(1);
    const changeA = () =>{
       setA(a + 1);
       setA(a + 1);
    }
    Copy the code

    If YOU call setA twice in a row in this function you’ll see that a is still equal to 2, not 3. This is the correct call

    const [a , setA] = useState(1);
    const changeA = () =>{
       setA( a => a+1 );
       setA( a => a+1 );
    }
    Copy the code
  • How to split state

    In functional components, a single state change causes the component to be rerendered. In React, whenever the parent component is re-rendered, its children are re-rendered.

    The problem is that if you split the states into multiple parts, changing them one by one will trigger component rerendering multiple times.

    If you don’t split the state, changing the state will only trigger a component re-rendering once. Note, however, that functional components, unlike class components, do not automatically update the corresponding data in state by changing one data in state. This is how you deal with it

    const [data,setData] = useState({a:1,b:2,c:3}); const changeData = () =>{ setData(data =>{ ... data, a:2, }) }Copy the code

Of course, you can’t split state, which would greatly reduce code reusability. My experience:

  • Separate completely unrelated states into individual pieces.

  • If some states are related or need to be changed together, merge them into one state.

UseMemo and UsebCallback

  • An understanding of its definition
useCallback(fn,[a, b]);
useMemo(fn, [a, b]);
Copy the code

As shown above, fn is a function in the useMemo and usebcallback arguments, and a and b of [a,b] can be either state or props.

When useMemo and usebcallback are first executed, fn creates a cache and returns the cache, listening for a and B in [a,b], representing state or props, and returns the cache if the value has not changed. If the value has changed, fn re-creates a cache and returns the cache.

Both useMemo and UsebCallback return a cache, but this cache is different. UseMemo returns a value that can be considered returned by performing FN. Usebcallback returns a function that can be thought of as fn. So note that the FN passed to the useMemo must return a value.

  • In combination withReact.memoTo use the

A component is wrapped in react. Memo so that it is rerendered when its props change.

If the wrapped component is a functional component that has hooks for useState, useReducer, and useContext, it will still re-render when state or context changes. The main purpose of using React. Memo is to control the problem of parent component updates forcing child components to update as well.

The types of props can be String, Boolean, Null, undefined, Number, Symbol, Object, Array, or Function.

A comparison of two equal base types returns true, and a comparison of two equal reference types returns false.

console.log({a:1} == {a:1});
console.log([1] == [1]);
const fn1 = () =>{console.log(1)};
const fn2 = () =>{console.log(1)};
console.log(fn1 == fn2);
Copy the code

Because of this, when the value of props is a reference type, and the value is evaluated by a function, use useMemo or usebcallback to prevent the evaluated values from being equal, but the comparison is not equal, resulting in component updates. I think usebCallback is a Hook created specifically to handle functions with the props value.

In the following example, the List component is an expensive component to render. It has two properties, where the data property is the data source to render the List, which is an array, and the onClick property is a function. Introduce the List component in the Demo component, use useMome to deal with the data attribute value, use useCallback to deal with the onClick attribute value, so that whether the List component re-render is only controlled by the Data state of the Demo component.

The List component:

import React from 'react'; const List = (props) => { const { onClick, data } = props; / /... return ( <> {data.map(item => { return ( <div onClick={() =>{onClick(item)}} key={item.id}>{item.content}</div> ) })} </> ) } export default React.memo(List);Copy the code

The Demo module:

import
React,
{ useState, useCallback, useMemo }
  from 'react';
import List from './List';

const Demo = () => {
  //...
  const [data, setData] = useState([]);
  const listData = useMemo(() => {
    return data.filter(item => {
      //...
    })
  }, [data])

  const onClick = useCallback((item) => {
    console.log(item)
  }, []);

  return (
    <div>
      <List onClick={onClick} data={listData} />
    </div>
  )
}

export default Demo;
Copy the code

It can be seen that useMemo and UsebCallback, as a means of performance optimization, can solve the performance problem of React parent component updating and its children being forced to update to a certain extent.

  • Used alone

Assuming the List component is not wrapped in React. Memo, can it be optimized using useMemo or UsebCallback? Starting with a piece of code, you can also use components this way.

import React, { useState } from 'react';
import List from './List';

export default function Demo() {
  //...
  const [data, setData] = useState([]);
  const list = () => {
    const listData = data.filter(item => {
      //...
    })
    const onClick = (item) => {
      console.log(item)
    }
    return (
      <List onClick={onClick} data={listData} />
    )
  }
  return (
    <div>
      {list()}
    </div>
  )
}
Copy the code

List returns a React element, which is a value. Use useMemo to handle it.

import React, { useState, useMemo } from 'react';
import List from './List';

export default function Demo() {
  //...
  const [data, setData] = useState([]);
  const listMemo = useMemo(() => {
    const listData = data.filter(item => {
      //...
    })
    const onClick = (item) => {
      console.log(item)
    }
    return (
      <List onClick={onClick} data={listData} />
    )
  }, [data])
  return (
    <div>
      {listMemo}
    </div>
  )
}
Copy the code

Whether the listMemo value (the React element) is regenerated is controlled only by the data state of the Demo component. This does not mean that the List component is only governed by the data state of the Demo component.

  • Can’t abuse

You can’t assume that “no matter what, you can just handle it with useMemo or useCallback and you’re away from performance problems.”

Realize that useMemo and useCallback also have some computational overhead. For example, useMemo will cache some values, then re-render them, and compare the values in the dependent array with the last recorded value. If they are not equal, the callback will be executed again. This process has some computational overhead.

Therefore, before using useMemo and useCallback, you must carefully consider the usage scenarios and do not abuse them.

Here’s a summary of the scenarios where useMemo and useCallback are used:

  • When an expensive component property is a function, use useCallback to handle the property value.

  • You get a value, which is a lot of overhead, so use useMemo to do that.

  • A value of a computed reference type assigned to an expensive component property, handled with useMemo.

  • The value of the reference type exposed in a custom Hook is handled with useMemo.

In a word useuseMemoanduseCallbackThis is to keep references equal and avoid repeating costly calculations.

5. Harvest of useEffect and useLayoutEffect

useEffect(fn);
useLayoutEffect(fn);
Copy the code

UseEffect is the Hook with the second highest frequency of use, while useLayoutEffect has a low frequency of use. Here are four tips:

  • Timing of execution

Before using useEffect and useLayoutEffect, it is important to know when the functions passed to these hooks will be executed.

The useEffect function passed in is executed after React completes the DOM changes and the browser renders the DOM.

The function passed to useLayoutEffect is executed after React has made changes to the DOM and before the browser renders the DOM.

So useEffect doesn’t block page rendering, useLayoutEffect does, but useLayoutEffect is a good choice if you want to get the attributes of a DOM element before rendering to make some changes.

  • I only want to do it once

The useEffect and useLayoutEffect functions passed in are re-executed during component initialization and each update. What if you only want to execute it once when the component is initialized? Mounted in Vue

useEffect(fn,[]);
useLayoutEffect(fn,[]);
Copy the code
  • Pit used for event listening

UseEffect is used by passing an empty array [] to the second argument, which is equivalent to Vue mounted. Then remove the event listener with removeEventListener in beforeDestory. UseEffect.

useEffect(() => { window.addEventListener('keypress', handleKeypress, false); return () => { window.removeEventListener('keypress', handleKeypress, false); }; }, [])Copy the code

It is used to listen for keyboard events, but you will find a strange phenomenon, some pages return after the handleKeypress method, some pages return after a few times handleKeypress method, no longer execute.

This is because a useEffect is executed before the return function of the previous useEffect function is executed. This return function can be used to unbind, cancel the request, and prevent memory leaks.

In addition, the return function of useEffect’s passing function is executed when the component is unloaded.

The following code is shown:

window.addEventListener('keypress', handleKeypress, false); / / run the first effect window. RemoveEventListener (' keypress handleKeypress, false); // Clear the previous effect window.addeventListener ('keypress', handleKeypress, false); / / run the next effect window. The removeEventListener (' keypress handleKeypress, false); // Clear the previous effect window.addeventListener ('keypress', handleKeypress, false); / / run the next effect window. The removeEventListener (' keypress handleKeypress, false); // Clear the last effectCopy the code

To solve this problem, remove the second parameter to useEffect. This is different from Mounted in Vue.

  • Used to listen on a state or prop

The second argument to useEffect and useLayoutEffect is an array, called a dependency, that executes the function passed in when the dependency changes.

For example, monitor a state and b prop.

useEffect(fn,[a,b]);
useLayoutEffect(fn,[a,b]);
Copy the code

However, it is important not to monitor too many states or prop at one time, that is, useEffect depends too much, if too much, it should be optimized, otherwise it will cause the useEffect to be difficult to maintain.

Here’s how I optimized it:

  • Consider whether the dependency is necessary and not necessary to remove it.
  • willuseEffectBroken down into smaller units, eachuseEffectDepends on the respective dependency array.
  • Multiple states are aggregated into a single state by merging related states in dependencies.

Six, the concluding

The five points above are the most impressive gains I have made in these six months, including the pits I have stepped on and the codes I have written that have been ridiculed by the leader. But the biggest gain was a 120% increase in salary. Hahaha, you have to be willing to step out of your comfort zone to get a better deal.

Although the above gains may seem relatively simple to some diggers, for diggers who have just moved into React, these knowledge are frequently used, so we need to consider more. If there are mistakes, please point them out in the comments, or share them in the comments if you have better results.

“Welcome to the discussion in the comments section. The nuggets will be giving away 100 nuggets in the comments section after the diggnation project. See the event article for details.”