当前位置:网站首页>Our best practices for writing react components

Our best practices for writing react components

2020-11-06 01:23:01 :::::::

Rigid contact React When , In one tutorial after another, I saw many functions for writing components , Even then React The framework is quite mature , But there is no fixed rule for us to write code .

In the past year , We are constantly improving our approach , Until you are satisfied .

This article will list our own best practices in use , Whether you're a beginner or an experienced developer , We all hope this article will help you .

Before the start , Here are a few :

  • We use ES6/ES7
  • If you can't tell a page component from a container component , Recommended reading This article
  • If there is a better opinion or suggestion , Please tell me in the comments area , thank you

be based on Class The components of

be based on Class The components of are stateful , Whether it contains no functions , We all use as little as possible . But it also has its uses .

Now let's write our components line by line :

Import CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

I like CSS in Javascript, But the concept is still relatively new , There is no mature solution now , So we refer to... In each component CSS

initialization 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 }

Of course, you can also choose to initialize in the constructor , But we think this way is clearer .

Of course, it will guarantee that Class It is exported by default .

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 and defaultProps It's a static property , Write them at the top of the component as much as possible , For other developers to read .

If you use React 15.3.0 Or later , Use prop-types Instead of React.PropTypes

All components must declare propTypes

function

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 })
  }

Using a Class Components of , When you pass functions to subcomponents , Make sure they have the right one this, It is usually implemented in this form this.handleSubmit.bind(this)

But if you use the arrow function , There is no need to bind(this)

by setState Transfer function

This is what we did in the example above :

this.setState({ expanded: !this.state.expanded })

Here is a setState Little knowledge —— It's asynchronous , To guarantee performance ,React It will be modified in batches state, therefore state It won't be calling setState Immediately after that

This means you can't rely on the current state , Because you don't know what the current state is

Here's a solution —— Transfer function to setState,React Will put the last state prevState Pass it on to you

this.setState(prevState => ({ expanded: !prevState.expanded }))

deconstruction Props

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>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}

Like the example above , Every prop All in one line

Decorator (Decorators)

@observer
export default class ProfileContainer extends Component {

If you use something like mobx The library of , You can decorate your Class Components

Modify functional components to use decorators It's flexible and readable

If you don't want to use decorators , You can do that :

class ProfileContainer extends Component {
  // Component code
}
export default observer(ProfileContainer)

Closure

Avoid passing new closures to subcomponents as noted below :

    <input
      type="text"
      value={model.name}
      // onChange={(e) => { model.name = e.target.value }}
      // ^ Not this. Use the below:
      onChange={this.handleChange}
      placeholder="Your Name"/>

The advantage of this approach is that every time render, It doesn't recreate a function , No additional performance loss .

Here's the complete component :

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
// Separate local imports from dependencies
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

// Use decorators if needed
@observer
export 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>
          <h1>{title}</h1>
          <input
            type="text"
            value={model.name}
            // onChange={(e) => { model.name = e.target.value }}
            // Avoid creating new closures in the render method- use methods like below
            onChange={this.handleNameChange}
            placeholder="Your Name"/>
        </div>
      </ExpandableForm>
    )
  }
}

Functional component

These components have no states and functions , They are pure , It's very easy to read , Use them as much as possible .

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

Here we put propTypes Let me write it first , It will be immediately visible to the component , This is due to JavaScript Of Function enhancement

deconstruction 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 function , We got his props Is to get the parameter value of the function , We can use it directly ES6 Deconstruction of :

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>
  )
}

We can also use default parameter values to set defaultProps, Just like up here expanded = false

Avoid using the following ES6 grammar :

const ExpandableForm = ({ onExpand, expanded, children }) => {

It seems to be very early ( forced ) Into the ( grid ), But this function is anonymous .

If your Babel Set up correctly , This anonymous function won't be a problem —— But if not , Any errors will be displayed in << anonymous >> in , This is very bad for debugging .

Wrapping

Cannot be used in functional components decorators, You just pass it to the past as an argument

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)

Here's the complete component :

import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
// Separate local imports from dependencies
import './styles/Form.css'

// Declare propTypes here, before the component (taking advantage of JS function hoisting)
// You want these to be as visible as possible
ExpandableForm.propTypes = {
  onSubmit: func.isRequired,
  expanded: bool,
  onExpand: func.isRequired
}

// Destructure props like so, and use default arguments as a way of setting defaultProps
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>
  )
}

// Wrap the component instead of decorating it
export default observer(ExpandableForm)

JSX Condition judgment in

You may have complex conditional statements , But you should avoid the following :

Nested ternary expressions are not a good way , It's so hard to read

There are libraries that can solve this problem (jsx-control-statements), But we didn't introduce any other libraries , This is how we solved it :

We used Immediate execution function Write the conditional sentence in it , Although this may lead to performance degradation , But in most cases , Its negative impact is still less than poor readability .

Of course, if the components are detailed enough , You may not use such complex conditional judgments .

Besides , If you only render components in one expression , Avoid this :

True!

: } title="" data-original-title=" Copy ">

{
  isTrue
   ? <p>True!</p>
   : <none/>
}

You can use short circuit syntax :

True!

} " title="" data-original-title=" Copy ">

{
  isTrue && 
    <p>True!</p>
}

summary

Does this article help you ? Please give your comments and suggestions in the comments section , Thank you for reading !

This article starts with my Personal blog , I also recommend a scaffold I wrote a while ago parcel-typescript-react-boilerplate, Please give your comments and suggestions , Learn from each other . Shamelessly ask for a star , thank you ~~!

Participation of this paper Tencent cloud media sharing plan , You are welcome to join us , share .

版权声明
本文为[:::::::]所创,转载请带上原文链接,感谢