本章假設你已經有了JSX經驗,你希望提高自己的技能去高效地使用它。
要想使用JSX,沒有任何問題或不被期望的行為,理解它底層的工作原理和它成為構建UI一個有用工具的原因是重要的。
我們的目標是編寫干凈且可維護的JSX代碼,因此,我們必須知道它的出處,如何轉換為JavaScript,以及它的特性。
在第一部分,我們會有一點回顧,請耐心等待,因為它對于掌握基礎以應用最佳實踐是至關重要的。
在本章,我們將講解以下內容:
- JSX的概念以及使用它的理由
- Babel的概念,如何使用它編寫現代化的JavaScript代碼
- JSX 的特性,HTML和JSX的區別
- 以優雅和可維護的方式編寫JSX的最佳實踐
- linting,特別是ESLint如何使我們的JavaScript代碼跨應用程序和團隊也保持一致
- 函數式編程的基礎知識,以及為什么遵循功能范式會讓我們寫出更好的React組件
2.1 JSX
在前一章中,我們了解了React如何改變關注點分離的概念,將界限移入組件。
我們已經學習了React 如何用組件返回的元素在屏幕上顯示UI。
讓我們現在看一下如何在組件內聲明元素。
React 提供兩種方式來定義元素。第一個是函數式聲明,第二種是使用JSX,一種可選的XML式語法。下面是React官網的示例:
最初,JSX是人們無法接近React的主要原因之一。因為在首頁的例子可以看見HTML和JavaScript是混在一起寫的。這對于大部分人來說第一眼看上去是奇怪的。
一旦我們習慣了它,我們就會意識到它非常方便,正因為它類似于HTML,對于已經在web上創建過UI的人來說,它看起來非常熟悉。
開始和關閉標簽使其更容易表示嵌套樹元素——這些東西本來是不可讀的并且很難用純JavaScript來維護。
2.1.2 Babel
為了在我們的代碼中使用JSX(和ES2015的一些特性),我們必須安裝Babel。
首先,重要的是要清楚地理解它能解決的問題以及為什么需要在程序中添加一個步驟。原因是我們想要使用語言的特性,這在瀏覽器,即我們的目標環境,還沒有添加。對于開發人員,這些先進的特性使我們的代碼更簡潔,但是瀏覽器不能理解和
執行它。
解決方案是用JSX和ES2015中編寫腳本,當我們準備發布時,我們將源代碼編譯成ES5,這是在主流瀏覽器中實現的標準規范。
Babel是一個流行的JavaScript編譯器在React社區中被廣泛采用。
Babel可以將ES2015代碼編譯成ES5 JavaScript,并將JSX編譯成JavaScript
函數。該過程稱為轉換,因為它將源代碼編譯為新的資源而不是可執行文件。
使用它很簡單;安裝:
npm install --global babel-cli
如果你不想在全局上安裝它(開發人員通常傾向于避免這種情況),您可以通過一個npm腳本將Babel局部安裝到一個項目中,但是本章為了達到這個目的,使用一個全局實例。
安裝完成后,運行以下命令可以編譯任何JavaScript文件:
babel source.js -o output.js
Babel之所以如此強大,原因之一在于它是高度可配置的。Babel只是一個將源文件轉換成輸出文件的工具,但是要應用一些轉換,我們需要配置。
幸運的是,有一些非常有用的配置集,我們可以很容易地安裝和使用:
npm install --global babel-preset-es2015
babel-preset-react
安裝完成后,在根目錄下創建了一個名為.babelrc的配置文件,并將以下幾行放入其中,告訴Babel使用這些預設值:
{
"presets": [
"es2015",
"react"
]
}
現在,我們可以在源文件中編寫ES2015和JSX,并在瀏覽器中執行輸出文件。
2.1.3 Hello World
現在我們的環境已經設置為支持JSX,我們可以深入研究最基本的例子:生成div元素。
這是使用React的createElement函數創建div的方法:
React.createElement('div)
這是在JSX中創建div元素:
<div />
它看起來類似于常規HTML。
最大的區別是我們在.js文件中編寫標記,但重要的是請注意,JSX只是語法糖,它會被轉換為JavaScript在瀏覽器中執行。
實際上,當我們運行Babel時,我們的<div />被轉換成React.createElement('div'),這是我們在編寫模板時應始終牢記的事情。
2.1.4 DOM元素和React組件
使用JSX,我們可以創建HTML元素和React組件;唯一的區別是首字母是否大寫。
例如,為了渲染按鈕,在HTML中我們使用<button/>,在組件中我們使用<Button/>渲染。
第一個按鈕被轉換成以下內容:
React.createElement('button')
第二種則被轉換為以下內容:
React.createElement(Button)
這里的不同之處在于,在第一次調用中,我們將DOM元素的類型傳遞為一個字符串,在第二個調用中我們傳遞的是組件本身,也就是在工作的范圍內存在。
正如您可能已經注意到的,JSX支持自閉標簽,這非常適合保持代碼簡潔,不要求我們重復不必要的標記。
2.1.5 Props
當DOM元素或者React組件有props,JSX是十分便利的。事實上,使用XML給元素設置屬性更加容易:
<img src="https://facebook.github.io/react/img/logo.svg"
alt="React.js" />
JavaScript 中等效代碼如下:
React.createElement("img", {
src: "https://facebook.github.io/react/img/logo.svg",
alt: "React.js"
});
這很難讀懂,即使只有幾個屬性,不經過一點推理很難讀懂。
2.1.6 Children
ISX允許你定義子元素來描述元素樹,并組成復合元素。
一個基本的例子是一個包含文本的鏈接,如下所示:
<a >Click me!</a>
這將被轉換成下列代碼:
React.createElement(
"a",
{ href: "https://facebook.github.io/react/" },
"Click me!"
);
我們的鏈接可以包含在一個div中,以滿足一些布局需求,以及JSX代碼片段。實現如下:
<div>
<a >Click me!</a>
</div>
JavaScript 中等效代碼如下:
React.createElement(
"div",
null,
React.createElement(
"a",
{ href: "https://facebook.github.io/react/" },
"Click me!"
)
);
現在應該很清楚,類似于xml的JSX語法如何使所有東西都更易于閱讀和維護,但是了解JSX的JavaScript并行性對于控制元素的創建總是很重要的。。
好的方面是我們不局限于將元素作為元素的子元素,而且我們可以使用lavaScript表達式,如函數或變量。
要做到這一點,我們只需要把表達式括在大括號中:
<div>
Hello, {variable}.
I'm a {function()}.
</div>
這同樣適用于非字符串屬性:
<a href={this.makeHref()}>Click me!</a>
2.1.7 與HTML的不同
到目前為止,我們已經研究了JSX和HTML之間的相似之處。現在讓我們看看它們之間的細微差別以及它們存在的原因。
Attributes
我們必須始終牢記JSX不是一種標準語言,它被轉換成lavaScript。因此,有些屬性無法使用。
例如,我們必須使用className,而不是class,我們必須使用htmlFor,而不是for:
<label className="awesome-label" htmlFor="name" />
原因是這個類和for是JavaScript中的保留字。
Style
一個非常重要的區別是樣式屬性的工作方式。我們將在第7章中詳細介紹如何使用它,使組件看起來更漂亮。但現在我們將關注它的工作方式。
style屬性不像HTML并行程序那樣接受CSS字符串,但它希望是一個JS對象,其中樣式名是駝峰式的:
<div style={{ backgroundColor: 'red' }} />
Root
值得一提的HTML的一個重要區別是,由于JSX元素被轉換為JavaScript函數,并且您不能在JavaScript中返回兩個函數,因此當您在同一級別上有多個元素時,您必須將它們包裝為一個父元素。
讓我們看一個簡單的例子:
<div />
<div />
這給了我們以下錯誤:
Adjacent JSX elements must be wrapped in an enclosing tag
另一方面,以下工作:
<div>
<div />
<div />
</div>
為了使JSX工作,不得不添加不必要的div標簽是非常煩人的。但是React開發人員正在努力尋找解決方案(在撰寫本文時):
https://github.com/reactjs/core-notes/blob/master/2016-07/july-07.md
Spaces
有一件事在一開始可能會有點棘手,而且它涉及到這樣一個事實,即我們應該始終記住JSX不是HTML,即使它具有類似xml的語法。
實際上,JSX在某種程度上以不同于HTML的方式處理文本和元素之間的空格。這是反直覺的。
思考以下代碼片段:
<div>
<span>foo</span>
bar
<span>baz</span>
</div>
在解析HTML的瀏覽器中,這段代碼會給你foo bar baz,這正是我們所期望的。
相反,在ISX中,相同的代碼將呈現為foobarbaz,這是因為三個.嵌套的行被轉換為div元素的子元素,而不考慮空格。為了得到相同的輸出,一個常見的解決方案是在元素之間顯式地放置一個空格:
<div>
<span>foo</span>
{' '}
bar
{' '}
<span>baz</span>
</div>
你可能已經注意到的,我們使用了一個包裝在JavaScript表達式中的空字符串來強制編譯器應用元素之間的空格。
Boolean attributes
在開始介紹真正的ISX中定義布爾屬性的方法之前,還有幾件事值得一提。如果您設置了一個不帶值的屬性,ISX假設它
關于你的生活方式,在真正開始介紹JSX中定義布爾屬性的方法之前還有幾件事值得一提。如果您設置了一個沒有值的屬性,JSX默認值為true。例如,HTML disabled屬性。
這意味著如果我們想要將一個屬性設置為false,我們必須顯式地聲明它為false:
<button disabled />
React.createElement("button", { disabled: true });
下面是另一個例子:
<button disabled={false} />
React.createElement("button", { disabled: false });
這在一開始可能會令人困惑,因為我們可能認為省略屬性意味著false,但事實并非如此。在React中,我們應該始終明確避免混淆。
2.1.8 Spread attributes
一個重要的特性是 spread attributes屬性操作符,它來自于ECMAScript提案的Rest/spread屬性(https://github.com/sebmarkbage/ecmascript-re-spread),當我們想將一個JavaScript對象的所有屬性傳遞給一個元素時,它都非常方便。
使bug更少的一個常見實踐不是通過引用將整個JavaScript對象傳遞給子對象,而是使用它們的基本值,這可以很容易地進行驗證,從而使組件更加健壯且不會出錯。
讓我們看一下它是如何工作的:
const foo = { id: 'bar' }
return <div {...foo} />
前面的代碼被轉換為以下代碼:
var foo = { id: 'bar' };
return React.createElement('div', foo);
2.1.9 JavaScript templating
最后,我們假設移動模板的優點之一。在組件內部,而不是使用外部模板庫,我們可以使用lavaScript的全部功能,所以讓我們開始看看這意味著什么。
spread屬性就是一個例子,另一個常見的例子是JavaScript表達式可以作為屬性值使用,方法是將它們括在花括號中:
<button disabled={errors.length} />
2.1.10 常見模式
既然我們已經知道ISX是如何工作的,并且能夠掌握它,我們就可以看看如何在其中使用它了。遵循一些有用的約定和技術的正確方法。
Multi-line
讓我們從一個非常簡單的開始。如前所述,我們應該更喜歡JSX而不是React的createElement的主要原因之一是它類似于xml的語法,也因為平衡的閉合標簽十分適合代表節點樹。
一個例子如下;每當我們有嵌套的元素,我們應該總是走多行:
<div>
<Header />
<div>
<Main content={...} />
</div>
</div>
這比下列更可取:
<div><Header /><div><Main content={...} /></div></div>
如果子元素不是元素(如文本或變量),則例外。在這種情況下,保持在同一行是有意義的,并避免給標記添加噪聲,如下所示:
<div>
<Alert>{message}</Alert>
<Button>Close</Button>
</div>
當你用多行來寫你的元素時,一定要記得把它們用圓括號括起來。實際上,JSX總是被函數所替代,而在新的一行上編寫的函數會由于自動分號插入而得到意想不到的結果。例如,假設您正在從render方法返回ISX,這是創建UI在React的方法。
下面的示例工作得很好,因為div與返回的內容在同一行:
return <div />
但是,下列情況是不對的:
return
<div />
原因是:
return;
React.createElement("div", null);
這就是為什么你必須用括號括起來:
return (
<div />
)
多屬性
當一個元素具有多個屬性時,就會出現編寫JSX的問題。一種解決方案是將所有屬性寫在同一行上,但是這將導致非常長的行,這是我們的代碼中不希望看到的(關于如何執行編碼風格的指南,請參閱下面一節)。
一種常見的解決方案是在新行中寫入每個屬性,使用一層縮進,然后將結束括號與開始標記對齊:
<button
foo="bar"
veryLongPropertyName="baz"
onSomething={this.handleSomething}
/>
Conditionals
當我們開始使用條件語句時,事情變得更加有趣,例如,如果我們想只在特定條件匹配時render某些組件。這一事實中,我們可以使用JavaScript條件是一大亮點,但也有許多不同的方式來表達條件JSX。為了代碼可讀性和可維護性,重要的是要理解每一個的好處和問題。
假設我們想要顯示一個注銷按鈕,僅當當前用戶在應用程序中被注銷時。
一個簡單的代碼片段如下:
let button
if (isLoggedIn) {
button = <LogoutButton />
}
return <div>{button}</div>
這是可行的,但是可讀性不是很好,特別是當有多個組件和多個條件時。
在JSX中,我們可以使用內聯條件:
<div>
{isLoggedIn && <LoginButton />}
</div>
這可以工作,因為如果條件為false,就不會呈現任何內容,但是如果條件為true,就會調用LoginButton的createElement函數,并返回元素以組成結果樹。
如果條件有選擇,(經典的if…else語句),我們想,例如,要顯示用戶是否登錄的注銷按鈕和登錄按鈕,我們可以使用JavaScript的if…else其他,如下所示:
let button
if (isLoggedIn) {
button = <LogoutButton />
} else {
button = <LoginButton />
}
return <div>{button}</div>
或者,更好的是,我們可以使用三元條件,使代碼更緊湊:
<div>
{isLoggedIn ? <LogoutButton /> : <LoginButton />}
</div>
您可以找到流行的存儲庫中使用的三元條件,例如Redux realworld示例 https://github.com/reactjs/redux/blob/master/examples/real-world/src/components/List.js#L25,其中如果組件正在獲取數據,三元用于顯示加載標簽,或者根據isFetching變量的值在按鈕中加載更多內容:
<button [...]>
{isFetching ? 'Loading...' : 'Load More'}
</button>
現在讓我們看看當事情變得更復雜時的最佳解決方案,例如,我們必須檢查多個變量以確定是否渲染組件:
<div>
{dataIsReady && (isAdmin || userHasPermissions) &&
<SecretData />
}
</div>
在這種情況下,顯然使用內聯條件是一種很好的解決方案,但是可讀性有很大的影響。我們可以在組件內部創建一個函數,并在JSX中使用它來驗證條件:
canShowSecretData() {
const { dataIsReady, isAdmin, userHasPermissions } = this.props
return dataIsReady && (isAdmin || userHasPermissions)
}
<div>
{this.canShowSecretData() && <SecretData />}
</div>
如您所見,此更改使代碼更具可讀性,條件更顯式。如果您在6個月后查看這段代碼,您仍然會發現僅僅通過讀取函數的名稱就可以清楚地理解它。
如果你不喜歡使用函數,那么可以使用對象的getters,這些getters構成代碼。。更優雅。
例如,我們不是聲明一個函數,而是定義一個getter:
get canShowSecretData() {
const { dataIsReady, isAdmin, userHasPermissions } = this.props
return dataIsReady && (isAdmin || userHasPermissions)
}
<div>
{this.canShowSecretData && <SecretData />}
</div>
這同樣適用于計算屬性。假設有兩個屬性貨幣和值。你不需要在渲染方法中創建價格字符串,你可以創建一個類函數:
getPrice() {
return `${this.props.currency}${this.props.value}`
}
<div>{this.getPrice()}</div>
這更好,因為它是隔離的,您可以很容易地測試它,以防它包含邏輯。或者,你可以更進一步,就像我們剛才看到的,使用getter:
get price() {
return `${this.props.currency}${this.props.value}`
}
<div>{this.price}</div>
回到條件語句,還有其他需要使用外部依賴的解決方案。一個好的實踐是盡可能地避免外部依賴,以使您的包更小,但是在這種特殊情況下,這樣做可能是值得的,因為改進模板的易用性是一個很大的勝利。
第一個解決方案是render-if,我們可以使用以下方法安裝它:
npm install --save render-if
我們可以很容易地在我們的項目中使用它,如下所示:
const { dataIsReady, isAdmin, userHasPermissions } = this.props
const canShowSecretData = renderIf(
dataIsReady && (isAdmin || userHasPermissions)
)
<div>
{canShowSecretData(<SecretData />)}
</div>
我們將條件包裝在renderIf函數中。
返回的實用程序函數可以用作函數,當條件為真時,該函數接收要顯示的JSX標記。
我們應該牢記的一個目標是,永遠不要在我們的組件中加入太多邏輯。其中一些可能需要一些,但是我們應該盡量使它們盡可能簡單,這樣我們就可以很容易地發現和修復錯誤。
我們至少應該盡量保持renderIf方法的干凈,為了做到這一點,我們可以使用另一個實用庫,稱為react-only-if,它允許我們通過使用高階組件設置條件函數來編寫組件,就好像條件總是為真一樣。
我們將在第4章(Compose All the Things)詳細討論高階組件,但現在您只需要知道它是接收組件并通過添加一些屬性或修改其行為返回增強組件的函數。
使用庫,我們只需要安裝如下:
npm install --save react-only-if
一旦安裝完畢,我們就可以在應用程序中使用它:
const SecretDataOnlyIf = onlyIf(
({ dataIsReady, isAdmin, userHasPermissions }) => {
return dataIsReady && (isAdmin || userHasPermissions)
}
)(SecretData)
<div>
<SecretDataOnlyIf
dataIsReady={...}
isAdmin={...}
userHasPermissions={...}
/>
</div>
正如您在這里看到的,組件本身中根本沒有邏輯。
我們將條件作為onlyIf函數的第一個參數傳遞,當條件匹配時,組件將被渲染。
用于驗證條件的函數接收組件的props、state和context。
這樣,我們就避免用條件句污染我們的組件,以便更容易理解和理解。
循環
UI開發中一個非常常見的操作是顯示項目列表。當涉及到顯示列表,使用JavaScript作為模板語言是一個很好的主意。
如果我們編寫一個函數來返回JSX模板中的數組,那么數組的每個元素都會被編譯成一個元素。
正如我們之前看到的,我們可以在花括號中使用任何lavaScript表達式,在給定對象數組的情況下,最常用方法是使用map生成元素數組。
讓我們看一個真實的例子。假設您有一個用戶列表,每個用戶都有一個name屬性。
要創建一個無序列表來顯示用戶,您可以執行以下操作:
<ul> {users.map(user =><li>{user.name}</li>)}</u
這段代碼非常簡單,同時又非常強大,HTML和JavaScript的力量匯聚在一起。
控件語句
條件和循環是UI模板中非常常見的操作,使用JavaScript三目運算符或map函數執行它們時,您可能會感到錯誤。JSX的構建方式是這樣的:它只抽象元素的創建,而把邏輯部分留給了真正的lavaScript,這很好,只是有時代碼變得不那么清晰。
通常,我們的目標是從組件中刪除所有邏輯,特別是從render方法中刪除邏輯。但有時我們必須根據應用的state顯示和隱藏元素,通常需要循環遍歷集合和數組。
如果您覺得將JSX用于這種操作將使代碼更易于閱讀,那么可以使用Babel插件:jsx-control-statements.
這遵循與JSX相同的原理,并且它不向語言添加任何真正的功能;只是語法糖被編譯成JavaScript。
讓我們看看它是如何工作的。
首先,我們必須安裝它:
npm install --save jsx-control-statements
一旦安裝完畢,我們必須將其添加到.babelrc文件中的babel插件列表中:
"plugins": ["jsx-control-statements"]
從現在開始,我們可以使用插件提供的語法,Babel將把它與常用的ISX語法一起傳遞出去。
使用插件編寫的條件語句如下:
<If condition={this.canShowSecretData}>
<SecretData />
</If>
這被轉換成三元表達式如下:
{canShowSecretData ? <SecretData /> : null}
If組件很好,但是如果由于某種原因您在render方法中有嵌套的條件,那么它很容易變得混亂和難以理解。下面是Choose組件派上用場的地方:
<Choose>
<When condition={...}>
<span>if</span>
</When>
<When condition={...}>
<span>else if</span>
</When>
<Otherwise>
<span>else</span>
</Otherwise>
</Choose>
請注意,前面的代碼被轉換為多個三目運算符。
最后,還有一個組件(請記住,我們討論的不是全部組件,而是語法糖)來管理循環,這也非常方便:
<ul>
<For each="user" of={this.props.users}>
<li>{user.name}</li>
</For>
</ul>
前面的代碼被轉換為map函數——沒有魔法。
如果你習慣使用linters,你可能會想為什么linters沒有抱怨該代碼。事實上,變量user在轉換之前并不存在,也不會被包裝成函數。為了避免這些連接錯誤,還需要安裝另一個插件:eslint?plugin-jsx-control-statements。
如果你不明白前面的句子,不要擔心;我們將在下一節討論linting。
Sub-rendering
值得強調的是,我們總是希望保持組件非常小,渲染方法非常干凈和簡單。
然而,這并不是一個簡單的目標,特別是當您迭代地創建應用程序時,在第一次迭代中,您不確定如何將組件劃分為更小的組件。
那么,當render方法變得太大而無法維護時,我們應該做什么呢?一種解決方案是將它分解成更小的函數,這樣我們就可以保持所有邏輯不變。
讓我們看一個例子:
renderUserMenu() {
// JSX for user menu
}
renderAdminMenu() {
// JSX for admin menu
}
render() {
return (
<div>
<h1>Welcome back!</h1>
{this.userExists && this.renderUserMenu()}
{this.userIsAdmin && this.renderAdminMenu()}
</div>
)
}
這并不總是被認為是最佳實踐,因為將組件分成更小的組件似乎更明顯。然而,有時它只有助于保持呈現方法的清晰。例如,在Redux realworld示例中,使用子呈現方法來渲染 load more按鈕。
現在我們是JSX的高級用戶,現在是時候繼續前進了,看看如何在我們的代碼中遵循樣式指南以使其一致。
2. ESLint
我們總是盡可能地編寫最好的代碼,但有時會發生錯誤,并且花費幾個小時解決拼寫錯誤的bug是非常令人沮喪的。幸運的是,有一些工具可以幫助我們在輸入代碼時檢查代碼的正確性。
這些工具不能告訴我們我們的代碼是否會做它應該做的事情,但是它們可以幫助我們避免語法錯誤。
如果你寫過C語言,那么您已經習慣在IDE中得到這種警告。
Douglas Crockford幾年前使用JSLint(最初于2002年發布),使linting在JavaScript中流行起來。然后我們有了JSHint,最后,當今react世界標準是ESLint。
ESLint是2013年發布的一個開源項目,由于其高度可配置性和可擴展性而廣受歡迎。
在JavaScript生態系統中,庫和技術的變化非常迅速。有一個可以很容易地擴展插件,和規則,在需要時啟用和禁用的工具,這一點至關重要。
最重要的是,現在我們使用的是像Babel這樣的編譯器和不是JavaScript標準版本的一部分實踐特性,因此我們需要能夠告訴linter在源文件中遵循哪些規則。
linter不僅幫助我們減少錯誤,或者至少更快地發現這些錯誤,而且它還強制執行一些常見的編碼風格指南,這非常重要,特別是在有許多開發人員的大型團隊中,每個開發人員都有自己喜歡的編碼風格。
在使用不同風格編寫的不同文件甚至各種函數的代碼庫中,很難讀取代碼。
安裝
首先,我們如下安裝ESLint:
npm install --global eslint
一旦安裝了可執行文件,我們可以使用以下命令運行它:
eslint source.js
輸出將告訴我們文件中是否有錯誤。
當我們第一次安裝并運行它時,我們沒有看到任何錯誤,因為它是完全可配置的,并且沒有任何默認規則。
配置項
讓我們開始配置。
可以使用位于項目根文件夾中的.eslintrc文件配置ESLint。
要添加一些規則,我們使用規則鍵。
例如,讓我們創建一個.eslintrc并禁用分號:
{
"rules": {
"semi": [2, "never"]
}
}
這個配置文件需要解釋一下:“semi”是規則名稱,[2,"never"]是值。當你第一次看到它的時候,并不是很直觀。
”ESLint規則有三個級別,決定問題的嚴重程度:
- off(或0):禁用。
- warn(或1):警告。
- error(或2):拋出錯誤。
我們使用這個值2,是因為每當我們的代碼不遵守規則時,我們都希望ESLint拋出一個錯誤。
第二個參數告訴ESLint我們不希望使用分號(對立的值是 always)。
ESLint和它的插件都有很好的文檔記錄,對于任何一個規則,您都可以找到關于規則的描述,以及它何時通過以及何時失敗的一些說明。
現在,創建一個包含以下內容的文件:.
var foo = 'bar';
(注意,我們在這里使用var是因為ESLint不知道我們想在ES2015中編寫代碼。)
如果我們運行eslint index-js,我們會得到以下結果:
Extra semicolon (semi)
這是好的;我們建立了linter,它幫助我們遵循我們的第一條規則。
我們可以手動啟用和禁用每個規則,或者通過將以下代碼放入我們的.eslintrc,我們可以一次性啟用推薦的配置:
{
"extends": "eslint:recommended"
}
extends鍵意味著我們是從ESLint擴展建議的規則。但是,我們總是可以使用規則鍵在.eslintrc中手動重寫單個規則,就像我們之前所做的那樣。
一旦啟用了推薦的規則并再次運行ESLint,就不應該接收。分號的錯誤(這不是建議配置的一部分),但是我們應該看到linter提示foo變量已經聲明且從未使用過。
不使用vars規則對于保持代碼整潔非常有用。
正如我們從一開始就說過的,我們想要編寫ES2015代碼,但是如下改變代碼,返回錯誤:
const foo = 'bar'
錯誤:
Parsing error: The keyword 'const' is reserved
所以要啟用ES2015,我們需要添加一個配置選項:
"parserOptions": {
"ecmaVersion": 6,
}
一旦我們這樣做了,我們將再次得到未使用的錯誤,這是好的。
最后,為了啟用ISX,我們使用以下方法:
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"jsx": true
}
},
在這一點上,如果您以前編寫過任何React應用程序,而且從未使用過linter,那么學習規則并習慣它的一個很好的練習就是對源代碼運行ESLint并修復所有問題。。我們通過不同的方式,使ESLint幫助我們寫更好的代碼。其中一個是我們到目前為止所做的:從命令行運行它并獲得錯誤列表。
這是可行的,但是一直手動運行它不是很方便。最好在編輯器中添加linting過程,以便在輸入時立即得到反饋。要做到這一點,有針對SublimeText、Atom和其他最流行的編輯器的ESLint插件。
在實際應用中,手動運行ESLint或在編輯器中實時獲取反饋,即使非常有用,也不夠,因為我們可能會遺漏一些警告或錯誤,或者干脆忽略它們。
為了避免在存儲庫中有未連接的代碼,我們可以做的是在一個庫中添加ESLint。我們過程的重點。例如,我們可以在測試時運行linting,如果代碼沒有通過linting規則,那么整個測試步驟都會失敗。
另一個解決方案是在打開pull請求之前添加linting,這樣我們就有機會在同事開始審查代碼之前清理代碼。
React 插件
正如前面提到的,ESLint流行的主要原因之一是它可以通過插件進行擴展,對我們來說最重要的原因是ESLint -plugin-react。
ESLint可以在不使用任何插件的情況下解析JSX(僅通過啟用標記),但我們還想做更多。例如,我們可能希望執行我們在上一節中看到的最佳實踐之一,并使我們的模板在開發人員和團隊之間保持一致。
要使用插件,我們首先必須安裝它:
npm install --global eslint-plugin-react
安裝完成后,我們指示ESLint使用它,向配置文件中添加以下行:
"plugins": [
"react"
]
如您所見,它非常簡單,不需要任何復雜的配置或設置。就像ESLint一樣,沒有任何規則,它不會做任何事情,但是我們可以啟用建議的配置來激活一組基本規則。
.為此,我們更新了.eslintrc文件中的extend鍵,如下所示:
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
現在,如果我們寫錯了,例如,我們試圖在一個React組件中使用同一個prop兩次,我們就會得到一個錯誤:
No duplicate props allowed (react/jsx-no-duplicate-prop
在我們的項目中有很多可用的規則。讓我們瀏覽其中的一些,看看它們如何幫助我們遵循最佳實踐。
正如前一章所討論的,遵循元素樹結構,縮進JSX是很有幫助的。提高可讀性。
當縮進與代碼庫和組件不一致時,就會出現問題。
因此,這里有一個例子說明了ESLint是如何幫助團隊中的每個人在不需要記住它的情況下遵循一個風格指南的。
注意,在這種情況下,有錯誤的縮進不是實際的錯誤,代碼是可運行的;這只是一個一致性的問題。
首先,我們必須激活以下規則:
"rules": {
"react/jsx-indent": [2, 2]
}
第一個2表示我們希望ESLint在代碼中不遵守規則時引發錯誤。第二個2表示我們希望每個JSX元素都縮進2個空格。ESLint不會為您做任何決定,所以由您決定要啟用的規則。您甚至可以選擇使用0作為第二個參數強制執行不縮進。
這樣寫:
<div>
<div />
</div>
ESLint 提示如下:
Expected indentation of 2 space characters but found 0
(react/jsx-indent)
當我們在新行上寫入屬性時,類似的規則也適用于縮進屬性的方式。
正如我們在前一節中看到的,當屬性太多或太長時,最好將它們寫在新行上。
如果屬性與元素名稱之間有兩個空格縮進,為了強制格式化,我們可以啟用以下規則:
"react/jsx-indent-props": [2, 2]
從現在開始,如果我們不使用兩個空格縮進屬性,ESLint將失敗。
問題是,我們什么時候認為一行太長?多少屬性太多了?
每個開發人員對此都會有不同的看法。ESLint幫助我們保持與jsx-max-props-per-line規則的一致性,以便以相同的方式編寫每個組件。
ESLint的React插件不僅為我們提供了一些規則來編寫更好的JSX,還提供了一些規則來編寫更好的React組件,
例如,我們可以讓一個規則使prop類型按字母順序排序,當我們使用prop還沒有聲明,會拋出錯誤。規則迫使我們更喜歡無狀態組件多于類(我們將會看到在第3章中詳細的區別,創建真正可重用的組件),等等。
Airbnb configuration
我們已經看到了ESLint如何使用靜態分析幫助我們發現錯誤,以及它如何迫使我們在整個代碼庫中遵循一致的風格。
我們還看到了它的靈活性,以及如何通過配置和插件來擴展它。
我們知道我們已經推薦了激活基本規則集,避免手工操作的配置,,這可能是一項乏味的任務。
讓我們更進一步。
ESLint extends屬性非常強大,您可以使用第三方配置作為起點,然后在其上添加特定的規則。
React公司最常用的配置之一無疑是Airbnb那種。Airbnb的開發人員創建了一組遵循React最佳做法的規則,你可以很容易地在代碼庫中使用它們,這樣你就不必手動決定啟用哪些規則。
為了使用它,你必須首先安裝一些依賴:
npm install --global eslint-config-airbnbeslint@^2.9.0 eslint-plugin-jsx-a11y@^1.2.0 eslint-plugin-import@^1.7.0 eslint-plugin-react@^5.0.1
然后將以下配置添加到您的eslintrc:
{
"extends": "airbnb"
}
嘗試對您的React源文件再次運行ESLint,您將看到您的代碼是否遵循了Airpnb規則,以及是否喜歡這些規則。
這是最簡單也是最常見的開始linting的方法。
3.函數式編程的基礎知識
除了在編寫JSX時遵循最佳實踐之外,我們還使用linter來強制一致性和及早發現錯誤,我們還可以做一件事來清理我們的代碼:遵循函數式編程(FP)風格。
如第一章所述,關于React基礎知識,React具有聲明式編程,使我們的代碼更具可讀性。
函數式編程是一種聲明性范式,在這種范式中,可以避免副作用,并且認為數據是不可變的,以使代碼更易于維護和推理。
不要認為下面的部分是函數式編程的詳盡指南;這只是一個介紹,讓我們從React常用的一些概念入手,你應該知道這些概念。
First-class objects
在JavaScript中,函數是一級對象,這意味著它們可以分配給變量,并作為參數傳遞給其他函數。
這允許我們引入高階函數(HoF)的概念。高階函數是將函數作為參數(可選的其他參數)并返回函數的函數。返回的函數通常通過一些特殊的行為來增強。
讓我們來看一個簡單的例子,其中有一個用于添加兩個數字的函數,它使用一個函數進行了增強,該函數首先記錄所有參數,然后執行add(參數):.
const add = (x, y) => x + y
const log = func => (...args) => {
console.log(...args)
return func(...args)
}
const logAdd = log(add)
理解這個概念是非常重要的,因為在React中,一個常見的模式是使用高階組件(HoC),將組件視為功能,并用常見的行為增強它們。我們將在第四章看到。
純函數
FP的一個重要方面是編寫純函數。您將經常在React生態系統中遇到這個概念,特別是如果您查看Redux之類的庫的話。
什么是純函數?
一個函數在沒有副作用的情況下是純函數,這意味著函數不會改變任何與函數本身無關的東西。
例如,一個改變應用程序狀態的函數,或者修改在上作用域中定義的變量,或者一個觸及外部實體(如DOM)的函數,被認為是不純的。
非純函數更難調試,而且大多數情況下,多次調用這些函數并期望得到相同的結果是不可能的。
例如,下面的函數是純函數:
const add = (x, y) => x + y
它可以運行多次,總是得到相同的結果,因為任何地方都不會存儲任何東西,也不會修改任何東西。
以下函數不是純函數:
let x = 0
const add = y => (x = x + y)
運行add(1)兩次,我們得到兩個不同的結果。第一次得到1,第二次得到2,即使我們用相同的參數調用相同的函數。我們得到這種行為的原因是在每次執行之后全局狀態都會被修改。
Immutability
我們已經看到了如何編寫不會改變state的純函數,但是如果我們需要改變變量的值呢?在純函數中,用新值創建一個新變量并返回它,來代替改變變量的值。這種處理數據的方式稱為不變性。
不可變值是不能被改變的值。
讓我們看一個例子:
const add3 = arr => arr.push(3)
const myArr = [1, 2]
add3(myArr) // [1, 2, 3]
add3(myArr) // [1, 2, 3, 3]
前面的函數不遵循不變性,因為它改變了給定參數的值。同樣,如果我們調用同一個函數兩次,得到的結果也會不同。
我們可以使用concat修改前面的函數使其不可變,concat返回一個數組,而不修改給定的數組:
在我們運行函數兩次之后,nyArr仍然有它的原始值。
局部套用
純函數中一個常見的技術是局部套用。局部套用是指將一個有多個參數的函數轉換為一個每次有一個參數的函數,然后返回另一個函數的過程。讓我們看一個例子來闡明這個概念。讓我們從以前見過的add函數開始,并將其轉換為curried函數。
讓我們從以前見過的add函數開始,并將其轉換為局部套用函數。
之前編寫:
const add = (x, y) => x + y
我們這樣定義函數:
const add = x => y => x + y
我們是這樣用的:
const add1 = add(1)
add1(2) // 3
add1(3) // 4
這是編寫函數的一種非常方便的方式,因為第一個值是在第一個參數應用程序之后存儲的,因此我們可以多次重用第二個函數。
組合
最后,純函數中一個可以應用于react的重要概念是組合。可以將函數(和組件)組合起來,生成具有更高級特性和屬性的新函數。
考慮以下函數:
const add = (x, y) => x + y
const square = x => x * x
這些函數可以組合在一起,創建一個新函數,將兩個數字相加,然后使結果加倍:
const addAndSquare = (x, y) => square(add(x, y))
//先執行add
按照這種范式,我們最終得到了可以組合在一起的小的、簡單的、可測試的純函數。
純函數和用戶接口
最后一步是學習如何使用純函數構建UI,這是我們使用React的目的。
我們可以把UI看作是一個調用了應用程序state的函數,如下所示:
UI = f(state)
我們期望這個函數是全等的,這樣它在應用程序的相同狀態下返回相同的UI。
使用React,我們可以考慮函數創建UI組件,在下一章我們將會看到。
組件可以組成最終的UI,這是純函數的一個屬性。
在我們用React和純函數的原則構建UI的方式上有很多相似之處,我們越了解它,代碼就會越好。
總結
在本章中,我們學習了很多關于JSX如何工作以及如何在組件中正確使用它的知識。我們從語法的基礎知識開始,創建了一個堅實的知識庫,使我們能夠掌握JSX及其特性。
在第二部分中,我們研究了ESLint及其插件如何幫助我們更快地發現問題,并在我們的代碼庫中執行一致的風格指南。
最后,我們學習了函數式編程的基礎知識,以理解編寫React應用程序時要使用的重要概念。
現在我們的代碼是干凈的,我們準備開始深入了解React并學習如何來編寫真正可重用的組件。