React 和JavaScript 類
關(guān)于 React 類組件苛预,需要用到有關(guān) JavaScript 類的先驗知識。JavaScript 類的概念相對較新锋玲。之前,只有 JavaScript 的原型鏈可用于實現(xiàn)繼承打肝。JavaScript 類以原型繼承為基礎(chǔ),讓繼承體系變得更簡單挪捕。
定義 React 組件的一種方法是使用 JavaScript 類粗梭。
class Developer {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
getName() {
return this.firstname + ' ' + this.lastname;
}
}
var me = new Developer('Robin', 'Wieruch');
console.log(me.getName());
一個類描述了一個實體,用于創(chuàng)建實體的實例级零。在使用 new 語句創(chuàng)建類的實例時断医,會調(diào)用這個類的構(gòu)造函數(shù)。類的屬性通常位于構(gòu)造函數(shù)中奏纪。此外鉴嗤,類方法(例如 getName())用于讀取(或?qū)懭耄嵗臄?shù)據(jù)序调。類的實例在類中使用 this 對象來表示醉锅,但在外部,僅指定給 JavaScript 變量发绢。
在面向?qū)ο缶幊讨杏菜#愅ǔS脕韺崿F(xiàn)繼承。在 JavaScript 中也一樣边酒,extends 語句可用于讓一個類繼承另一個類默垄。一個子類通過 extends 語句繼承了一個父類的所有功能,還可以添加自己的功能甚纲。
class Developer {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
getName() {
return this.firstname + ' ' + this.lastname;
}
}
class ReactDeveloper extends Developer {
getJob() {
return 'React Developer';
}
}
var me = new ReactDeveloper('Robin', 'Wieruch');
console.log(me.getName());
console.log(me.getJob());
基本上,要理解 React 的類組件朦前,知道這些就夠了介杆。JavaScript 類用于定義 React 組件,React 組件繼承了從 React 包導(dǎo)入的 React Component 類的所有功能韭寸。
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
<h1>Welcome to React</h1>
</div>
);
}
}
export default App;
這就是為什么 render() 方法在 React 類組件中是必需的:從 React 包導(dǎo)入的 React Component 用它在瀏覽器中顯示某些內(nèi)容春哨。此外,如果不從 React Component 繼承恩伺,將無法使用其他生命周期方法(包括 render() 方法)赴背。例如,如果不繼承晶渠,那么 componentDidMount() 生命周期方法就不存在凰荚,因為現(xiàn)在這個類只是一個普通 JavaScript 類的實例。除了生命周期方法不可用褒脯,React 的 API 方法(例如用于本地狀態(tài)管理的 this.setState())也不可用便瑟。
我們可以通過 JavaScript 類來擴(kuò)展通用類的行為。因此番川,我們可以引入自己的類方法或?qū)傩浴?/p>
import React, { Component } from 'react';
class App extends Component {
getGreeting() {
return 'Welcome to React';
}
render() {
return (
<div>
<h1>{this.getGreeting()}</h1>
</div>
);
}
}
export default App;
現(xiàn)在你應(yīng)該知道為什么 React 使用 JavaScript 類來定義 React 類組件到涂。當(dāng)你需要訪問 React 的 API(生命周期方法脊框、this.state 和 this.setState())時,可以使用它們践啄。接下來浇雹,你將看到如何以不同的方式定義 React 組件,比如不使用 JavaScript 類屿讽,因為有時候你可能不需要使用類方法昭灵、生命周期方法或狀態(tài)。
盡管我們可以在 React 中使用 JavaScript 類繼承聂儒,但這對于 React 來說不是一個理想的結(jié)果虎锚,因為 React 更傾向于使用組合而不是繼承。因此衩婚,你的 React 組件需要擴(kuò)展的唯一類應(yīng)該是 React Component窜护。
React 中的箭頭函數(shù)
箭頭函數(shù)是 ES6 新增的語言特性之一,讓 JavaScript 向函數(shù)式編程更近了一步非春。
// JavaScript ES5 function
function getGreeting() {
return 'Welcome to JavaScript';
}
// JavaScript ES6 arrow function with body
const getGreeting = () => {
return 'Welcome to JavaScript';
}
// JavaScript ES6 arrow function without body and implicit return
const getGreeting = () =>
'Welcome to JavaScript';
在 React 應(yīng)用程序中使用 JavaScript 箭頭函數(shù)通常是為了讓代碼保持簡潔和可讀柱徙。我很喜歡箭頭函數(shù),總是嘗試將我的函數(shù)從 JavaScript ES5 重構(gòu)成 ES6奇昙。在某些時候护侮,當(dāng) JavaScript ES5 函數(shù)和 JavaScript ES6 函數(shù)之間的差異很明顯時,我會使用 JavaScript ES6 的箭頭函數(shù)储耐。不過羊初,對 React 新手來說,太多不同的語法可能會讓人不知所措什湘。因此长赞,在 React 中使用它們之前,我會嘗試解釋 JavaScript 函數(shù)的不同特點(diǎn)闽撤。在以下部分得哆,你將了解到如何在 React 中使用 JavaScript 箭頭函數(shù)。
在React 中將函數(shù)視為組件
React 使用了不同的編程范式哟旗,這要?dú)w功于 JavaScript 是一門“多面手”編程語言贩据。在面向?qū)ο缶幊谭矫妫琑eact 的類組件可以很好地利用 JavaScript 類(React 組件 API 的繼承闸餐、類方法和類屬性饱亮,如 this.state)。另一方面舍沙,React(及其生態(tài)系統(tǒng))也使用了很多函數(shù)式編程的概念近尚。例如,React 的函數(shù)無狀態(tài)組件是另一種定義 React 組件的方式场勤。那么戈锻,如果可以像函數(shù)那樣使用組件歼跟,將會怎樣?
function (props) {
return view;
}
這是一個接收輸入(例如 props)并返回 HTML 元素(視圖)的函數(shù)格遭。它不需要管理任何狀態(tài)(無狀態(tài))哈街,也不需要了解任何方法(類方法、生命周期方法)拒迅。這個函數(shù)只需要使用 React 組件的 render() 方法來進(jìn)行渲染骚秦。
function Greeting(props) {
return <h1>{props.greeting}</h1>;
}
功能無狀態(tài)組件是在 React 中定義組件的首選方法。它們的樣板代碼較少璧微,復(fù)雜性較低作箍,并且比 React 類組件更易于維護(hù)。不過前硫,這兩者都有自己存在的理由胞得。
之前提到了 JavaScript 箭頭函數(shù)以及它們可以提升代碼的可讀性,現(xiàn)在讓我們將這些函數(shù)應(yīng)用無狀態(tài)組件中屹电。之前的 Greeting 組件在 JavaScript ES5 和 ES6 中的寫法有點(diǎn)不一樣:
// JavaScript ES5 function
function Greeting(props) {
return <h1>{props.greeting}</h1>;
}
// JavaScript ES6 arrow function
const Greeting = (props) => {
return <h1>{props.greeting}</h1>;
}
// JavaScript ES6 arrow function without body and implicit return
const Greeting = (props) =>
<h1>{props.greeting}</h1>
JavaScript 箭頭函數(shù)是讓 React 無狀態(tài)組件保持簡潔的一個不錯的方法阶剑。
React 類組件語法
React 定義組件的方式一直在演化。在早期階段危号,React.createClass() 方法是創(chuàng)建 React 類組件的默認(rèn)方式∧脸睿現(xiàn)在不再使用這個方法,因為隨著 JavaScript ES6 的興起外莲,之前的 React 類組件語法成為默認(rèn)語法猪半。
不過,JavaScript 也在不斷發(fā)展偷线,因此 JavaScript 愛好者一直在尋找新的方式办龄。這就是為什么你會發(fā)現(xiàn) React 類組件使用了不同的語法。使用狀態(tài)和類方法來定義 React 類組件的一種方法如下:
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
this.onIncrement = this.onIncrement.bind(this);
this.onDecrement = this.onDecrement.bind(this);
}
onIncrement() {
this.setState(state => ({ counter: state.counter + 1 }));
}
onDecrement() {
this.setState(state => ({ counter: state.counter - 1 }));
}
render() {
return (
<div>
<p>{this.state.counter}</p>
<button onClick={this.onIncrement} type="button">Increment</button>
<button onClick={this.onDecrement} type="button">Decrement</button>
</div>
);
}
}
不過淋昭,在實現(xiàn)大量的 React 類組件時,構(gòu)造函數(shù)中的類方法綁定和構(gòu)造函數(shù)本身就變成了繁瑣的實現(xiàn)細(xì)節(jié)安接。所運(yùn)的是翔忽,有一個簡短的語法可用來擺脫這兩個煩惱:
class Counter extends Component {
state = {
counter: 0,
};
onIncrement = () => {
this.setState(state => ({ counter: state.counter + 1 }));
}
onDecrement = () => {
this.setState(state => ({ counter: state.counter - 1 }));
}
render() {
return (
<div>
<p>{this.state.counter}</p>
<button onClick={this.onIncrement} type="button">Increment</button>
<button onClick={this.onDecrement} type="button">Decrement</button>
</div>
);
}
}
通過使用 JavaScript 箭頭函數(shù),可以自動綁定類方法盏檐,不需要在構(gòu)造函數(shù)中綁定它們歇式。通過將狀態(tài)直接定義為類屬性,在不使用 props 時就可以省略構(gòu)造函數(shù)胡野。(注意:請注意材失,JavaScript 還不支持類屬性。)因此硫豆,你可以說這種定義 React 類組件的方式比其他版本更簡潔龙巨。
React 中的模版字面量
模板字面量是 JavaScript ES6 附帶的另一種 JavaScript 語言特性笼呆。之所以提到這個特性,是因為當(dāng) JavaScript 和 React 新手看到它們時旨别,可能會感到困惑诗赌。以下面的連接字符串的語法為例:
function getGreeting(what) {
return 'Welcome to ' + what;
}
const greeting = getGreeting('JavaScript');
console.log(greeting);
// Welcome to JavaScript
// 模板字面量可以用于達(dá)到相同的目的,被稱為字符串插值:
function getGreeting(what) {
return `Welcome to ${what}`;
}
// 你只需使用反引號和 ${}來插入 JavaScript 原語秸弛。字符串字面量不僅可用于字符串插值铭若,還可用于多行字符串:
function getGreeting(what) {
return `
Welcome
to
${what}
`;
}
// 這樣就可以格式化多行文本塊。
React 中Map递览、Reduce 和Filter
在向 React 新手教授 JSX 語法時叼屠,我通常會先在 render() 方法中定義一個變量,然后將其用在返回代碼塊中绞铃。
import React, { Component } from 'react';
class App extends Component {
render() {
var greeting = 'Welcome to React';
return (
<div>
<h1>{greeting}</h1>
</div>
);
}
}
export default App;
你只需使用花括號來操作 HTML 中的 JavaScript镜雨。不管是渲染字符串還是渲染一個復(fù)雜的對象,并沒有太大不同憎兽。
import React, { Component } from 'react';
class App extends Component {
render() {
var user = { name: 'Robin' };
return (
<div>
<h1>{user.name}</h1>
</div>
);
}
}
export default App;
接下來的問題是:如何渲染項目列表冷离?React 沒有提供特定的 API(例如 HTML 標(biāo)記的自定義屬性)用于渲染項目列表。我們可以使用純 JavaScript 代碼來迭代項目列表纯命,并返回每個項目的 HTML西剥。
import React, { Component } from 'react';
class App extends Component {
render() {
var users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<ul>
{users.map(function (user) {
return <li>{user.name}</li>;
})}
</ul>
);
}
}
export default App;
通過使用 JavaScript 箭頭函數(shù),你可以擺脫箭頭函數(shù)體和 return 語句亿汞,讓渲染輸出更加簡潔瞭空。
import React, { Component } from 'react';
class App extends Component {
render() {
var users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
);
}
}
export default App;
很快,每個 React 開發(fā)人員都習(xí)慣了 JavaScript 內(nèi)置的 map() 方法疗我。對數(shù)組進(jìn)行 map 并返回每個項的渲染輸出咆畏,這樣做非常有用。在某系情況下吴裤,結(jié)合使用 filter() 或 reduce() 會更有用旧找,而不只是為每個被 map 的項渲染輸出。
import React, { Component } from 'react';
class App extends Component {
render() {
var users = [
{ name: 'Robin', isDeveloper: true },
{ name: 'Markus', isDeveloper: false },
];
return (
<ul>
{users
.filter(user => user.isDeveloper)
.map(user => <li>{user.name}</li>)
}
</ul>
);
}
}
export default App;
通常麦牺,React 開發(fā)人員習(xí)慣于使用 JavaScript 的這些內(nèi)置函數(shù)钮蛛,而不必使用 React 特定的 API。它只是 HTML 中的 JavaScript剖膳。
React 中的var魏颓、let和 const
對于 React 的新手來說,使用 var吱晒、let 和 const 來聲明變量可能也會給他們造成混淆甸饱,雖然它們不是 React 相關(guān)的。我會嘗試在教學(xué)中盡早介紹 let 和 const,并從在 React 組件中交替使用 const 和 var 開始:
import React, { Component } from 'react';
class App extends Component {
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
);
}
}
export default App;
然后我給出了一些使用這些變量聲明的經(jīng)驗法則:
- 不要使用 var叹话,因為 let 和 const 更具體
- 默認(rèn)使用 const偷遗,因為它不能被重新分配或重新聲明
- 如果要重新賦值變量則使用 let
let 通常用于 for 循環(huán)中,const 通常用于保持 JavaScript 變量不變渣刷。盡管在使用 const 時可以修改對象和數(shù)組的內(nèi)部屬性鹦肿,但變量聲明表達(dá)了保持變量不變的意圖。
React 中的三元運(yùn)算符
如果要通過 if-else 語句進(jìn)行條件渲染該怎么辦辅柴?我們不能直接在 JSX 中使用 if-else 語句箩溃,但可以從渲染函數(shù)中提前返回。如果不需要顯示內(nèi)容碌嘀,返回 null 在 React 中是合法的涣旨。
import React, { Component } from 'react';
class App extends Component {
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
const showUsers = false;
if (!showUsers) {
return null;
}
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
);
}
}
export default App;
不過,如果要在返回的 JSX 中使用 if-else 語句股冗,可以使用 JavaScript 的三元運(yùn)算符:
import React, { Component } from 'react';
class App extends Component {
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
const showUsers = false;
return (
<div>
{
showUsers ? (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
) : (
null
)
}
</div>
);
}
}
export default App;
如果你只返回條件渲染的一個方面霹陡,可以使用 && 運(yùn)算符:
import React, { Component } from 'react';
class App extends Component {
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
const showUsers = false;
return (
<div>
{
showUsers && (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
)
}
</div>
);
}
}
export default App;
詳細(xì)的原理我就不說了,但如果你感興趣止状,可以在這篇文章(https://www.robinwieruch.de/conditional-rendering-react/)里了解到更詳細(xì)的信息以及與條件渲染相關(guān)的其他技術(shù)烹棉。React 中的條件渲染告訴我們,大多數(shù) React 都是與 JavaScript 有關(guān)怯疤,而不是 React 特定的內(nèi)容浆洗。
React 中的導(dǎo)入和導(dǎo)出語句
在 JavaScript 中,我們可以通過 import 和 export 語句來導(dǎo)入和導(dǎo)出在 JavaScript ES6 文件中定義的功能集峦。在開始你的第一個 React 應(yīng)用程序之前伏社,這些 import 和 export 語句是另一個需要了解的話題。create-react-app 項目已經(jīng)在使用 import 語句:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
這對初始項目來說非常棒塔淤,因為它為你提供了一個全面的體驗摘昌,可以導(dǎo)入和導(dǎo)出其他文件。不過高蜂,在剛開始接觸 React 時聪黎,我會試著避免這些導(dǎo)入。相反备恤,我會專注于 JSX 和 React 組件稿饰。在需要將 React 組件或 JavaScript 函數(shù)分離到單獨(dú)的文件中時,才需要引入導(dǎo)入和導(dǎo)出語句烘跺。
那么這樣使用這些導(dǎo)入和導(dǎo)出語句呢?假設(shè)你想要導(dǎo)出一個文件的如下變量:
const firstname = 'Robin';
const lastname = 'Wieruch';
export { firstname, lastname };
然后脂崔,你可以通過第一個文件的相對路徑將它們導(dǎo)入到另一個文件中:
import { firstname, lastname } from './file1.js';
console.log(firstname);
// output: Robin
因此滤淳,它不一定只是與導(dǎo)入或?qū)С鼋M件或函數(shù)有關(guān),它可以是共享可分配給變量的所有東西(我們只談 JS)砌左。你還可以將另一個文件導(dǎo)出的所有變量作為一個對象導(dǎo)入:
import * as person from './file1.js';
console.log(person.firstname);
// output: Robin
導(dǎo)入可以有別名脖咐。當(dāng)從多個文件導(dǎo)入具有相同導(dǎo)出名稱的功能時铺敌,就需要用到別名。
import { firstname as username } from './file1.js';
console.log(username);
// output: Robin
之前所有的例子都是命名的導(dǎo)入和導(dǎo)出屁擅。除此之外偿凭,還有默認(rèn)的導(dǎo)入和導(dǎo)出。它可以用于以下一些場景:
- 導(dǎo)出和導(dǎo)入單個功能派歌;
- 強(qiáng)調(diào)一個模塊導(dǎo)出 API 的主要功能弯囊;
- 作為導(dǎo)入功能的后備。
const robin = {
firstname: 'Robin',
lastname: 'Wieruch',
};
export default robin;
在使用默認(rèn)導(dǎo)入時可以省略大括號:
import developer from './file1.js';
console.log(developer);
// output: { firstname: 'Robin', lastname: 'Wieruch' }
此外胶果,導(dǎo)入名稱可以與導(dǎo)出的默認(rèn)名稱不同匾嘱。你還可以將它與命名的 export 和 import 語句一起使用:
const firstname = 'Robin';
const lastname = 'Wieruch';
const person = {
firstname,
lastname,
};
export {
firstname,
lastname,
};
export default person;
在另一個文件中導(dǎo)入:
import developer, { firstname, lastname } from './file1.js';
console.log(developer);
// output: { firstname: 'Robin', lastname: 'Wieruch' }
console.log(firstname, lastname);
// output: Robin Wieruch
// 你還可以節(jié)省一些行,直接導(dǎo)出命名的變量:
export const firstname = 'Robin';
export const lastname = 'Wieruch';
這些是 ES6 模塊的主要功能早抠。它們可以幫助你更好地組織代碼霎烙,并設(shè)計出可重用的模塊 API。
React 中的庫
React 只是應(yīng)用程序的視圖層蕊连。React 提供了一些內(nèi)部狀態(tài)管理悬垃,但除此之外,它只是一個為瀏覽器渲染 HTML 的組件庫甘苍。API(例如瀏覽器 API尝蠕、DOM API)、JavaScript 或外部庫可以為 React 添加額外的東西羊赵。為 React 應(yīng)用程序選擇合適的庫并不是件容易的事趟佃,但一旦你對不同的庫有了很好的了解,就可以選擇最適合你的技術(shù)棧的庫昧捷。
例如闲昭,我們可以使用 React 原生的獲取數(shù)據(jù)的 API 來獲取數(shù)據(jù):
import React, { Component } from 'react';
class App extends Component {
state = {
data: null,
};
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
...
}
}
export default App;
但你也可以使用另一個庫來獲取數(shù)據(jù)按价,Axios 就是這樣的一個流行庫:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
state = {
data: null,
};
componentDidMount() {
axios.get('https://api.mydomain.com')
.then(data => this.setState({ data }));
}
render() {
...
}
}
export default App;
因此理郑,一旦你知道了需要解決什么問題期揪,React 的生態(tài)系統(tǒng)就可以為你提供大量的解決方案飒炎。這可能與 React 本身無關(guān)月洛,而是有關(guān)了解如何選擇可用于彌補(bǔ) React 應(yīng)用程序的各種 JavaScript 庫号涯。
React 中的高階函數(shù)
高階函數(shù)是函數(shù)式編程中的一個非常棒的概念晒喷。在 React 中委可,了解這些函數(shù)是非常有意義的毒返,因為在某些時候你需要處理高階組件租幕,如果已經(jīng)了解了高階函數(shù),那么就可以更好地了解這些高階組件拧簸。
我們假設(shè)可以根據(jù)一個輸入字段的值對用戶列表進(jìn)行過濾劲绪。
import React, { Component } from 'react';
class App extends Component {
state = {
query: '',
};
onChange = event => {
this.setState({ query: event.target.value });
}
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<div>
<ul>
{users
.filter(user => this.state.query === user.name)
.map(user => <li>{user.name}</li>)
}
</ul>
<input
type="text"
onChange={this.onChange}
/>
</div>
);
}
}
export default App;
我們并不總是希望通過提取函數(shù)的方式來實現(xiàn),因為這樣會增加不必要的復(fù)雜性。但是贾富,通過提取函數(shù)歉眷,我們可以對其進(jìn)行單獨(dú)的測試。因此颤枪,讓我們使用內(nèi)置的 filter 函數(shù)來實現(xiàn)這個例子汗捡。
import React, { Component } from 'react';
function doFilter(user) {
return query === user.name;
}
class App extends Component {
...
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<div>
<ul>
{users
.filter(doFilter)
.map(user => <li>{user.name}</li>)
}
</ul>
<input
type="text"
onChange={this.onChange}
/>
</div>
);
}
}
export default App;
這個實現(xiàn)還起不到作用,因為 doFilter() 函數(shù)需要知道 state 的 query 屬性畏纲。我們可以通過另一個包裝函數(shù)來傳遞它扇住,也就是高階函數(shù)。
import React, { Component } from 'react';
function doFilter(query) {
return function (user) {
return query === user.name;
}
}
class App extends Component {
...
render() {
const users = [
{ name: 'Robin' },
{ name: 'Markus' },
];
return (
<div>
<ul>
{users
.filter(doFilter(this.state.query))
.map(user => <li>{user.name}</li>)
}
</ul>
<input
type="text"
onChange={this.onChange}
/>
</div>
);
}
}
export default App;
基本上霍骄,高階函數(shù)是可以返回函數(shù)的函數(shù)台囱。通過使用 JavaScript ES6 的箭頭函數(shù),你可以讓高階函數(shù)變得更簡潔读整。此外簿训,這種簡化的方式讓將函數(shù)組合成函數(shù)變得更吸引人。
const doFilter = query => user =>
query === user.name;
現(xiàn)在可以將 doFilter() 函數(shù)從文件中導(dǎo)出米间,并將其作為純(高階)函數(shù)進(jìn)行單獨(dú)的測試强品。在了解了高階函數(shù)之后,就為學(xué)習(xí) React 的高階組件奠定了基礎(chǔ)屈糊。
將這些函數(shù)提取到 React 組件之外的(高階)函數(shù)中也助于單獨(dú)測試 React 的本地狀態(tài)管理的榛。
export const doIncrement = state =>
({ counter: state.counter + 1 });
export const doDecrement = state =>
({ counter: state.counter - 1 });
class Counter extends Component {
state = {
counter: 0,
};
onIncrement = () => {
this.setState(doIncrement);
}
onDecrement = () => {
this.setState(doDecrement);
}
render() {
return (
<div>
<p>{this.state.counter}</p>
<button onClick={this.onIncrement} type="button">Increment</button>
<button onClick={this.onDecrement} type="button">Decrement</button>
</div>
);
}
}
函數(shù)式編程非常強(qiáng)大,轉(zhuǎn)向函數(shù)式編程有助于了解 JavaScript 將函數(shù)作為一等公民所帶來的好處逻锐。
React 中的解構(gòu)和展開運(yùn)算符
JavaScript 中引入的另一種語言特性稱為解構(gòu)夫晌。通常情況下,你需要在組件的 state 或 props 中訪問大量的屬性昧诱。你可以在 JavaScript 中使用解構(gòu)賦值晓淀,而不是逐個將它們分配給變量。
// no destructuring
const users = this.state.users;
const counter = this.state.counter;
// destructuring
const { users, counter } = this.state;
這對函數(shù)式無狀態(tài)組件來說特別有用盏档,因為它們可以在函數(shù)簽名中收到 props 對象凶掰。通常,你用到的不是 props蜈亩,而是 props 里的內(nèi)容懦窘,因此你可以對函數(shù)簽名中已有的內(nèi)容進(jìn)行解構(gòu)。
// no destructuring
function Greeting(props) {
return <h1>{props.greeting}</h1>;
}
// destructuring
function Greeting({ greeting }) {
return <h1>{greeting}</h1>;
}
解構(gòu)也適用于 JavaScript 數(shù)組稚配。另一個很棒的特性是剩余解構(gòu)畅涂。它通常用于拆分對象的一部分屬性,并將剩余屬性保留在另一個對象中道川。
// rest destructuring
const { users, ...rest } = this.state;
uesrs 可以在 React 組件中渲染午衰,而剩余狀態(tài)可以用在其他地方苹丸。這就是 JavaScript 展開(spread)運(yùn)算發(fā)揮作用的地方,它可以將對象的剩余部分轉(zhuǎn)到下一個組件苇经。
JavaScript 多過 React
React 只提供了一個細(xì)小的 API 表面區(qū)域,因此開發(fā)人員必須習(xí)慣于 JavaScript 提供的所有功能宦言。這句話并非沒有任何理由:“成為 React 開發(fā)者也會讓你成為更好的 JavaScript 開發(fā)者”扇单。讓我們通過重構(gòu)一個高階組件來回顧一下學(xué)到的 JavaScript 的一些方面。
function withLoading(Component) {
return class WithLoading extends {
render() {
const { isLoading, ...props } = this.props;
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
}
}
};
}
這個高階組件用于顯示條件加載進(jìn)度條奠旺,當(dāng) isLoading 被設(shè)為 true 時蜘澜,就可以顯示加載進(jìn)度條,否則就渲染輸入組件响疚。在這里可以看到(剩余)解構(gòu)和展開運(yùn)算符的實際應(yīng)用鄙信。后者可以在渲染的 Component 中看到,因為 props 對象的剩余屬性被傳給了那個 Component忿晕。
讓高階組件變得更簡潔的第一步是將返回的 React 類組件重構(gòu)為函數(shù)式無狀態(tài)組件:
function withLoading(Component) {
return function ({ isLoading, ...props }) {
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
};
}
可以看到装诡,剩余解構(gòu)也可以被用在函數(shù)的簽名中。接下來践盼,使用 JavaScript ES6 箭頭函數(shù)讓高階組件變得更加簡潔:
const withLoading = Component => ({ isLoading, ...props }) => {
if (isLoading) {
return <p>Loading</p>;
}
return <Component { ...props } />;
}
通過使用三元運(yùn)算符可以將函數(shù)體縮短為一行代碼鸦采。因此可以省略函數(shù)體和 return 語句。
const withLoading = Component => ({ isLoading, ...props }) =>
isLoading
? <p>Loading</p>
: <Component { ...props } />
如你所見咕幻,高階組件使用的是各種 JavaScript 而不是 React 相關(guān)技術(shù):箭頭函數(shù)渔伯、高階函數(shù)、三元運(yùn)算符肄程、解構(gòu)和展開運(yùn)算符锣吼。