最近項目基本都是用 React,今天總結分享 React Component 常見的幾種形式,若是你在寫 React 時常常不知道怎麼拆分代碼,這篇文章或許對你有所幫助。html
原文連接: w3ctrain.com/2018/11/05/…前端
爲了更充分理解 React,先搞懂平時寫的 JSX 是什麼。初學的時候有比較大困惑,這是一門新語言嗎?大部分人是匆匆掃過文檔就開始開發。經過 babel-presets-react 處理能看到,其實 JSX 只是語法糖,最終在瀏覽器跑的仍是 JS。React Component 最終都經過 React.createElement 建立。總之,寫 React 其實就是在寫 JS。react
React 可使用 Function 來建立 Component,這類 Component 沒有 lifecycle, 內部不維護 state,只要傳入的 props 有變化則進行從新渲染。git
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
複製代碼
用箭頭函數的寫法還更加簡潔。redux
const Welcome = props => <h1>Hello, {props.name}</h1>;
複製代碼
上面兩種形式生成 es5 代碼都是同樣的。後端
var Welcome = function Welcome(props) {
return _react2.default.createElement(
"h1",
null,
"Hello, ",
props.name
);
};
複製代碼
SFC 的特色是純粹只作 render,代碼簡短沒有其餘條件分支,而且相比 class Component 編譯後的代碼量會少一些。api
尷尬的是,在 React 16.7 react hooks 出來以後,SFC 這個名字有歧義了,由於用上 useState,SFC 也能夠有 local state, 一樣能夠擁有 lifecycle。再稱之爲 Stateless Components 就很尷尬,更名叫 FC ?瀏覽器
高階組件對於 Vue 開發者來講應該是個陌生的概念(不知道,我用 Vue 的時候沒見過相似的用法)。從代碼上看,高階組件就是一個方法,傳入一個組件,返回另外一個組件。babel
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <WrappedComponent {...this.props} />; } } } 複製代碼
最多見的高階組件是 react-redux 裏面的 connect 方法,經過傳入 組件和 map*ToProps 方法,讓組件和 store 鏈接。組件內部就能夠直接經過 props 得到 connect 以後的值。app
exprot default connect(
mapStateToProps,
mapDispatchToProps,
)(Component);
複製代碼
高階組件適合用來擴展功能,把這部分功能從業務組件中抽離出來,須要的套上,不須要的時候移除,對被包裹組件侵入性很是小。
有些業務場景下,在執行時才能肯定具體的標籤或者組件是什麼。在 React 的世界裏面,以大寫字母開頭會被當成動態組件加載,而小寫字母開頭會被認爲是 HTML DOM tag。
// Heading.js
render() {
const { tag: Tag, children } = this.props;
return <Tag>{ children }</Tag>
}
複製代碼
根據萬物皆爲 JS 理論,只要傳入不一樣的 tag 標籤,就會渲染出不一樣的 heading 標籤。
咱們經常使用這種方式,在後端配置組件和數據,前端讀取配置以後渲染出對應的內容。
React children 還能夠是 Function 類型,若是直接調用它會什麼寫法? 好比封裝一個 Loading 組件,會給 children 提供 loading 參數,業務組件再根據 loading 判斷須要 render 什麼內容。
class LoadArea extends Component {
state = {
loading: true,
};
componentDidMount() {
asyncFunc()
.then(() => {
this.setState({
loading: false,
})
})
.catch(() => {
this.setState({
loading: false,
})
})
}
render() {
return (
<React.Fragment> {this.props.children({ ...this.props, ...this.state, })} </React.Fragment> ); } } 複製代碼
用法
render() {
<LoadingArea>
({ loading }) => {
loading
? <Wating /> : <Main /> } </LoadingArea>
}
複製代碼
一樣的,最終執行時都是 JS,沒有什麼好奇怪的。
React 16.* 新版本的 Conext.Consumer 就是採用了這種寫法。
render() {
<ThemeContext.Provider value={this.state.theme}>
...
<ThemeContext.Consumer>
{({theme}) => (
<button
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
...
</ThemeContext.Provider>
}
複製代碼
再以最近開發的例子,分享組件拆分的好處。
需求:開發倒計時組件,運營配置倒計時結束時間,倒計時初始化時間從服務端獲取,結束以前顯示倒計時,倒計時結束以後作對應的操做,好比切換倒計時爲其餘組件。
組件拆分:
僞代碼:
// CountDownContainer.js
render() {
const {
endTime,
renderSomethingAfterCountDown,
} = this.props;
return (
<TimeLeftProvider endTime={endTime} > {seconds => ( seconds > 0 ? <CountDown {...this.props} remainingSeconds={seconds} /> : renderSomethingAfterCountDown() )} </TimeLeftProvider> ); } 複製代碼
// TimeLeftProvider.js
export default class TimeLeftProvider extends PureComponent {
static propTypes = {
children: PropTypes.func,
endTime: PropTypes.number,
}
// ...
componentDidMount() {
this.poll();
}
poll() {
queryServerTime();
this.pollTimer = setInterval(() => {
queryServerTime();
}, pollInterval * 1000);
}
countDown() {
setInterval(() => {
this.setState(prevState => ({
remainingSeconds: prevState.remainingSeconds - 1,
}));
}, 1000);
}
render() {
const { remainingSeconds, reliable } = this.state;
return this.props.children(remainingSeconds, reliable);
}
}
複製代碼
// CountDown.js
function CountDown(props) {
const {
remainingSeconds,
} = props;
const numbers = formatSeconds(remainingSeconds);
const inputs = ['days', 'hours', 'minutes', 'seconds'];
return (
<div styleName={cls}> { inputs.map(key => ({ label: key, number: numbers[key], })).map( //... ) } </div>
);
}
複製代碼
最終獲得的結果是: