Compile React component project practice analysis and react component project
When I first started writing React, I saw many methods for writing components. There are one hundred writing methods in one hundred tutorials. Although React itself is mature, there seems to be no "correct" way to use it. So I (the author) summarized the React usage experience summarized by our team over the years here. I hope this article will be useful to you, whether you are a beginner or veteran.
Before:
We use ES6 and ES7 syntax. If you are not clear about the differences between components and container components, we recommend that you leave a class-based component in the comments if you have any suggestions or questions.
Currently, development of React components generally uses class-based components. Next we will write our components in the same line:
import React, { Component } from 'react';import { observer } from 'mobx-react';import ExpandableForm from './ExpandableForm';import './styles/ProfileContainer.css';
I like css in javascript very much. However, the style Writing Method is too new. So we introduce css files in each component. In addition, the import introduced locally and the global import will be separated by a blank line.
Initialize State
import React, { Component } from 'react'import { observer } from 'mobx-react'import ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'export default class ProfileContainer extends Component { state = { expanded: false }
You can use the old method in
constructor
In Initialization
state
. For more information, see here. But we chose a clearer method.
At the same time, make sure that
export default
. (Note: although this is not necessarily true when redux is used ).
PropTypes and defaultProps
import React, { Component } from 'react'import { observer } from 'mobx-react'import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'export default class ProfileContainer extends Component { state = { expanded: false } static propTypes = { model: object.isRequired, title: string } static defaultProps = { model: { id: 0 }, title: 'Your Name' } // ...}
propTypes
AnddefaultProps
Is a static property. Define the component class as much as possible, so that other developers can immediately notice when reading the code. They can serve as documents.
If you use React 15.3.0 or later, you need to introduceprop-types
Package, instead of usingReact.PropTypes
. For more information, see here.
All your components should have prop types.
Method
import React, { Component } from 'react'import { observer } from 'mobx-react'import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'export default class ProfileContainer extends Component { state = { expanded: false } static propTypes = { model: object.isRequired, title: string } static defaultProps = { model: { id: 0 }, title: 'Your Name' } handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState({ expanded: !this.state.expanded }) } // ...}
In Class components, when you pass methods to sub-components, you must ensure that they are used correctly when called. This is usually done when it is passed to the child component:this.handleSubmit.bind(this)
.
The ES6 arrow method is much simpler. It automatically maintains the correct context (this
).
Pass a method to setState
In the above example, there is such a line:
this.setState({ expanded: !this.state.expanded });
setState
It is actually asynchronous! To improve performance, React
setState
Together. Therefore,
setState
After that, the state may not change immediately.
ThereforesetState
You cannot rely on the current state value. Because I didn't know that it would be a magic horse.
Solution:setState
Input a method and pass the state value before the call as a parameter to this method. Let's look at the example:
this.setState(prevState => ({ expanded: !prevState.expanded }))
Thanks to Austin Wood for help.
Disassembling Components
import React, { Component } from 'react'import { observer } from 'mobx-react'import { string, object } from 'prop-types'import ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'export default class ProfileContainer extends Component { state = { expanded: false } static propTypes = { model: object.isRequired, title: string } static defaultProps = { model: { id: 0 }, title: 'Your Name' } handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState(prevState => ({ expanded: !prevState.expanded })) } render() { const { model, title } = this.props return ( <ExpandableForm onSubmit={this.handleSubmit} expanded={this.state.expanded} onExpand={this.handleExpand}> <div>
With multiple rowsprops
, Each prop should occupy a single row. As in the preceding example. The best way to achieve this goal is to use a set of tools:Prettier
.
Decorator)
@observerexport default class ProfileContainer extends Component {
If you know some databases, suchMobxYou can use the previous example to modify Class components. The decorator imports a Class component as a parameter into a method.
The decorator can write more flexible and readable components. If you do not want to use a decorator, you can:
class ProfileContainer extends Component { // Component code}export default observer(ProfileContainer)
Closure
Do not include closures in child components as much as possible, such:
<input type="text" value={model.name} // onChange={(e) => { model.name = e.target.value }} // ^ Not this. Use the below: onChange={this.handleChange} placeholder="Your Name"/>
NOTE: If
input
If it is a React component, this will automatically trigger its re-painting, regardless of whether other props has changed.
Consistency check is the most resource-consuming part of React. Do not add additional work here. The best way to handle the problem in the previous example is to pass in a class method, which is easier to read and debug. For example:
import React, { Component } from 'react'import { observer } from 'mobx-react'import { string, object } from 'prop-types'// Separate local imports from dependenciesimport ExpandableForm from './ExpandableForm'import './styles/ProfileContainer.css'// Use decorators if needed@observerexport default class ProfileContainer extends Component { state = { expanded: false } // Initialize state here (ES7) or in a constructor method (ES6) // Declare propTypes as static properties as early as possible static propTypes = { model: object.isRequired, title: string } // Default props below propTypes static defaultProps = { model: { id: 0 }, title: 'Your Name' } // Use fat arrow functions for methods to preserve context (this will thus be the component instance) handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.name = e.target.value } handleExpand = (e) => { e.preventDefault() this.setState(prevState => ({ expanded: !prevState.expanded })) } render() { // Destructure props for readability const { model, title } = this.props return ( <ExpandableForm onSubmit={this.handleSubmit} expanded={this.state.expanded} onExpand={this.handleExpand}> // Newline props if there are more than two <div>
Method component
This type of component has no state, no props, and no method. They are pure components and contain the least changed content. They are often used.
PropTypes
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = { onSubmit: func.isRequired, expanded: bool}// Component declaration
We defined it before the component declaration.propTypes
.
Decompose Props and defaultProps
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = { onSubmit: func.isRequired, expanded: bool, onExpand: func.isRequired}function ExpandableForm(props) { const formStyle = props.expanded ? {height: 'auto'} : {height: 0} return ( <form style={formStyle} onSubmit={props.onSubmit}> {props.children} <button onClick={props.onExpand}>Expand</button> </form> )}
Our component is a method. Its parameter isprops
. We can expand this component as follows:
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = { onSubmit: func.isRequired, expanded: bool, onExpand: func.isRequired}function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) { const formStyle = expanded ? {height: 'auto'} : {height: 0} return ( <form style={formStyle} onSubmit={onSubmit}> {children} <button onClick={onExpand}>Expand</button> </form> )}
Now we can also use the default parameters to assume the role of the default props, which is quite readable. Ifexpanded
It is not defined, so we set itfalse
.
However, try to avoid using the following example:
const ExpandableForm = ({ onExpand, expanded, children }) => {
It looks modern, but this method is not named.
If your Babel configuration is correct, the unnamed method will not be a big problem. However, if Babel has a problem, any errors in this component are displayed as <>.
The anonymous method also causes other Jest problems. It can cause various hard-to-understand problems and has no practical advantages. We recommend that you usefunction
, Less useconst
.
Decoration method component
Since method components cannot use decorator, they can only be passed as parameters to other methods.
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'import './styles/Form.css'ExpandableForm.propTypes = { onSubmit: func.isRequired, expanded: bool, onExpand: func.isRequired}function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) { const formStyle = expanded ? {height: 'auto'} : {height: 0} return ( <form style={formStyle} onSubmit={onSubmit}> {children} <button onClick={onExpand}>Expand</button> </form> )}export default observer(ExpandableForm)
You can only do this:export default observer(ExpandableForm)
.
This is all the code of the component:
import React from 'react'import { observer } from 'mobx-react'import { func, bool } from 'prop-types'// Separate local imports from dependenciesimport './styles/Form.css'// Declare propTypes here, before the component (taking advantage of JS function hoisting)// You want these to be as visible as possibleExpandableForm.propTypes = { onSubmit: func.isRequired, expanded: bool, onExpand: func.isRequired}// Destructure props like so, and use default arguments as a way of setting defaultPropsfunction ExpandableForm({ onExpand, expanded = false, children, onSubmit }) { const formStyle = expanded ? { height: 'auto' } : { height: 0 } return ( <form style={formStyle} onSubmit={onSubmit}> {children} <button onClick={onExpand}>Expand</button> </form> )}// Wrap the component instead of decorating itexport default observer(ExpandableForm)
Condition judgment
In some cases, you may make many conditional judgments:
<div id="lb-footer"> {props.downloadMode && currentImage && !currentImage.video && currentImage.blogText ? !currentImage.submitted && !currentImage.posted ? <p>Please contact us for content usage</p> : currentImage && currentImage.selected ? <button onClick={props.onSelectImage} className="btn btn-selected">Deselect</button> : currentImage && currentImage.submitted ? <button className="btn btn-submitted" disabled>Submitted</button> : currentImage && currentImage.posted ? <button className="btn btn-posted" disabled>Posted</button> : <button onClick={props.onSelectImage} className="btn btn-unselected">Select post</button> }</div>
Such multi-layer condition judgment is not a good phenomenon.
A third-party library JSX-Control Statements can solve this problem. However, instead of adding a dependency, it is better to solve the problem as follows:
<div id="lb-footer"> { (() => { if(downloadMode && !videoSrc) { if(isApproved && isPosted) { return <p>Right click image and select "Save Image As.." to download</p> } else { return <p>Please contact us for content usage</p> } } // ... })() }</div>
Use IIFE wrapped in braces, and thenif
All expressions are included. Returns the component you want to return.
Last
Again, I hope this article will be useful to you. If you have any good comments or suggestions, please write them in the following comments. Thank you!