React 設計模式和場景分析

Kygo on live

這一周連續(xù)發(fā)表了兩篇關于 React 的文章:

其中涉及到 React 組件復用烁试、輪子設計相關話題匠楚,并配合相關場景實例進行了分析只锭。這些內容都算是 React 設計模式少梁,一提到 Design Patterns我纪,讀者大可不必恐懼麻顶,事實上這都是 React 開發(fā)應用靈活性的體現(xiàn)巷折。今天這篇文章遍略,我們繼續(xù)通過一個場景惧所,循序漸進,通過一步步優(yōu)化設計來進行加深理解绪杏。

場景介紹

頁面展現(xiàn)

屏幕左側大面積展現(xiàn)區(qū)塊內容下愈,點擊 continue 按鈕,切換為下條內容信息蕾久;右側是一個導航條势似,指示當前區(qū)塊展示信息條目。

如果看 Gif 圖不過癮,可以到 CodeSandbox 進行在線了解履因。

具體代碼結構為:

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

Stepper 組件 'stage' prop 表示默認開始第幾個區(qū)塊障簿,同時具用同名 'stage' 狀態(tài)。stage 在這里表示左側一個個內容區(qū)塊栅迄。
handleClick 方法對 this.stata.stage 進行切換站故。

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 組件霞篡。
這樣的代碼運行良好世蔗,但是在復用性和靈活性上有一些問題。比如:

  • 如果我們需要切換 Progress 和 Steps 組件(左右)展示順序怎么辦朗兵?
  • 如果我們的 Stepper 需要承載更多的 stages 怎么辦污淋?
  • 如果我們需要更改某個 stage 內容怎么辦?
  • 如果我們想要切換 stages 順序該怎么辦余掖?

現(xiàn)有代碼基礎上寸爆,這些問題都可以解決。但是需要重新更改組件編寫內容盐欺。如果某天又新增或者調整了需求赁豆,組件內容同樣又需要改寫。

接下來冗美,我們用另一種方式實現(xiàn)需求魔种,使得代碼更加靈活,復用性更強粉洼。

重新設計

仔細觀察 Stepper 組件:它包含了當前區(qū)塊 stage节预,以及一個更改 stage 的方法,渲染了兩個子組件属韧。

我們使用 Function as Child Component 手段安拟,將 Stepper 組件重構。(如果對 Function as Child Component 不熟悉宵喂,請參考我之前文章 組件復用那些事兒 - React 實現(xiàn)按需加載輪子

如下圖:

Function as Child Component 重構

Progress 和 Steps 組件不再直接出現(xiàn)在 Stepper 組件的 render 方法中糠赦。我們使用 this.props.children 對 Stepper 組件的所有子組件進行渲染。這樣 Stepper 組件渲染的內容更加靈活锅棕。

但是僅僅這樣的修改是不可能完成需求的拙泽,當用戶點擊 continue 按鈕,stage 并不會進行切換裸燎。因為 Progress 和 Steps 組件無法再通過 props 感知 stage 和 handleClick 方法顾瞻。

為了解決這個問題,我們可以手動遍歷 Stepper 組件的子節(jié)點顺少,并對相應 props 一一注入。如下代碼:

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

借助 React.Children.map 進行子節(jié)點遍歷,并通過 React.cloneElement 方法對子組件進行拷貝脆炎,這個方法通過第二個參數(shù)梅猿,具有添加額外 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 組件中創(chuàng)建兩個 static 方法弧烤,并賦值給 Progress 組件和 Steps 組件:

static Progress = Progress;
static Steps = Steps

現(xiàn)在忱屑,在 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;

這樣的好處體現(xiàn)在不用一次次地 import 進來 Progress 組件和 Steps 組件,它們都將作為 Stepper 的靜態(tài)屬性出現(xiàn)暇昂。我個人并不是很喜歡這種做法莺戒。

使用 React Transition Group

我們使用 React Transition Group 對 Steps 組件內容添加過渡動畫。只有當 props.num 與 this.props.stage 相等時急波,區(qū)塊內容設置為可見:

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 排列順序等都可以隨意搭配赏寇。

重構代碼以及效果可以訪問這里查看绘盟。

思考及待續(xù)

如果你覺得上述代碼完美無懈可擊,那顯然想簡單了扁耐。需求是變化多端的番捂,如果我們想在 Steps 區(qū)塊上,加一個大標題呢渠退?

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 組件的直接唯一子節(jié)點了碎乃,那預期之中的 props 自然又一次無法取得姊扔!

問題也不僅僅于此。筆者本人不是很喜歡類似 React.cloneElement 頂層 API梅誓,除了偏好以外恰梢,也有一個難以規(guī)避的問題:在使用 React.cloneElement 擴充 props 時佛南,如果出現(xiàn) props 命名沖突怎么辦?

比如一個 <input> 遇見了命名為 value 的 prop嵌言,后果可想而知嗅回。

那么問題來了,是否有更優(yōu)雅高效的方法解決上述問題摧茴?或者绵载,是否有更好的方式,實現(xiàn)更靈活的設計苛白?

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

本文源于:How To Master Advanced React Design Patterns购裙,部分內容有改動懂版。

廣告時間:
如果你對前端發(fā)展,尤其對 React 技術棧感興趣:我的新書中缓窜,也許有你想看到的內容定续。關注作者 Lucas HC,新書出版將會有送書活動禾锤。

Happy Coding!

PS: 作者 Github倉庫知乎問答鏈接 歡迎各種形式交流私股!

我的其他幾篇關于React技術棧的文章:

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

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

組件復用那些事兒 - React 實現(xiàn)按需加載輪子

通過實例,學習編寫 React 組件的“最佳實踐”

React 組件設計和分解思考

從 React 綁定 this恩掷,看 JS 語言發(fā)展和框架設計

做出Uber移動網(wǎng)頁版還不夠 極致性能打造才見真章**

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末倡鲸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黄娘,更是在濱河造成了極大的恐慌峭状,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼争,死亡現(xiàn)場離奇詭異优床,居然都是意外死亡,警方通過查閱死者的電腦和手機誓焦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門胆敞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杂伟,你說我怎么就攤上這事移层。” “怎么了赫粥?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵观话,是天一觀的道長。 經(jīng)常有香客問我越平,道長频蛔,這世上最難降的妖魔是什么灵迫? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮晦溪,結果婚禮上龟再,老公的妹妹穿的比我還像新娘。我一直安慰自己尼变,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布浆劲。 她就那樣靜靜地躺著嫌术,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牌借。 梳的紋絲不亂的頭發(fā)上度气,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音膨报,去河邊找鬼磷籍。 笑死,一個胖子當著我的面吹牛现柠,可吹牛的內容都是我干的院领。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼够吩,長吁一口氣:“原來是場噩夢啊……” “哼比然!你這毒婦竟也來了?” 一聲冷哼從身側響起周循,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤强法,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后湾笛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饮怯,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年嚎研,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓖墅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘉赎,死狀恐怖置媳,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情公条,我是刑警寧澤拇囊,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站靶橱,受9級特大地震影響寥袭,放射性物質發(fā)生泄漏路捧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一传黄、第九天 我趴在偏房一處隱蔽的房頂上張望杰扫。 院中可真熱鬧,春花似錦膘掰、人聲如沸章姓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡伊。三九已至,卻和暖如春窒舟,著一層夾襖步出監(jiān)牢的瞬間系忙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工惠豺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留银还,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓洁墙,卻偏偏與公主長得像蛹疯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子热监,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容