brief introduction
React 16.8 was officially released on February 2019. It is a feature that can improve code quality and development efficiency. The author will first list some practical points for further discussion.
However, it should be understood that there is no perfect best practice specification. For an efficient team, stable specifications are more important than reasonable specifications, so this scheme is only one of the best practices.
intensive reading
Environmental requirements
- Have a stable front-end team that understands functional programming.
- Open the ESLint plug-in: eslint-plugin-react-hooks.
Component definition
Function Component is defined by const + arrow function:
const App: React.FC<{ title: string }> = ({ title }) => { return React.useMemo(() => <div>{title}</div>, [title]); }; App.defaultProps = { title: 'Function Component' }
The above examples include:
- Use react FC declares the Function Component component type and defines the Props parameter type.
- Use react Usememo optimizes rendering performance.
- Use app Defaultprops defines the default value of Props.
FAQ
Why not use react memo?
It is recommended to use react Usememo instead of react Memo, because react exists during component communication The usage of usecontext, which will re render all the components used, only react Usememo can handle on-demand rendering of such scenes.
Should useMemo also be used for components without performance problems?
Consider that when maintaining this component in the future, some data may be injected through useContext at any time. At this time, who will think of adding useMemo?
Why not use deconstruction instead of defaultProps?
Although writing defaultProps by deconstruction is more elegant, there is a hard injury: for object types, the reference will change every time you Rerender, which will cause performance problems, so don't do so.
Local state
There are three kinds of local states, which are arranged according to the degree of common use: useState useRef useReducer.
useState
const [hide, setHide] = React.useState(false); const [name, setName] = React.useState('BI');
The name of the state function shall be ideographic and shall be declared together as far as possible for easy reference.
useRef
const dom = React.useRef(null);
useRef should be used as little as possible. A large amount of Mutable data will affect the maintainability of the code.
However, for objects that do not need repeated initialization, useRef storage is recommended, such as new G2().
useReducer
useReducer is not recommended for local state, which will make the internal state of the function too complex and difficult to read. useReducer is recommended to be used together with useContext when communicating among multiple components.
FAQ
Can I declare ordinary constants or ordinary functions directly in a function?
No, the Function Component will be re executed every time it is rendered. It is recommended to put constants on the outer layer of the function to avoid performance problems. It is recommended to use useCallback declaration for functions.
function
All functions in Function Component must use react Usecallback package to ensure accuracy and performance.
const [hide, setHide] = React.useState(false); const handleClick = React.useCallback(() => { setHide(isHide => !isHide) }, [])
The second parameter of useCallback must be written, eslint-plugin-react-hooks The plug-in automatically fills in dependencies.
Send request
Sending requests are divided into operational sending requests and rendering sending requests.
Operational send request
An operation type sends a request as a callback function:
return React.useMemo(() => { return ( <div onClick={requestService.addList} /> ) }, [requestService.addList])
Render send request
Rendering requests made in useAsync, such as refreshing the list page, obtaining basic information, or searching, can be abstracted as relying on some variables. When these variables change, the data should be retrieved again:
const { loading, error, value } = useAsync(async () => { return requestService.freshList(id); }, [requestService.freshList, id]);
Communication between components
Simple inter component communication uses transparent propagation of Props variables, while frequent inter component communication uses react useContext .
Take a large complex component as an example. If many modules are split inside the component, but many internal states need to be shared, the best practices are as follows:
Define sharing status within components - 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; } };
Root component injection 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]) };
Any subcomponent accessing / modifying shared 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]) };
The above has solved the problem of convenient sharing of state among multiple closely related component modules, but sometimes the problem of sharing root component Props is encountered. This unmodifiable state is not suitable to be stuffed into StoreContext. We create a PropsContext to inject Props into the root component:
const PropsContext = React.createContext<Props>(null) const AppProvider: React.FC<Props> = props => { return React.useMemo(() => ( <PropsContext.Provider value={props}> <App /> </PropsContext.Provider> ), [props]) };
Combined with project data flow
reference resources react-redux hooks.
debounce optimization
For example, when the input box is frequently entered, we will choose to debounce when onChange in order to ensure the smoothness of the page. However, in the field of Function Component, we have a more elegant way to implement it.
In fact, there is a problem with using debounce in onChange of the Input component, that is, when the Input component is controlled, the debounce value cannot be backfilled in time, resulting in the problem that it can not even be Input.
We think about this problem in the thinking mode of Function Component:
- React scheduling By optimizing the rendering priority through the intelligent scheduling system, we don't have to worry about performance problems caused by frequent state changes.
- Do you still feel slow if you link a text? onChange is not slow, and most components that use values are not slow. There is no need to debounce from the source of onChange.
- Find the component with the slowest rendering performance (such as iframe component), and use debounce some input parameters that frequently cause its rendering.
The following is a component with poor performance. It refers to text that changes frequently (this text may be triggered by onChange). We can use useDebounce to slow down the change frequency:
const App: React.FC = ({ text }) => { // No matter how fast text changes, textDebounce can be modified once a second at most const textDebounce = useDebounce(text, 1000) return useMemo(() => { // A pile of code that uses textDebounce but renders slowly }, [textDebounce]) };
Using textDebounce instead of text can control the rendering frequency within the range we specify.
useEffect considerations
In fact, useEffect is the weirdest Hook and the most difficult to use. For example, the following code:
useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id])
If the id changes, onChange is called. However, if the upper layer code does not reasonably encapsulate onChange, resulting in changes every time the reference is refreshed, serious consequences will occur. Let's assume that the parent code is written as follows:
class App { render() { return <Child id={this.state.id} onChange={id => this.setState({ id })} /> } }
This can lead to an endless cycle. Although it seems that < app > only gives the time to update the id to the child element < child >, because the onChange function will be regenerated every time it is rendered, the reference is always changing, and an infinite loop will appear:
New onChange - > useeffect dependency update - > props onChange - > parent re render - > New onChange
To prevent this cycle, just change to onChange={this.handleChange}. useEffect has strict requirements for external dependence. It can take effect only when the whole project pays attention to maintaining correct references.
However, how to write the code at the called place is not under our control, which leads to non-standard parent elements, which may lead to an endless loop in React Hooks.
Therefore, when using useEffect, pay attention to the debugging context and whether the parameter reference passed by the parent is correct. If the reference is not passed correctly, there are two methods:
- use useDeepCompareEffect Deep comparison of dependencies.
- Wrap props whose references always change with useCurrentValue:
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) };
The reference to onChangeCurrent remains unchanged, but it always points to the latest props Onchange, which can avoid this problem.