簡評:組件(component)是 React 的核心,了解它們有助於構建好的設計結構。
什么是組件(component)
組件運行你將 UI 拆分為獨立的可重用的部分。和 JavaScript 函數類似,組件接收名為 props 的輸入並返回 React 元素,它描述(聲明)用戶界面應該是什么樣子的。這就是 React 被稱為聲明性 API 的原因,你只需要聲明你希望得到的 UI ,之后 React 負責具體的細節。
組件 API
當安裝 React 后,便可以使用 React 提供的 API,基本可以分成 5 種。
- render
- state
- props
- context
- lifecycle events
雖然組件可以使用所有的 API,但是通常一個組件只使用部分的 API,我們可以對使用不同 API 的組件進行細分,分成有狀態(stateful) 和無狀態(stateless) 兩種組件。
- 有狀態組件使用(render,state 和 生命周期相關事件)
- 無狀態組件使用 (render,props 和 context)
這樣將數據邏輯和 UI 表現層進行分離,通過組件之間划分職責可以創建更多可重用的組件,在構建可擴展的應用程序時尤為重要。
組件模式
通常組件模式有以下幾種:
- Container
- Presentational
- 高階組件(Higher order components 【HOC’s】)
- Render callback
container
容器組件(container component )負責獲取數據然后渲染部分交給相應的子組件來負責。
容器是你的數據或邏輯層並利用 stateful API,使用生命周期事件你可以連接 state 到 redux 或 flux 的 storage 中。在容器組件的 render 方法中,你可以使用 Presentational 組件來渲染具體的樣式。
注意:由於容器組件需要使用 stateful api ,所以容器組件需要定義成類而不能是一個純函數。
我們來定義一個 組件 Greeting,他具有狀態,生命周期 componentDidMount 事件 和 render 方法。
class Greeting extends React.Component {
constructor() {
super();
this.state = {
name: "",
};
}
componentDidMount() {
// AJAX
this.setState(() => {
return {
name: "William",
};
});
}
render() {
return (
<div>
<h1>Hello! {this.state.name}</h1>
</div>
);
}
}
現在我們對 Greeting 進行改進,將其分離成容器組件(container component)和展示組件(presentational component)。
Presentational
Presentational components 使用 props,render,和 context (stateless API's) ,並且由於不需要使用生命周期相關api,我們可以使用純函數來簡化表述它們:
const GreetingCard = (props) => {
return (
<div>
<h1>Hello! {props.name}</h1>
</div>
)
}
Presentational components 只從 props 獲取數據和回調函數,props 由容器組件提供。
容器組件和展示組件各自將數據/邏輯和展示部分封裝到各自的組件中:
const GreetingCard = (props) => {
return (
<div>
<h1>{props.name}</h1>
</div>
)
}
class Greeting extends React.Component {
constructor() {
super();
this.state = {
name: "",
};
}
componentDidMount() {
// AJAX
this.setState(() => {
return {
name: "William",
};
});
}
render() {
return (
<div>
<GreetingCard name={this.state.name} />
</div>
);
}
}
高階組件(Higher order components【HOC’s】)
高階組件是一個接收一個組件作為參數然后返回全新組件的函數。
這是一種強大的模式,我們可以對輸入組件的 props 進行修改(增刪改查)然后返回全新的修改后的組件,例如 react-router-v4 的 withRouter() 方法可以包裝任何自定義組件,將 react-router 的 history,location,match 三個對象傳入,不需要一級級傳入。例如 Redux,你可以使用 connect({})() 方法來將展示組件和 store 中的數據進行連接。
代碼演示:
import {withRouter} from 'react-router-dom';
class App extends React.Component {
constructor() {
super();
this.state = {path: ''}
}
componentDidMount() {
let pathName = this.props.location.pathname;
this.setState(() => {
return {
path: pathName,
}
})
}
render() {
return (
<div>
<h1>Hi! I'm being rendered at: {this.state.path}</h1>
</div>
)
}
}
export default withRouter(App);
導出組件時,我使用 react-router-v4 的 withRouter() 來封裝它。在 componentDidMount 這個生命周期中,我們使用 this.props.location.pathname 來更新我們的 state,由於我們使用了 withRouter 高階組件,我們可以直接訪問 this.props.locationlocation,而不需要直接將 location 作為 props 直接傳入,非常方便。
Render callbacks
與高階組件類似,render callbacks 或 render props 可以用來重用邏輯。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
increment = () => {
this.setState(prevState => {
return {
count: prevState.count + 1,
};
});
};
render() {
return (
<div onClick={this.increment}>{this.props.children(this.state)}</div>
);
}
}
class App extends React.Component {
render() {
return (
<Counter>
{state => (
<div>
<h1>The count is: {state.count}</h1>
</div>
)}
</Counter>
);
}
}
在 Counter 類中,我們 render 中嵌入 this.props.childrn 並將 this.state 作為參數。在 App 類下面,我們可以將我們組件包裝在 Counter 組件中。
Counter 組件的本質是暴露了 children 這個外部屬性,將 children 具體的渲染細節交個 Counter 的使用者,使用的時候只需要將組件傳入到 Counter 的 children 中,當然可以使用其他參數,如果 children 不夠的話。例如實現一個聊天列表每條消息有頭像和消息內容,具體頭像是圓是方,具體消息內容是文字是圖片,都交給了外部使用者。