React 進階之選擇合適的組件類型

最近項目基本都是用 React,今天總結分享 React Component 常見的幾種形式,若是你在寫 React 時常常不知道怎麼拆分代碼,這篇文章或許對你有所幫助。html

原文連接: w3ctrain.com/2018/11/05/…前端

爲了更充分理解 React,先搞懂平時寫的 JSX 是什麼。初學的時候有比較大困惑,這是一門新語言嗎?大部分人是匆匆掃過文檔就開始開發。經過 babel-presets-react 處理能看到,其實 JSX 只是語法糖,最終在瀏覽器跑的仍是 JS。React Component 最終都經過 React.createElement 建立。總之,寫 React 其實就是在寫 JSreact

jsx

SFC (Stateless Functional Component)

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 ?瀏覽器

HOC (Higher-Order Components)

高階組件對於 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);
複製代碼

高階組件適合用來擴展功能,把這部分功能從業務組件中抽離出來,須要的套上,不須要的時候移除,對被包裹組件侵入性很是小。

Dynamic Component

有些業務場景下,在執行時才能肯定具體的標籤或者組件是什麼。在 React 的世界裏面,以大寫字母開頭會被當成動態組件加載,而小寫字母開頭會被認爲是 HTML DOM tag。

// Heading.js
render() {
    const { tag: Tag, children } = this.props;
    return <Tag>{ children }</Tag>
}
複製代碼

根據萬物皆爲 JS 理論,只要傳入不一樣的 tag 標籤,就會渲染出不一樣的 heading 標籤。

heading

咱們經常使用這種方式,在後端配置組件和數據,前端讀取配置以後渲染出對應的內容。

FaCC(Functions as Child Components)

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>    
}

複製代碼

再以最近開發的例子,分享組件拆分的好處。

需求:開發倒計時組件,運營配置倒計時結束時間,倒計時初始化時間從服務端獲取,結束以前顯示倒計時,倒計時結束以後作對應的操做,好比切換倒計時爲其餘組件。

組件拆分:

  • 一個業務層容器組件,負責統籌,處理業務邏輯。(統籌規劃,倒計時完要幹什麼,你直接跟我說)
  • 一個通用‘倒計時’的組件,向服務端輪詢系統時間,計算當前剩餘時間,FaCC 的形式提供給 children。(我數個人羊,大家愛幹嗎幹嗎)
  • 一個倒計時UI組件,對剩餘時間格式化以及 UI 展現。(剩下多少,時間怎麼來的,與我無關,給我什麼我展現什麼)

僞代碼:

// 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>
    );
}
複製代碼

最終獲得的結果是:

Count Down

與此同時

  • 代碼結構清晰,組件之間各司其職。
  • 組件可複用性強。
  • 單元測試簡單,每一個組件都只測試自身的邏輯。

推薦閱讀

相關文章
相關標籤/搜索