React 設計模式和場景分析

這一週連續發表了兩篇關於 React 的文章:前端

其中涉及到 React 組件複用、輪子設計相關話題,並配合相關場景實例進行了分析。這些內容都算是 React 設計模式,一提到 Design Patterns,讀者大可沒必要恐懼,事實上這都是 React 開發應用靈活性的體現。今天這篇文章,咱們繼續經過一個場景,按部就班,經過一步步優化設計來進行加深理解。react

場景介紹

頁面展示

屏幕左側大面積展示區塊內容,點擊 continue 按鈕,切換爲下條內容信息;右側是一個導航條,指示當前區塊展現信息條目。git

若是看 Gif 圖不過癮,能夠到 CodeSandbox 進行在線瞭解。github

具體代碼結構爲:設計模式

class App extends Component {
  render() {
    return (
        <Stepper stage={1}/>
    );
  }
}
複製代碼

Stepper 組件 'stage' prop 表示默認開始第幾個區塊,同時具用同名 'stage' 狀態。stage 在這裏表示左側一個個內容區塊。 handleClick 方法對 this.stata.stage 進行切換。promise

class Stepper extends Component {
  state = {
    stage: this.props.stage
  }
  static defaultProps = {
    stage: 1
  }
  handleClick = () => {
    this.setState({ stage: this.state.stage + 1 })
  }
  render() {
    const { stage } = this.state;
    return (
      <div style={styles.container}>
        <Progress stage={stage}/>
        <Steps handleClick={this.handleClick} stage={stage}/>
      </div>
    );
  }
}
複製代碼

咱們看到,Stepper 組件包含 Progress 組件(左側導航)以及 Steps 組件。 這樣的代碼運行良好,可是在複用性和靈活性上有一些問題。好比:app

  • 若是咱們須要切換 Progress 和 Steps 組件(左右)展現順序怎麼辦?
  • 若是咱們的 Stepper 須要承載更多的 stages 怎麼辦?
  • 若是咱們須要更改某個 stage 內容怎麼辦?
  • 若是咱們想要切換 stages 順序該怎麼辦?

現有代碼基礎上,這些問題均可以解決。可是須要從新更改組件編寫內容。若是某天又新增或者調整了需求,組件內容一樣又須要改寫。框架

接下來,咱們用另外一種方式實現需求,使得代碼更加靈活,複用性更強。性能

從新設計

仔細觀察 Stepper 組件:它包含了當前區塊 stage,以及一個更改 stage 的方法,渲染了兩個子組件。學習

咱們使用 Function as Child Component 手段,將 Stepper 組件重構。(若是對 Function as Child Component 不熟悉,請參考我以前文章 組件複用那些事兒 - React 實現按需加載輪子

以下圖:

Function as Child Component 重構

Progress 和 Steps 組件再也不直接出如今 Stepper 組件的 render 方法中。咱們使用 this.props.children 對 Stepper 組件的全部子組件進行渲染。這樣 Stepper 組件渲染的內容更加靈活。

可是僅僅這樣的修改是不可能完成需求的,當用戶點擊 continue 按鈕,stage 並不會進行切換。由於 Progress 和 Steps 組件沒法再經過 props 感知 stage 和 handleClick 方法。

爲了解決這個問題,咱們能夠手動遍歷 Stepper 組件的子節點,並對相應 props 一一注入。以下代碼:

const children = React.Children.map(this.props.children, child => {
		return React.cloneElement(child, {stage, handleClick: this.handleClick})
	})
複製代碼

藉助 React.Children.map 進行子節點遍歷,並經過 React.cloneElement 方法對子組件進行拷貝,這個方法經過第二個參數,具備添加額外 props 的能力。Stepper 組件的 render 方法只須要具體應用:

const { stage } = this.state;
const children = React.Children.map(this.props.children, child => {
	return React.cloneElement(child, {stage, handleClick: this.handleClick})
})
return (
	<div style={styles.container}>
		{children}
	</div>
	);
複製代碼

這樣一來,應用又一次正確運轉!

class App extends Component {
  render() {
    return (
      <div>
        <Stepper stage={1}>
          <Progress />
          <Steps />
        </Stepper>
      </div>
    );
  }
}
複製代碼

一樣的手段,咱們也能夠應用到 Progress 組件當中。這裏再也不一一展開。

使用 Static Properties

值得一提的是,咱們可使用 Static Properties 加強代碼的可讀性。Static Properties 容許咱們在 class 當中直接對方法進行調用。首先,咱們在 Stepper 組件中建立兩個 static 方法,並賦值給 Progress 組件和 Steps 組件:

static Progress = Progress;
static Steps = Steps
複製代碼

如今,在 App.js 中咱們能夠直接:

import React, { Component } from 'react';
import Stepper from "./Stepper"

class App extends Component {
  render() {
    return (
      <Stepper stage={1}>
        <Stepper.Progress />
        <Stepper.Steps />
      </Stepper>
    );
  }
}
export default App;
複製代碼

這樣的好處體如今不用一次次地 import 進來 Progress 組件和 Steps 組件,它們都將做爲 Stepper 的靜態屬性出現。我我的並非很喜歡這種作法。

使用 React Transition Group

咱們使用 React Transition Group 對 Steps 組件內容添加過渡動畫。只有當 props.num 與 this.props.stage 相等時,區塊內容設置爲可見:

class Steps extends Component {
	render() {
		const {stage,handleClick} = this.props
		const children = React.Children.map(this.props.children, child => {
			console.log(child.props)
			return (
				stage === child.props.num &&
				<Transition appear={true} timeout={300} onEntering={entering} onExiting={exiting}>
					{child}
				</Transition>
			)
		})
		return (
			<div style={styles.stagesContainer}>
				<div style={styles.stages}>
					<TransitionGroup>
						{children}
					</TransitionGroup>
				</div>
				<div style={styles.stageButton}>
					<Button disabled={stage === 4} click={handleClick}>Continue</Button>
				</div>
			</div>
		);
	}
}
複製代碼

咱們也能夠給 Steps 組件添加任意個內容:

import Stepper from "./Stepper"

class App extends Component {
  render() {
    return (
        <Stepper stage={1}>
          <Stepper.Progress>
            <Stepper.Stage num={1} />
            <Stepper.Stage num={2} />
            <Stepper.Stage num={3} />
          </Stepper.Progress>
          <Stepper.Steps>
            <Stepper.Step num={1} text={"Stage 1"}/>
            <Stepper.Step num={2} text={"Stage 2"}/>
            <Stepper.Step num={3} text={"Stage 3"}/>
            <Stepper.Step num={4} text={"Stage 4"}/>
          </Stepper.Steps>
        </Stepper>
    );
  }
}
複製代碼

從新設計以後,整個應用變得更加靈活,複用性更強。咱們能夠指定任意個 stages,每個 stage 文本內容也能夠自定義設置,一樣 stages 排列順序等均可以隨意搭配。

重構代碼以及效果能夠訪問這裏查看。

思考及待續

若是你以爲上述代碼完美無懈可擊,那顯然想簡單了。需求是變化無窮的,若是咱們想在 Steps 區塊上,加一個大標題呢?

class App extends Component {
  render() {
    return (
        <Stepper stage={1}>
          <Stepper.Progress>
            <Stepper.Stage num={1} />
            <Stepper.Stage num={2} />
            <Stepper.Stage num={3} />
          </Stepper.Progress>
          <div>
            <div>Title</div>
            <Stepper.Steps>
              <Stepper.Step num={1} text={"Stage 1"}/>
              <Stepper.Step num={2} text={"Stage 2"}/>
              <Stepper.Step num={3} text={"Stage 3"}/>
              <Stepper.Step num={4} text={"Complete!"}/>
            </Stepper.Steps>
          </div>
        </Stepper>
    );
  }
}
複製代碼

如圖,

加入標題

這樣一來,Stepper.Steps 組件不再是 Stepper 組件的直接惟一子節點了,那預期之中的 props 天然又一次沒法取得!

問題也不只僅於此。筆者本人不是很喜歡相似 React.cloneElement 頂層 API,除了偏好之外,也有一個難以規避的問題:在使用 React.cloneElement 擴充 props 時,若是出現 props 命名衝突怎麼辦?

好比一個 input 碰見了命名爲 value 的 prop,後果可想而知。

那麼問題來了,是否有更優雅高效的方法解決上述問題?或者,是否有更好的方式,實現更靈活的設計?

答案必定是有的,我將會留在下一篇文章進行講解。

本文源於:How To Master Advanced React Design Patterns,部份內容有改動。

廣告時間: 若是你對前端發展,尤爲對 React 技術棧感興趣:個人新書中,也許有你想看到的內容。關注做者 Lucas HC,新書出版將會有送書活動。

Happy Coding!

PS: 做者 Github倉庫 和 知乎問答連接 歡迎各類形式交流!

個人其餘幾篇關於React技術棧的文章:

從setState promise化的探討 體會React團隊設計思想

React 應用設計之道 - curry 化妙用

組件複用那些事兒 - React 實現按需加載輪子

經過實例,學習編寫 React 組件的「最佳實踐」

React 組件設計和分解思考

從 React 綁定 this,看 JS 語言發展和框架設計

作出Uber移動網頁版還不夠 極致性能打造才見真章**

React+Redux打造「NEWS EARLY」單頁應用 一個項目理解最前沿技術棧真諦

相關文章
相關標籤/搜索