Introduction to the

React 16.8 will be released in 2019.2. This is a feature that will improve code quality and development efficiency.

However, it is important to understand that there is no perfect best practice, and stable practices are more important than sound ones for a high performing team, so this solution is just one of the best practices.

Intensive reading

Environmental requirements

  • Have a stable front-end team that understands functional programming.
  • Enable ESLint plugin: eslint-plugin-react-hooks.

Component definition

Function Component is defined as const + arrow Function:

const App: React.FC<{ title: string }> = ({ title }) => {
  return React.useMemo(() => <div>{title}</div>, [title]);
};

App.defaultProps = {
  title: 'Function Component'
}
Copy the code

The above example contains:

  1. withReact.FCDeclare the Function Component Component type and define the Props parameter type.
  2. withReact.useMemoOptimize rendering performance.
  3. withApp.defaultPropsDefine the default values for Props.

FAQ

Why not use React. Memo?

Use react. useMemo instead of React.memo is recommended because there is a use of react. useContext for component communication. This use of react. useContext causes all components to be re-rendered.

Should components without performance issues also use useMemo?

Yes, who would want to add useMemo at a time when the future maintenance of this component might inject some data through useContext, etc.?

Why not use deconstruction instead of defaultProps?

While it’s more elegant to write defaultProps destructively, there’s a catch: references to object types change every time you Rerender them, which can cause performance problems, so don’t do it.

Local state

There are three types of local states, which are in order of common use: useState useRef useReducer.

useState

const [hide, setHide] = React.useState(false);
const [name, setName] = React.useState('BI');
Copy the code

State function name to indicate, as far as possible together declare, easy to refer to.

useRef

const dom = React.useRef(null);
Copy the code

Use useRef sparingly; lots of Mutable data can affect the maintainability of your code.

However, useRef storage is recommended for objects that do not need repeated initialization, such as new G2().

useReducer

It is not recommended to use useReducer for local status. As a result, the internal status of functions is too complex and difficult to read. UseReducer is recommended to be used in combination with useContext for communication between multiple components.

FAQ

Is it possible to declare normal constants or normal functions directly inside functions?

No, Function Component is re-executed every time it is rendered. It is recommended that constants be placed outside functions to avoid performance problems. Functions are recommended to use useCallback declarations.

function

All functions in Function Component must be wrapped with react. useCallback for accuracy and performance.

const [hide, setHide] = React.useState(false); const handleClick = React.useCallback(() => { setHide(isHide => ! isHide) }, [])Copy the code

The second argument to useCallback must be written. The eslint-plugin-react-hooks plugin fills in the dependencies automatically.

Send the request

Sending request is divided into operation type sending request and rendering type sending request.

Making an operational request

Operation-type request, as callback function:

return React.useMemo(() => {
  return (
    <div onClick={requestService.addList} />
  )
}, [requestService.addList])
Copy the code

Render the request

Rendering requests made in useAsync, such as refreshing a list page, retrieving basic information, or performing a search, can be abstracted to depend on variables that are recalculated as they change:

const { loading, error, value } = useAsync(async () => {
  return requestService.freshList(id);
}, [requestService.freshList, id]);
Copy the code

Intercomponent communication

Simple intercomponent communication uses transparent Props variables, while frequent intercomponent communication uses React. UseContext.

Taking a large complex component as an example, if the component has many modules split internally, but needs to share many internal states, the best practice is as follows:

Defines the share state within the component – store.ts

export const StoreContext = React.createContext<{ state: State; dispatch: React.Dispatch<Action>; }>(null) export interface State {}; export interface Action { type: 'xxx' } | { type: 'yyy' }; export const initState: State = {}; export const reducer: React.Reducer<State, Action> = (state, action) => { switch (action.type) { default: return state; }};Copy the code

The root component injects the shared state – main.ts

import { StoreContext, reducer, initState } from './store'

const AppProvider: React.FC = props => {
  const [state, dispatch] = React.useReducer(reducer, initState);

  return React.useMemo(() => (
    <StoreContext.Provider value={{ state, dispatch }}>
      <App />
    </StoreContext.Provider>
  ), [state, dispatch])
};
Copy the code

Any child component accesses/modifies the share state – child.ts

import { StoreContext } from './store'

const app: React.FC = () => {
  const { state, dispatch } = React.useContext(StoreContext);
  
  return React.useMemo(() => (
    <div>{state.name}</div>
  ), [state.name])
};
Copy the code

This is an example of how to share the Props of the root component. It is not appropriate to insert the Props of the root component into StoreContext.

const PropsContext = React.createContext<Props>(null)

const AppProvider: React.FC<Props> = props => {
  return React.useMemo(() => (
    <PropsContext.Provider value={props}>
      <App />
    </PropsContext.Provider>
  ), [props])
};
Copy the code

Integrate project data flow

See react-redux hooks.

Debounce optimization

For example, if the input field is frequently typed, debounce is used during onChange to keep the page flowing. In the Function Component realm, however, we have a more elegant way to implement it.

The problem with using debounce in the onChange Input component is that when the Input component is controlled, the value of debounce cannot be backfilled in time and cannot even be entered.

Let’s think about this in the Function Component mindset:

  1. React Scheduling optimizes the rendering priority with an intelligent scheduling system, so we don’t have to worry about performance issues caused by frequent state changes.
  2. If linkage a text still feel slow?onChangeThis is not slow, and most components that use values are not slow, and there is no need to use values fromonChangeFrom the beginningdebounce 。
  3. Find the component with the slowest rendering performance (such as the iframe component) and useDebounce any input that frequently causes it to render.

Here is a poorly performing component that references a text that changes frequently (possibly triggered by onChange) and uses useDebounce to slow it down:

const App: React.FC = ({ text }) = > {
  // No matter how fast text changes, textDebounce can change at most once every 1 second
  const textDebounce = useDebounce(text, 1000)
  
  return useMemo((a)= > {
    // a bunch of code that uses textDebounce but renders slowly
  }, [textDebounce])
};
Copy the code

Using textDebounce instead of Text keeps the render frequency within the range we specify.

UseEffect Precautions

In fact, useEffect is one of the weirdest hooks, and one of the most difficult to use. Take this code for example:

useEffect(() => {
  props.onChange(props.id)
}, [props.onChange, props.id])
Copy the code

If the ID changes, onChange is called. But if the upper layer code does not properly encapsulate onChange, causing the reference to change with each refresh, there can be serious consequences. Let’s assume the parent code is written like this:

class App {
  render() {
    return <Child id={this.state.id} onChange={id => this.setState({ id })} />
  }
}
Copy the code

This can lead to an endless loop. Although it looks like

just gives the opportunity to update the ID to the Child

, since the onChange function is regenerated on every render, the reference is always changing, and an infinite loop is created:

New onChange -> useEffect relies on updates -> props. OnChange -> Parent rerender -> new onChange…

To stop this loop, change onChange={this.handlechange}. UseEffect’s strict requirements on external dependencies only work elegantly if the entire project is careful to maintain correct references.

However, we have no control over what code is written where it is called, which leads to the possibility that non-standard parent elements can cause React Hooks to loop.

Therefore, when using useEffect, pay attention to the debugging context and check whether the parameter reference passed by the parent is correct. If the reference is passed incorrectly, there are two methods:

  1. UseDeepCompareEffect for deep comparisons of dependencies.
  2. useuseCurrentValueWrapper around props that reference always changing:
function useCurrentValue<T>(value: T): React.RefObject<T> {
  const ref = React.useRef(null);
  ref.current = value;
  return ref;
}

const App: React.FC = ({ onChange }) => {
  const onChangeCurrent = useCurrentValue(onChange)
};
Copy the code

The onChangeCurrent reference stays the same, but each time it points to the latest props. OnChange to get around this problem.

conclusion

If there is more to add, welcome to discuss at the end of the article.

For basic usage of Function Component or Hooks, see previous intensive reading:

  • Close reading of React Hooks
  • How to Build Wheels with React Hooks
  • Close reading complete guide to useEffect
  • Function Component Primer

The address for the discussion is: Close reading React Hooks Best Practices · Issue #202 · dt-fe/weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)