前言
最近項(xiàng)目基本都是用 React,今天總結(jié)分享 React Component 常見的幾種形式,如果你在寫 React 時(shí)經(jīng)常不知道怎么拆分代碼,這篇文章或許對(duì)你有所幫助。
React.Component是一個(gè)抽象基類。這意味著直接引用React.Component是毫無意義的。你可以實(shí)現(xiàn)一個(gè)它的子類,并且至少定義一個(gè)render()方法即可使用。
為了更充分理解 React,先搞懂平時(shí)寫的 JSX 是什么。初學(xué)的時(shí)候有比較大困惑,這是一門新語(yǔ)言嗎?大部分人是匆匆掃過文檔就開始開發(fā)。通過 babel-presets-react 處理能看到,其實(shí) JSX 只是語(yǔ)法糖,最終在瀏覽器跑的還是 JS。React Component 最終都通過 React.createElement 創(chuàng)建。 總之,寫 React 其實(shí)就是在寫 JS 。
SFC (Stateless Functional Component)
React 可以使用 Function 來創(chuàng)建 Component,這類 Component 沒有 lifecycle, 內(nèi)部不維護(hù) state,只要傳入的 props 有變化則進(jìn)行重新渲染。
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
用箭頭函數(shù)的寫法還更加簡(jiǎn)潔。
const Welcome = props => <h1>Hello, {props.name}</h1>;
上面兩種形式生成 es5 代碼都是一樣的。
var Welcome = function Welcome(props) { return _react2.default.createElement( "h1", null, "Hello, ", props.name ); };
SFC 的特點(diǎn)是純粹只做 render,代碼簡(jiǎn)短沒有其他條件分支,并且相比 class Component 編譯后的代碼量會(huì)少一些。
尷尬的是,在 React 16.7 react hooks 出來之后,SFC 這個(gè)名字有歧義了,因?yàn)橛蒙?useState,SFC 也可以有 local state, 同樣可以擁有 lifecycle。再稱之為 Stateless Components 就很尷尬,改名叫 FC ?
HOC (Higher-Order Components)
高階組件對(duì)于 Vue 開發(fā)者來說應(yīng)該是個(gè)陌生的概念(不知道,我用 Vue 的時(shí)候沒見過類似的用法)。從代碼上看,高階組件就是一個(gè)方法,傳入一個(gè)組件,返回另一個(gè)組件。
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 連接。組件內(nèi)部就可以直接通過 props 獲得 connect 之后的值。
exprot default connect( mapStateToProps, mapDispatchToProps, )(Component);
高階組件適合用來擴(kuò)展功能,把這部分功能從業(yè)務(wù)組件中抽離出來,需要的套上,不需要的時(shí)候移除,對(duì)被包裹組件侵入性非常小。
Dynamic Component
有些業(yè)務(wù)場(chǎng)景下,在執(zhí)行時(shí)才能確定具體的標(biāo)簽或者組件是什么。在 React 的世界里面,以大寫字母開頭會(huì)被當(dāng)成動(dòng)態(tài)組件加載,而小寫字母開頭會(huì)被認(rèn)為是 HTML DOM tag。
// Heading.js render() { const { tag: Tag, children } = this.props; return <Tag>{ children }</Tag> }
根據(jù)萬物皆為 JS 理論,只要傳入不同的 tag 標(biāo)簽,就會(huì)渲染出不同的 heading 標(biāo)簽。
我們常用這種方式,在后端配置組件和數(shù)據(jù),前端讀取配置之后渲染出對(duì)應(yīng)的內(nèi)容。
FaCC(Functions as Child Components)
React children 還可以是 Function 類型,如果直接調(diào)用它會(huì)什么寫法?
比如封裝一個(gè) Loading 組件,會(huì)給 children 提供 loading 參數(shù),業(yè)務(wù)組件再根據(jù) loading 判斷需要 render 什么內(nèi)容。
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> }
同樣的,最終執(zhí)行時(shí)都是 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> }
再以最近開發(fā)的例子,分享組件拆分的好處。
需求:開發(fā)倒計(jì)時(shí)組件,運(yùn)營(yíng)配置倒計(jì)時(shí)結(jié)束時(shí)間,倒計(jì)時(shí)初始化時(shí)間從服務(wù)端獲取,結(jié)束之前顯示倒計(jì)時(shí),倒計(jì)時(shí)結(jié)束之后做對(duì)應(yīng)的操作,比如切換倒計(jì)時(shí)為其他組件。
組件拆分:
偽代碼:
// 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> ); }
最終得到的結(jié)果是:
與此同時(shí)
總結(jié)
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com