AntDesign 第一章(3) --------第一個元件
第一個元件
上一節,我們寫了一個頁面元件。
export default () => {
return <div>hello world</div>;
}
頁面插入這個元件以後,就會顯示 Hello World 。但是,上一節並沒有解釋這段程式碼的含義,只是讓大家照著拷貝。
本節就來解釋什麼是元件,以及怎樣寫元件。這是 React 和 Ant Design 的使用基礎,只有學會了這些內容,才能理解後面的知識。如果你對 React 已經有所瞭解,可以跳過這一節。
元件是什麼?
按照功能劃分,一張網頁可以由多個互相獨立的功能單位組成,這種功能單位就叫做“元件”(component)。比如,典型的網頁分成頁頭、內容、頁尾三個部分,就可以寫成三個元件:Header、Content、Footer。這些元件拼裝在一起,就構成了一張頁面。
元件內部還可以包含下一級的元件。比如,“文章”元件內部可以包含“表單”元件,“表單”元件內部又可以包含“按鈕”元件。
元件的好處有很多,下面是其中幾點。
- 有利於細化 UI 邏輯,不同的元件負責不同的功能點。
- 有利於程式碼複用,多個頁面可以使用同樣的元件。
- 有利於人員分工,不同的工程師負責不同的元件。
React 的核心概念就是元件。這個框架的主要功能,就是定義了一套編寫和使用元件的規範。本節開頭的那段程式碼,就是定義了一個最簡單的元件。
export default () => { return <div>hello world</div>; }
上面程式碼採用 ES6 模組格式,預設輸出一個箭頭函式。這個函式執行後,返回了一段 JSX 程式碼(後文介紹),代表 hello world 區塊。這就是最簡單的 React 元件的寫法。
JSX 語法
第一次接觸 React 的使用者,都會有一個共同的疑問,為什麼 JavaScript 程式碼裡面可以直接插入 HTML 程式碼。這難道不會報錯嗎?
回答是如果你把上面的程式碼,放到 JavaScript 引擎裡面執行,確實會報錯。因為這種語法不是 JavaScript,而是 React 為了方便開發者自創的 JSX 語法。
JSX 可以被 Babel 轉碼器轉為正常的 JavaScript 語法。上面的 JSX 語法轉碼後的結果如下。
exports.default = function () {
return React.createElement(
"div",
null,
"hello world"
);
};
兩種寫法一比較,就會發現對於複雜的 UI 元件來說,JSX 更易寫也更易讀。所以,幾乎所有的 React 開發者都使用 JSX 語法。
JSX 語法的特點就是,凡是使用 JavaScript 的值的地方,都可以插入這種類似 HTML 的語法。
const element = <h1>Hello, world!</h1>;
這裡有兩個注意點。一是所有 HTML 標籤必須是閉合的,如果寫成<h1>Hello
就會報錯。如果是那種沒有閉合語法的標籤,必須在標籤尾部加上斜槓,比如<img src="" />
。二是任何 JSX 表示式,頂層只能有一個標籤,也就是說只能有一個根元素。下面的寫法會報錯。
// 報錯
const element = <h1>hello</h1><h1>world</h1>;
// 不報錯
const element = <div><h1>hello</h1><h1>world</h1></div>;
上面程式碼中,第一種寫法會報錯,因為根元素的位置有兩個並列的<h1>
標籤。在它們外面再包一層,就不會報錯了。
一般來說,HTML 原生標籤都使用小寫,開發者自定義的元件標籤首字母大寫,比如<MyComponent/>
。
JSX 語法允許 HTML 與 JS 程式碼混寫。
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
上面程式碼中,<h1>
標籤的文字內容部分嵌入了 JS 程式碼。每次生成的文字,取決於函式formatName(user)
執行的結果。
可以看到,JSX 語法的值的部分,只要使用了大括號{}
,就表示進入 JS 的上下文,可以寫入 JS 程式碼。
更多介紹請參考官方文件。
React 元件語法
雖然輸出 JSX 程式碼的函式就是一個 React 元件,但是這種寫法只適合那些最簡單的元件。更正式、更通用的元件寫法,要使用 ES6 類(class)的語法。
import React from 'react';
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
export default ShoppingList;
上面程式碼定義了一個 ShoppingList 元件。自定義的元件必須繼承React.Component
這個基類,然後必須有一個render
方法,給出元件的輸出。
使用 React 元件也很簡單,引入這個元件以後,就可以直接使用。假定上面的元件指令碼叫做shoppinglist.js
,那麼使用它的程式碼如下。
import React from 'React';
import ShoppingList from './shoppinglist.js';
class Content extends React.Component {
render() {
return (
<ShoppingList name="張三" />
);
}
}
export default Content;
上面程式碼中,我們新建了一個Content
元件,裡面使用了ShoppingList
元件。注意,由於這個元件除了name
引數,沒有其他內容,所以可以寫成<ShoppingList name="張三"/>
這種直接閉合的形式。否則,可以寫成下面的形式。
class Content extends React.Component {
render() {
return (
<ShoppingList name="張三">
{/* 插入的其他內容 */}
</ShoppingList>
);
}
}
元件的引數
上一節的<ShoppingList name="張三"/>
這行程式碼,ShoppingList
是元件名,name="張三"
表示這個元件的有一個name
引數,值為張三
。
元件內部,所有引數都放在this.props
屬性上面。通過this.props.name
就可以拿到傳入的值(張三)。
<h1>Shopping List for {this.props.name}</h1>
通過這種引數機制,React 元件可以接受外部訊息。
this.props
物件有一個非常特殊的引數this.props.children
,表示當前元件“包裹”的所有內容。比如,上面程式碼裡面的Shopping List for {this.props.name}
,就是<h1>
元素的this.props.children
。這個屬性在 React 裡面有很大的作用,它意味著元件內部可以拿到,使用者在元件裡面放置的內容。
我們來看一個例子。下面是一個元件,內部使用props.children
,獲取使用者傳入的內容。
const Picture = (props) => {
return (
<div>
<img src={props.src} />
{props.children}
</div>
)
}
下面就是使用時,怎麼向props.children
傳入內容。
render () {
return (
<div className='container'>
<Picture key={picture.id} src={picture.src}>
// 這裡放置的內容就是 props.children
</Picture>
</div>
)
}
元件的狀態
除了接受外部引數,元件內部也有不同的狀態。React 規定,元件的內部狀態記錄在this.state
這個物件上面。
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value}
</button>
);
}
}
上面程式碼中,元件Square
的構造方法constructor
裡面定義了當前狀態this.state
物件。Square 元件的這個物件只有一個value
屬性,一開始的值是null
。
使用者點選按鈕以後,onClick
監聽函式執行this.setState()
方法。React 使用這個方法,更新this.state
物件。這個方法有一個特點,就是每次執行以後,它會自動呼叫render
方法,導致 UI 更新。UI 裡面使用this.state.value
,輸出狀態值。隨著使用者點選按鈕,頁面就會顯示X
。
可以看到,這個例子裡面,內部狀態用來區分使用者是否點選了按鈕。
生命週期方法
元件的執行過程中,存在不同的階段。React 為這些階段提供了鉤子方法,允許開發者自定義每個階段自動執行的函式。這些方法統稱為生命週期方法(lifecycle methods)。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
componentDidUpdate() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
上面程式碼中,componentDidMount()
、componentWillUnmount()
和componentDidUpdate()
就是三個最常用的生命週期方法。其中,componentDidMount()
會在元件掛載後自動呼叫,componentWillUnmount()
會在元件解除安裝前自動呼叫,componentDidUpdate()
會在 UI 每次更新後呼叫(即元件掛載成功以後,每次呼叫 render 方法,都會觸發這個方法)。
後面的章節結合例項,會對這幾個方法有更詳細的介紹。
此外,還有三個生命週期方法,不是經常使用,這裡只需要簡單瞭解。
shouldComponentUpdate(nextProps, nextState)
:每當this.props
或this.state
有變化,在render
方法執行之前,就會呼叫這個方法。該方法返回一個布林值,表示是否應該繼續執行render
方法,即如果返回false
,UI 就不會更新,預設返回true
。元件掛載時,render
方法的第一次執行,不會呼叫這個方法。static getDerivedStateFromProps(props, state)
:該方法在render
方法執行之前呼叫,包括元件的第一次記載。它應該返回一個新的 state 物件,通常用在元件狀態依賴外部輸入的引數的情況。getSnapshotBeforeUpdate()
:該方法在每次 DOM 更新之前呼叫,用來收集 DOM 資訊。它返回的值,將作為引數傳入componentDidUpdate()
方法。