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:
- with
React.FC
Declare the Function Component Component type and define the Props parameter type. - with
React.useMemo
Optimize rendering performance. - with
App.defaultProps
Define 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:
- 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.
- If linkage a text still feel slow?
onChange
This is not slow, and most components that use values are not slow, and there is no need to use values fromonChange
From the beginningdebounce
。 - 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:
- UseDeepCompareEffect for deep comparisons of dependencies.
- use
useCurrentValue
Wrapper 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)