剛接觸 React
的時候,在一個又一個的教程上面看到不少種編寫組件的方法,儘管那時候 React
框架已經至關成熟,可是並無一個固定的規則去規範咱們去寫代碼。javascript
在過去的一年裏,咱們在不斷的完善咱們的作法,直到滿意爲止。css
本文會列出咱們本身在使用的最佳實踐,無論你是剛入門的新手仍是頗有經驗的開發者,咱們都但願本文對你有所幫助。java
開始以前,先列幾條:react
基於 Class 的組件是有狀態的,無論它包不包含函數,咱們都會盡可能少用。可是它也有它的用處。git
如今來一行一行的編寫咱們的組件:github
import React, { Component } from 'react' import { observer } from 'mobx-react' import ExpandableForm from './ExpandableForm' import './styles/ProfileContainer.css'
我喜歡 CSS in Javascript
,可是這個概念還比較新,如今也並無成熟的解決方案,因此咱們在每一個組件裏面去引用 CSStypescript
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 }
固然你也能夠選擇在構造函數裏面去初始化,可是咱們以爲這種方式更加清晰。閉包
固然也會保證 Class 是默認導出的。app
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 和 defaultProps 是靜態屬性,儘量的把它們寫在組件的最上方,以便其餘開發者閱讀。框架
若是使用 React 15.3.0
或更高的版本,使用 prop-types 代替 React.PropTypes
全部的組件都必須聲明 propTypes
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 }) }
使用基於 Class 的組件時,當你傳遞函數給子組件的時候,要確保他們有正確的 this
,一般用這種形式實現 this.handleSubmit.bind(this)
可是若是你使用箭頭函數,就不須要 bind(this)
上面的例子中咱們是這麼作的:
this.setState({ expanded: !this.state.expanded })
這裏有個 setState 的小知識 —— 它是異步的,爲了保證性能,React
會分批修改 state,因此 state 不會在調用 setState 以後當即改變
這意味着你不能依賴當前的狀態,由於你不知道當前的狀態是什麼狀態
這裏有個解決方案 —— 傳遞函數給 setState,React
會把上一個狀態 prevState
傳遞給你
this.setState(prevState => ({ expanded: !prevState.expanded }))
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> ) } }
像上面的例子同樣,每一個 prop 都獨佔一行
@observer export default class ProfileContainer extends Component {
若是你使用了相似 mobx 的庫,你能夠這樣去裝飾你的 Class 組件
修改函數式組件使用 decorators 很靈活而且可讀
若是你不想使用裝飾器,能夠這麼作:
class ProfileContainer extends Component { // Component code } export default observer(ProfileContainer)
避免像下面註釋的地方同樣傳遞新的閉包給子組件:
<input type="text" value={model.name} // onChange={(e) => { model.name = e.target.value }} // ^ Not this. Use the below: onChange={this.handleChange} placeholder="Your Name"/>
這種方式的好處是每次render,不會從新建立一個函數,沒有額外的性能損失。
這裏是完整的組件:
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> ) } }
這些組件沒有狀態和函數,他們很純,很是容易閱讀,儘可能多的使用他們。
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
這裏咱們把 propTypes 寫在最前面,他會被組件當即可見,這要歸功於JavaScript的 函數提高
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> ) }
咱們的組件是一個函數,咱們獲取他的 props 就是在獲取函數的參數值,咱們能夠直接用 ES6
的解構:
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> ) }
咱們也可使用默認參數值去設置 defaultProps
,就像上面的 expanded = false
避免使用下面的 ES6 語法:
const ExpandableForm = ({ onExpand, expanded, children }) => {
看起來很先(逼)進(格),但這個函數是匿名的。
若是你的Babel設置正確,這個匿名函數不會成爲一個問題 —— 可是若是不是的話,任何錯誤都會顯示在 << anonymous >>
中,這對於調試來講是很是糟糕的。
函數式組件中不能使用 decorators
,你只需把它做爲參數傳遞給過去
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)
這裏是完整的組件:
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-control-statements),可是咱們沒有引入其餘的庫,咱們是這麼解決的:
咱們使用了 當即執行函數 把條件語句寫在裏面,雖然這樣可能會致使性能降低,可是在大多數狀況下,它帶來的負面影響仍是小於糟糕的可讀性。
固然若是組件分的足夠細,你可能不會用到這麼複雜的條件判斷。
此外,若是你只在一個表達式裏面去渲染組件,避免這麼作:
{ isTrue ? <p>True!</p> : <none/> }
你可使用短路語法:
{ isTrue && <p>True!</p> }
這篇文章對你有幫助嗎?請在評論區給出你的意見和建議,感謝閱讀!
原文連接,翻譯並首發於個人我的博客,推薦下我前陣子寫的一個 React 腳手架 parcel-typescript-react-boilerplate,請給出意見和建議,相互學習。無恥的求個星,謝謝~~!