精讀《React 八種條件渲染》

黃子毅發表於2018-06-19

1 引言

本期精讀的文章是:8 React conditional rendering methods

介紹了八種 React 條件渲染方式。

模版條件渲染非常常見,遇到的時候往往會隨機選擇一種方式使用,那麼怎麼寫會有較好的維護性呢?先一起了解下有哪八種條件渲染方式吧!

2 概述

IF/ELSE

既然 JSX 支援 js 與 html 混寫,那麼交替使用就能解決條件渲染的問題:

function render() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return <div />;
  }
}
複製程式碼

return null

如果不想渲染空元素,最好使用 null 代替空的 div

function render() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return null;
  }
}
複製程式碼

這樣對 React 渲染效率有提升。

元件變數

將元件賦值到變數,就可以在 return 前任意修改它了。

function render() {
  let component = null;

  if (renderComponent1) {
    component = <Component1 />;
  }

  return component;
}
複製程式碼

三元運算子

三元運算子的語法如下:

condition ? expr_if_true : expr_if_false
複製程式碼

用在 JSX 上也很方便:

function render() {
  return renderComponent1 ? <Component1 /> : null;
}
複製程式碼

但三元運算子產生巢狀時,理解成本會變得很高。

&&

這個是最常用了,因為程式碼量最少。

function render() {
  return renderComponent1 && <Component1 />;
}
複製程式碼

IIFE

IIFE 含義是立即執行函式,也就是如下程式碼:

(function myFunction(/* arguments */) {
  // ...
})(/* arguments */);
複製程式碼

當深陷 JSX 程式碼中,又想寫一大塊邏輯時,除了回到上方,還可以使用 IIFE:

function render() {
  return (
    <div>
      {(() => {
        if (renderComponent1) {
          return <Component1 />;
        } else {
          return <div />;
        }
      })()}
    </div>
  );
}
複製程式碼

子元件

這是 IIFE 的變種,也就是把這段立即執行函式替換成一個普通函式:

function render() {
  return (
    <div>
      <SubRender />
    </div>
  );
}

function SubRender() {
  if (renderComponent1) {
    return <Component1 />;
  } else {
    return <div />;
  }
}
複製程式碼

IF 元件

做一個條件渲染元件 IF 代替 js 函式的 if

<If condition={true}>
  <span>Hi!</span>
</If>
複製程式碼

這個元件實現也很簡單

const If = props => {
  const condition = props.condition || false;
  const positive = props.then || null;
  const negative = props.else || null;

  return condition ? positive : negative;
};
複製程式碼

高階元件

高階元件,就是返回一個新元件的函式,並且接收一個元件作為引數。

那麼我們就能在高階元件裡寫條件語句,返回不同的元件即可:

function higherOrderComponent(Component) {
  return function EnhancedComponent(props) {
    if (condition) {
      return <AnotherComponent {...props} />;
    }

    return <Component {...props} />;
  };
}
複製程式碼

3 精讀

這麼多方法都能實現條件渲染,那麼重點在於可讀性與可維護性。

比如通過呼叫函式實現元件渲染:

<div>{renderButton()}</div>
複製程式碼

看上去還是比較冗餘,如果使用 renderButton getter 定義,我們就可以這麼寫它:

<div>{button}</div>
複製程式碼

其實我們想要的就是 button,而不是 renderButton。那麼還可以進一步,乾脆封裝成 JSX 元件:

<div>
  <Button />
</div>
複製程式碼

是否要付出這些努力,取決於應用的複雜度。如果應用複雜度非常高,那你應當儘量使用最後一種封裝,讓每個檔案的邏輯儘量獨立、簡單。

如果應用複雜度比較低,那麼注意不要過度封裝,以免把自己繞進去。

所以看來這又是一個沒有固定答案的問題,選擇何種方式封裝,取決於應用複雜度。

應用複雜度

對任何程式碼封裝,都會增加這段 連線邏輯 的複雜度。

假定無論如何程式碼的複雜度都是恆定不變的,下面這段程式碼,連線複雜度為 0,而對於 render 函式而言,邏輯複雜度是 100:

function render() {
  if (renderComponent) {
    return isOk ? <Component1 /> : <Component2 />;
  } else {
    return <div />;
  }
}
複製程式碼

下面這段程式碼拆成了兩個函式,邏輯複雜度對 render SubComponent 來說都是 50,但連線複雜度是 50:

function render() {
  if (renderComponent) {
    return <SubComponent>;
  } else {
    return <div />;
  }
}

function SubComponent() {
  return isOk ? <Component1 /> : <Component2 />
}
複製程式碼

可以看到,我們通過函式拆分,降低了每個函式的邏輯複雜度,但卻提高了連線複雜度。

下面來做一個比較,我們假設一個正常的程式設計師,可以一次性輕鬆記憶 10 個函式。如果再多,函式之間的呼叫關係就會讓人摸不著頭腦。

應用較小時

在應用程式碼量比較小時,假設一共有 10 個函式,如果做了邏輯抽象,拆分出了 10 個子函式,那麼總邏輯複雜度不變,函式變成了 20 個。

此時小王要修改此專案,他需要找到關鍵程式碼的位置。

如果沒有做邏輯抽象,小王一下子就記住了 10 個函式,並且很快完成了需求。

如果應用做了邏輯抽象,他需要理解的邏輯複雜度是不變的,但是要讀的函式變成了 20 個。小王需要像偵探一樣線上索中不斷跳轉,他還是隻找了 10 個關鍵函式,但一共也就 20 個函式,邏輯並不複雜,這值得嗎?

小王心裡可能會嘀咕:簡單的邏輯瞎抽象,害我檔案找了半天!

應用較大時

此時應用程式碼量比較大,假設一共有 500 個函式,我們不考慮抽象後帶來的複用好處,假設都無法複用,那麼做了邏輯抽象後,那麼總邏輯複雜度不變,函式變成了 1000 個。

此時小王接到了需求,終於維護了一個大專案。

小王知道這個專案很複雜,從一開始就沒覺得能理解專案全貌,所以把自己當作一名偵探,準備一步步探索。

現在有兩種選擇,一種是在未做邏輯抽象時探索,一種是在做過邏輯抽象後探索。

如果沒做邏輯抽象,小王需要面對 500 個這種函式:

function render() {
  if (renderComponent) {
    return isOk ? <Component1 /> : <Component2 />;
  } else {
    return isReady ? <Component3 /> : <Component4 />;
  }
}
複製程式碼

如果做了邏輯抽象,小王需要面對 1000 個這種函式:

function render() {
  if (renderComponent) {
    return <Component1And2 />;
  } else {
    return <Component3And4 />;
  }
}
複製程式碼

在專案龐大後,總函式數量並不會影響對線索的查詢,而匯流排索深度也幾乎總是固定的,一般在 5 層左右。

小王理解 5 個或 10 個函式成本都差不多,但沒有做邏輯抽象時,這 5 個函式各自參雜了其他邏輯,反而影響對函式的理解。

這時做邏輯抽象是合適的。

4 總結

所以總的來說,筆者更傾向使用子函式、子元件、IF 元件、高階元件做條件渲染,因為這四種方式都能提高程式的抽象能力。

往往抽象後的程式碼會更具有複用性,單個函式邏輯更清晰,在切面程式設計時更利於理解。

當專案很簡單時,整個專案的理解成本都很低,抽象帶來的複雜度反而讓專案變成了需要切面程式設計的時候,就得不償失了。

總結一下:

  • 當專案很簡單,或者條件渲染的邏輯確認無法複用時,推薦在程式碼中用 && 或者三元運算子、IIFE 等直接實現條件渲染。
  • 當專案很複雜時,儘量都使用 子函式、子元件、IF 元件、高階元件 等方式做更有抽象度的條件渲染。
  • 在做邏輯抽象時,考慮下專案的複雜度,避免因為抽象帶來的成本增加,讓本可以整體理解的專案變得支離破碎。

5 更多討論

討論地址是:精讀《React 八種條件渲染》 · Issue #90 · dt-fe/weekly

如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。

相關文章