React coding standards

React coding standards

This article has participated in the Haowen Convening Order activity, click to view: Back-end, big front-end dual-track submissions, 20,000 yuan prize pool waiting for you to challenge!

Preface

The rules come from

  1. The rules provided by eslint-plugin-react select the parts that are beneficial to our current project;
  2. Discussion in the React community.

desire

  1. Each rule should have a detailed explanation, sample code, can intuitively see the role of the rule, and know the reason for using the rule;
  2. Each rule can be automatically checked using eslint;
  3. There is no need to list the rules that have not been violated at all in the existing projects.

JSX

[Mandatory] Components without child nodes use self-closing syntax (react/self-closing-comp)

Explanation:

JSX is different from HTML in that all elements can be self-closing.

//Bad <Foo></Foo> < div > </div > Good// < Foo/> < div/> copy the code

[Mandatory] Keep the opening and closing tags at the same level of indentation (react/jsx-wrap-multilines)

Explanation:

Code style.

//Bad class Message { render () { return < div > < span > Hello World </span > </div > ; } } //Good class Message { render () { return ( < div > < span > Hello World </span > </div > ; ); } } Copy code

[Mandatory] Add a space before the/> of the self-closing tag (react/jsx-space-before-closing)

Explanation:

Code style.

//Bad <Foo bar = "bar"/> < Foo bar = "bar" /> Good// < Foo bar = "bar"/> copy the code

API

[Mandatory] It is forbidden to write shouldComponentUpdate implementations for components inherited from PureComponent (react/no-redundant-should-component-update)

Explanation:

In the implementation of React, PureComponent does not directly implement shouldComponentUpdate, but adds a isReactPureComponent tag, and CompositeComponent implements related logic by identifying this tag. Therefore, customizing shouldComponentUpdate on PureComponent cannot enjoy the logical reuse of super.shouldComponentUpdate, and it will also make this inheritance relationship meaningless.

Related issue: github.com/facebook/re...

supplement:

  1. In order to avoid the performance overhead caused by the invalid render of the component, it is good to use PureComponet and memo, but please remember that they also have overhead, so please don't abuse them.

Related issue: github.com/facebook/re...

  1. Components wrapped with the connect method in react-redux do not need to inherit PureComponent or use the memo method.

Related code: github.com/reduxjs/rea...

  1. You can use the Highlight updates when components render setting item in the React Developer Tools to observe the rendering of the components.

[Mandatory] It is forbidden to use String type Refs (react/no-string-refs)

Explanation:

It is obsolete and may be removed in a future version of React.

supplement:

Related issue: github.com/facebook/re...

Problems with String type Refs:

  1. It requires React to keep track of the currently rendered components, which will cause React to execute slightly slower.
  2. It cannot use the "render callback" mode as most people expect.
class MyComponent extends Component { renderRow = ( index ) => { //This will not work, ref will be added to DataTable instead of MyComponent: return < input ref = { ' input- ' + index }/> ; //This would work though! Callback refs are awesome. return < input ref = {input => this['input-' + index] = input}/> ; } render () { return < DataTable data = {this.props.data} renderRow = {this.renderRow}/> } } Copy code
  1. It is not composable, that is, if the library puts a ref on the passed child object, the user cannot put another ref. Callback references are composable.

[Mandatory] Avoid using unsafe life cycle functions componentWillMount, componentWillReceiveProps, componentWillUpdate (react/no-unsafe)

Explanation:

The above life cycle functions are officially regarded as unsafe by React. The company's react specification recommends using constructor instead of componentWillMount. For specific life cycle migration, refer to "Migrating Outdated Life Cycle".

Remarks:

React officially considers the above life cycle unsafe reason: For the future feature of Concurrent mode (experimental), it is very important to avoid side effects in life cycle hooks such as willMount/willUpdate. Because React s current rendering is divided into two phases: reconcile and commit, the reconcile phase can be repeatedly executed by high-weight user events. Since the above life cycle functions are called in this phase, these life cycle functions are called repeatedly. may.

React RFC 0006-static-lifecycle-methods: github.com/reactjs/rfc...

React v16.9.0 update log: react.docschina.org/blog/2019/0...

Migrate outdated life cycle: react.docschina.org/blog/2018/0...

Concurrent mode introduction (experimental): react.docschina.org/docs/concur...

(: [__] Dan Bao's answer on stackoverflow-In React, should I make the initial network request in componentWillMount or componentDidMount? stackoverflow.com/a/41612993

[Mandatory] It is forbidden to use array index as key (react/no-array-index-key)

Explanation:

React uses keys to determine which elements have been changed, added, or deleted.

supplement:

Let me say more, I think someone must have such a misunderstanding of the key as I did before. The purpose of the key is for better performance. No, this is not true.

The main purpose of key is to serve as a unique identifier. It is very important to correctly determine the status of element change, addition, or deletion during the fiber diff phase, especially for components that have their own status.

Under this premise, for components whose properties have not changed, only the implementation of the shouldComponentUpdate/memo method can avoid repeated rendering.

Related issue: github.com/facebook/re...

Understanding the key prop: stackoverflow.com/questions/2...

//Bad function Posts () { const [list, setList] = useState([]); useEffect( () => { fetchPosts().then(setList); }, []); return list.map( ( post, index ) => < input key = {index} defaultValue = {post.title}/> ); } //Good function Posts () { const [list, setList] = useState([]); useEffect( () => { fetchPosts().then(setList); }, []); return list.map( post => < input key = {post.id} defaultValue = {post.title}/> ); } //Good-If there is no unique identifier such as id, the front end can actively generate a unique identifier function Posts () { const [list, setList] = useState([]); useEffect( () => { fetchPosts().then( list => { for ( const post of list) { post.id = SomeLibrary.generateUniqueID(); } setList(list); }); }, []); return list.map( post => < input key = {post.id} defaultValue = {post.title}/> ); } Copy code

[Recommendation] Avoid directly using object and function expressions in JSX attribute values (react/jsx-no-bind)

Explanation:

PureComponent uses shallowEqual to compare props and state to determine whether rendering is required, and the use of objects and function expressions in JSX attribute values will cause the object references to be different each time, so shallowEqual will return false, leading to unnecessary rendering.

supplement:

To avoid refreshing of sub-components in function components, hooks such as useCallback and useMemo can be used to keep the attribute references of functions and object types unchanged.

//Bad class WarnButton { alertMessage ( message ) { alrt(message); } render () { return < button type = "button" onClick = {() => this.alertMessage(this.props.message)}>Reminder </button > } } //Good class WarnButton { @bind() alertMessage () { alrt( this .props.message); } render () { return < button type = "button" onClick = {this.alertMessage} > Prompt </button > } } //Bad function Foo () { async function handleOk () { try { await fetch(); } catch (error) { message.error( 'error' ); return ; } message.success( 'success' ); } return < Modal onOk = {handleOk}/> ; } //Good function Foo () { const handleOk = useCallback( async () => { try { await fetch(); } catch (error) { message.error( 'error' ); return ; } message.success( 'success' ); }, []); return < Modal onOk = {handleOk}/> ; } Copy code

[Recommendation] It is forbidden to use defaultProps on functional components (react/require-default-props, ignoreFunctionalComponents: true)

Explanation:

defaultProps is very useful in classes because the props object is passed to many different methods, such as life cycle, callbacks, etc. Each has its own scope. This makes it difficult to use JS default parameters, because you must repeatedly determine the same default value in each function.

class Foo { static defaultProps = { foo : 1 }; componentDidMount () { let foo = this .props.foo; console .log(foo); } componentDidUpdate () { let foo = this .props.foo; console .log(foo); } componentWillUnmount () { let foo = this .props.foo; console .log(foo); } handleClick = () => { let foo = this .props.foo; console .log(foo); } render () { let foo = this .props.foo; console .log(foo); return < div onClick = {this.handleClick}/> ; } } Copy code

However, in functional components, this mode is actually not needed, because you can only use JS default parameters, and usually all of these values are used in the same scope.

function Foo ( {foo = 1 } ) { useEffect( () => { console .log(foo); return () => { console .log(foo); }; }); let handleClick = () => { console .log(foo); }; console .log(foo); return < div onClick = {handleClick}/> ; } Copy code

The future plan of the React team is when there is no

.prototype.isReactComponent
Used on the components
defaultProps
When, createElement will issue a warning. This includes those special components such as
forwardRef
with
memo
.

If the props are passed as a whole, then the upgrade will become difficult, but you can always refactor it when needed.

function Foo({foo = 1, bar = "hello"}) {let props = {foo, bar};//...} Supplement:

React RFC 0000-create-element-changes: 0000-create-element-changes.md/ github.com/reactjs/rfc...

[Note] In single-page applications, avoid using location.href ='url to jump' to jump inside the application

Explanation:

location.href = 'url to jump' will cause the page to refresh and re-request for static resources. //Bad function Foo () { const handleClick = useCallback( () => { location.href = '/path/to/jump' ; }); return < div onClick = {handleClick} > click </div > ; } //Good function Foo () { const history = useHistory(); const handleClick = useCallback( () => { //Use its history object in react-router to jump history.push( '/path/to/jump' ); }); return < div onClick = {handleClick} > click </div > ; } Copy code