編寫React組件的最佳實踐
在我第一次編寫 React 代碼的時候,我見發現許多不同的方法可以用來編寫組件,不同教程教授的內容也大不相同。盡管從那時候起框架已經相當成熟,但并沒有一種固定的“正確”方式指導。
在 MuseFind 工作的一年里,我們的團隊編寫了許多 React 組件,后期我們對方法進行了優化直到滿意為止。
本指南描述了我們推薦的最佳實踐,不管你是一名初學者還是有經驗的老手,希望它能對你有所幫助。
在我們開始之前,有幾個地方要注意一下:
-
我們使用的是 ES6 和 ES7 的語法。
-
如果你對于現實和容器組件兩者之間的區別不甚明了,建議首先閱讀一下這個。
-
如果有任何建議、疑問或者感想,請通過評論來讓我們知曉。
基于類的組件
基于類的組件具有豐富狀態而且可以含有方法。我們要盡可能有節制地去使用,因為它們也有特定的適用場合。
讓我們使用一行一行的代碼逐步地將我們的組件構建起來吧。
引入 CSS
import React, {Component} from 'react'
import {observer} from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
我喜歡在 JavaScript 中操作 CSS,這在理論上這樣做是可行的。不過它仍然是一種新的創意,還沒出現切實可行的解決方案。不過在此之前,我們可以先為每一個組件引入一個 CSS 文件。
我們也通過另寫一行將依賴引入從本地引入獨立了出來。
狀態初始化
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 }
propTypes 和 defaultProps
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 }
static propTypes = {
model: React.PropTypes.object.isRequired,
title: React.PropTypes.string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
propTypes 和 defaultProps 是靜態屬性,要在組件代碼中盡可能高的位置進行聲明。它們作為文檔放在醒目的位置,其他開發者閱讀此文件時能立即看到。
所有的組件都應該有 propTypes。
方法
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 }
static propTypes = {
model: React.PropTypes.object.isRequired,
title: React.PropTypes.string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}
handleNameChange = (e) => {
this.props.model.name = e.target.value
}
handleExpand = (e) => {
e.preventDefault()
this.setState({ expanded: !this.state.expanded })
}
有了類組件,在你想子組件傳遞方法時,就得去確認它們在被調用到時所持有的 this 對象是正確的。這個一般可以通過將 this.handleSubmit.bind(this) 傳遞給子組件來達成。
我們認為這種方式更加干凈且容易,借助 ES6 的箭頭函數可以自動地維護好正確的上線文。
屬性析構
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 }
static propTypes = {
model: React.PropTypes.object.isRequired,
title: React.PropTypes.string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
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() {
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> ) } }
擁有許多屬性的組件要讓每個屬性都另起一行,如上所示。
裝飾器
@observer
export default class ProfileContainer extends Component {
如果你使用了一些像 mobx 的東西,就可以像上面這樣對類組件進行裝飾?—?這樣做跟將組件傳遞給一個函數是一樣的效果。
裝飾器是一種用來修改組件功能的靈活且可讀性好的方式。我們對其進行了廣泛的運用,包括 mobx 還有我們自己的 mobx-models 庫。
如果你不想使用裝飾器,可以這樣做:
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"/>
原因: 每次父組件渲染時,都會有一個新的函數被創建并傳遞給輸入。
如果輸入是一個 React 組件,不管它的其它屬性實際是否已經發生了變化,都會自動地觸發讓它重新渲染。
調和是 React 中消耗最昂貴的部分,因此不要讓它的計算難度超過所需! 另外,傳遞一個類方法更容易閱讀、調試和修改。
如下是完整的組件代碼:
import React, {Component} from 'react'
import {observer} from 'mobx-react'
// 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: React.PropTypes.object.isRequired,
title: React.PropTypes.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> ) } }
函數式組件
這些組件沒有狀態和方法。它們就是單純的組件,容易理解。要盡可能常去使用它們。
propTypes
import React from 'react'
import {observer} from 'mobx-react'
import './styles/Form.css'
const expandableFormRequiredProps = {
onSubmit: React.PropTypes.func.isRequired,
expanded: React.PropTypes.bool
}
// Component declaration
ExpandableForm.propTypes = expandableFormRequiredProps
這里,我們將 propTypes 分配給了頂部一行的變量。在組件聲明的下面,我們對它們進行了正常的分配。
對 Props 和 defaultProps 進行析構
import React from 'react'
import {observer} from 'mobx-react'
import './styles/Form.css'
const expandableFormRequiredProps = {
onSubmit: React.PropTypes.func.isRequired,
expanded: React.PropTypes.bool
}
function ExpandableForm(props) {
return (
<form style={props.expanded ? {height: 'auto'} : {height: 0}}> {props.children} <button onClick={props.onExpand}>Expand</button> </form>
)
}
我的組件是一個函數,因此可以將它的屬性看做是參數。我們可以像下面這樣對它們進行擴展:
import React from 'react'
import {observer} from 'mobx-react'
import './styles/Form.css'
const expandableFormRequiredProps = {
onExpand: React.PropTypes.func.isRequired,
expanded: React.PropTypes.bool
}
function ExpandableForm({ onExpand, expanded = false, children }) {
return (
<form style={ expanded ? { height: 'auto' } : { height: 0 } }> {children} <button onClick={onExpand}>Expand</button> </form>
)
}
注意,我們也能以一種高度可讀的方式使用默認參數來扮演 defaultProps 的角色。如果 expanded 是 undefined, 我們就會將其設置為 false。 (這個例子有點勉強,因為是一個布爾值,不過本身對于避免對象的“Cannot read <property> of undefined“這樣的錯誤是很有用的)。
要避免如下這種 ES6 語法:
const ExpandableForm = ({ onExpand, expanded, children }) => {
看著非常現代,不過這里的函數實際上沒有被命令。
這樣子的名稱在 Bable 進行了正確的設置的情況下是可行的 —? 但如果沒有正確設置,任何錯誤都會以在<<anonymous>>中出現的方式顯示,調試起來相當麻煩。
無名的函數也會在 Jest 這個 React 測試庫中引發問題。為了避免潛在的復雜問題出現,我們建議使用 function 而不是 const。
封裝
因為在函數式組件中不能使用裝飾器,所以你可以簡單地將它傳遞到函數中充當參數:
import React from 'react'
import {observer} from 'mobx-react'
import './styles/Form.css'
const expandableFormRequiredProps = {
onExpand: React.PropTypes.func.isRequired,
expanded: React.PropTypes.bool
}
function ExpandableForm({ onExpand, expanded = false, children }) {
return (
<form style={ expanded ? { height: 'auto' } : { height: 0 } }> {children} <button onClick={onExpand}>Expand</button> </form>
)
}
ExpandableForm.propTypes = expandableFormRequiredProps
export default observer(ExpandableForm)
如下是完整的組件代碼:
import React from 'react'
import {observer} from 'mobx-react'
// Separate local imports from dependencies
import './styles/Form.css'
// Declare propTypes here as a variable, then assign below function declaration
// You want these to be as visible as possible
const expandableFormRequiredProps = {
onSubmit: React.PropTypes.func.isRequired,
expanded: React.PropTypes.bool
}
// Destructure props like so, and use default arguments as a way of setting defaultProps
function ExpandableForm({ onExpand, expanded = false, children }) {
return (
<form style={ expanded ? { height: 'auto' } : { height: 0 } }> {children} <button onClick={onExpand}>Expand</button> </form>
)
}
// Set propTypes down here to those declared above
ExpandableForm.propTypes = expandableFormRequiredProps
// Wrap the component instead of decorating it
export default observer(ExpandableForm)
JSX 中的條件分支
你會有不少機會去做許多條件分支渲染。如下是你想要去避免的情況:
嵌套的三元組并非不是好主意。
有一些庫可以解決這個問題 (JSX-Control Statements),不過相比引入額外的依賴,通過如下這種方式解決復雜條件分支問題要更好:
使用花括弧封裝一個 IIFE, 然后在里面放入 if 語句,可以返回任何你想要渲染的東西。注意像這樣的 IIFE 對性能會有影響,不過在大多數情況中還不足以讓我們為此選擇丟掉可讀性。
還有就是當你只想要在一個條件分支中渲染一個元素時,比起這樣做…
{
isTrue
? <p>True!</p>
: <none/>
}
… 使用短路寫法更劃算:
{
isTrue &&
<p>True!</p>
}
作者:leoxu leoxu
來源:51CTO
智能推薦
活動的最佳實踐
知曉當前是在哪一個活動 當我們入職新的公司的時候,難免要閱讀新公司以前開發的舊項目,讀了郭神的第一行代碼之后發現一個很實用的閱讀新項目代碼的技巧。就是新建一個baseActivity類,這個baseActivity繼承于系統的Activity,并從寫onCreate方法,如下圖所示。 在onCreate方法中獲取了當前實例的類名,并通過Log打印了出來。 接下來把項目里的其它activity繼承這...
Redux04 React + Redux 最佳實踐
本文由 簡悅 SimpRead 轉碼, 原文地址 https://github.com/sorrycc/blog/issues/1 更新:我們基于此最佳實踐做了一個封裝方案:dva,可以簡化使用 redux 和 redux-saga 時很多繁雜的操作。 前端變化雖快,但其實一直都圍繞這幾個概念在轉: URL - 訪問什么頁面 Data - 顯示什么信息 View - 頁面長成什么樣 Action ...
源碼分析:react hook 最佳實踐(上篇)
文章目錄 源碼分析:react hook 最佳實踐(上篇) 前言 2條規則 為什么? 源碼分析 useState 使用方式 為什么? 源碼分析 useEffect 使用方式 為什么? 源碼分析 useMemo 使用方式 為什么? 源碼分析 useCallback 使用方式 為什么? 源碼分析 下篇介紹 參考 源碼分析:react hook 最佳實踐(上篇) 原文鏈接 前言 本文從 mini Rea...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
猜你喜歡
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...