在 React 中實現條件渲染的 7 種方法

今天來看看在 React 中實現條件渲染的常見方法和注意事項!

條件渲染方式

1. if 語句

先從 React 最基本的條件類型來看。如果有數據就顯示組件,如果沒有數據就不顯示任何內容。posts 爲需要渲染的列表:

export default function App() {
  const { posts } = usePosts();

  if (!posts) return null;

  return (
    <div>
      <PostList posts={posts} />
    </div>
  );
}

這種形式會生效的原因就是我們會提前返回,如果滿足條件(posts 值不存在),就通過return null 在組件中不顯示任何內容。

如果有多個要檢查的條件時,也可以使用 if 語句。例如,在顯示數據之前檢查加載和錯誤狀態:

export default function App() {
  const { isLoading, isError, posts } = usePosts();
   
  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error!</div>;

  return (
    <div>
      <PostList posts={posts} />
    </div>
  );
}

這裏我們可以多次使用 if 語句,不需要再使用 else 或者 if-eles 語句,這樣就減少了需要編寫的代碼,並且可讀性更強。

2. 三元運算符

當我們想提前退出或者什麼都不顯示時,if 語句會很有用。但是,如果我們不想寫一個與返回的 JSX 分開的條件,而是直接在其中寫呢?那就可以使用三元表達式來編寫條件。

在 React 中,我們必須在 JSX 中包含表達式,而不是語句。這就是爲什麼我們在 JSX 中只能使用三元表達式,而不是 if 語句來編寫條件。

例如,在移動設備的屏幕上顯示一個組件,而在更大的屏幕上顯示另一個組件,就可以使用三元表達式來實現:

export default function App() {
  const isMobile = useWindowSize()

  return (
    <main>
      <Header />
      {isMobile ? <MobileChat /> : <Chat />}
    </main>
  )
}

其實,不必將這些三元表達式包含在 JSX 中,可以將其結果分配給一個變量,然後在需要的地方使用即可:

export default function App() {
  const isMobile = useWindowSize();
    
  const ChatComponent = isMobile ? <MobileChat /> : <Chat />;

  return (
    <main>
      <Header />
      <Sidebar />
      {ChatComponent}
    </main>
  )
}

3. && 運算符

在許多情況下,我們可能想要使用三元表達式,但是如果不滿足條件,就不顯示任何內容。那代碼會是這樣的:

condition ? <Component /> : null.

可以使用 && 運算符來簡化:

export default function App() {
  const { posts, hasFinished } = usePosts()

  return (
    <>
      <PostList posts={posts} />
      {hasFinished && (
        <p>已經到底啦!</p>
      )}
    </>
  )
}

如果條件爲真,則邏輯 && 運算符之後的表達式將是輸出。如果條件爲假,React 會忽略並跳過表達式.

4. switch

過多的 if 語句會導致組件變得混亂,可以將多個條件提取到包含 switch 語句的單獨的組件中(根據組件邏輯的複雜程度來選擇是否提取到單獨的組件)。下面來看一個簡單的菜單切換組件:

export default function Menu() {
  const [menu, setMenu] = React.useState(1);

  const toggleMenu = () ={
    setMenu((m) ={
      if (m === 3) return 1;
      return m + 1;
    });
  }

  return (
    <>
      <MenuItem menu={menu} />
      <button onClick={toggleMenu}>切換菜單</button>
    </>
  );
}

function MenuItem({ menu }) {
  switch (menu) {
    case 1:
      return <Users />;
    case 2:
      return <Chats />;
    case 3:
      return <Rooms />;
    default:
      return null;
  }
}

由於使用帶有 switch 語句的 MenuItem 組件父菜單組件不會被條件邏輯弄亂,可以很容易地看到給定 menu 狀態將顯示哪個組件。需要注意,必須爲 switch case 運算符使用默認值,因爲在 React 中,組件始終需要返回一個元素或 null。

5. 枚舉

在 JavaScript 中,當對象用作鍵值對的映射時,它可以用作枚舉:

const ENUMOBJECT = {
  a: '1',
  b: '2',
  c: '3',
};

假如要創建三個不同的組件 Foo、Bar 和 Default,並根據某種狀態顯示這些組件:

const Foo = () ={
  return <button>FOO</button>;
};
const Bar = () ={
  return <button>BAR</button>;
};
const Default = () ={
  return <button>DEFAULT</button>;
};

創建可用作枚舉的對象:

const ENUM_STATES = {
  foo: <Foo />,
  bar: <Bar />,
  default: <Default />
};

渲染這個枚舉對象的函數:

function EnumState({ state }) {
  return <div>{ENUM_STATES[state]}</div>;
}

上面的 state 屬性可以從對象中檢索值。可以看到,與 switch case 運算符相比,它更具可讀性。

6. JSX 庫

JSX Control Statements 庫擴展了 JSX 的功能,從而可以直接使用 JSX 實現條件渲染。它是一個 Babel 插件,可以在轉譯過程中將類似組件的控制語句轉換爲對應的 JavaScript。

安裝babel-plugin-jsx-control-statements包並修改 Babel 配置後,可以像這樣重寫應用程序:

export default function App(props) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
    //...

    return (
      <Choose>
        <When condition={isLoggedIn}>
        <button>Logout</button>;
        </When>
          <When condition={!isLoggedIn}>
          <button>Login</button>;
        </When>
      </Choose>
    );
}

當然,不建議這樣來編寫條件語句,這樣會導致代碼的可讀性變差,並且 JSX 允許使用強大的 JavaScript 功能來自己處理條件渲染,無需添加模板組件即可啓用它。

7. 高階組件

高階組件 (HOC) 與 React 中的條件渲染完美匹配。HOC 可以幫助處理多個用例,但一個用例可能是使用條件渲染來改變組件的外觀。讓我們看看顯示元素或組件的 HOC:

function withLoadingIndicator(Component) {
  return function EnhancedComponent({ isLoading, ...props }) {
    if (!isLoading) {
      return <Component {...props} />;
    }

    return (
      <div>
        <p>Loading</p>
      </div>
    );
  };
}

const ListWithLoadingIndicator = withLoadingIndicator(List);

function App({ list, isLoading }) {
  return (
      <ListWithLoadingIndicator isLoading={isLoading} list={list} />
  );
}

在這個例子中,List 組件可以專注於呈現列表。而不必再加載狀態。HOC 隱藏了實際組件中的所有干擾。最終,可以添加多個高階組件來隱藏多個條件渲染邊緣情況。

注意事項

1. 小心 0

來看一個常見的渲染示例,當數組中存在元素時才渲染內容:

{gallery.length && <Gallery slides={gallery}>}

預想的結果是,數組存在元素時渲染內容,不存在元素時什麼都不渲染。但是,頁面上得到了 “0”。這是因爲在使用與運算符時,一個假的左側值(如 0)會立即返回。在 JavaScript 中,布爾運算法不會將其結果轉化爲布爾值。所以,React 將得到的值放入 DOM 中,與 false 不同的是,0 是一個有效的 React 節點,所以最終會渲染成 0。

那該如何避免這個問題呢?可以顯式的將條件轉換爲布爾值,當表達式結果爲false時,就不會在頁面中渲染了:

gallery.length > 0 && jsx

!!gallery.length && jsx

Boolean(gallery.length) && jsx

或者使用三元表達式來實現:

{gallery.length ? <Gallery slides={gallery} /> : null}

2. 優先級

與運算符(&&)比或運算符(||)具有更高的優先級。所以,要格外小心使用包含與運算符的 JSX 條件:

user.anonymous || user.restricted && <div class />

這樣寫就相當於:

user.anonymous || (user.restricted && <div class />)

這樣,與運算符左側爲真時就會直接返回,而不會繼續執行後面的代碼。所以,多數情況下,看到或運算符時,就將其使用括號括起來,避免因爲優先級問題而渲染出錯:

{(user.anonymous || user.restricted) && <div class />}

3. 嵌套三元表達式

三元表達式適合在兩個 JSX 之間進行切換,一旦超過兩個項目,代碼就會變得糟糕:

{
  isEmoji
    ? <EmojiButton />
    : isCoupon
        ? <CouponButton />
        : isLoaded && <ShareButton />
}

有時使用 && 來實現會更好,不過一些條件判斷會重複:

{isEmoji && <EmojiButton />}
{isCoupon && <CouponButton />}
{!isEmoji && !isCoupon && isLoaded && <ShareButton />}

當然,這種情況下,使用 if 語句可能是更好的選擇:

const getButton = () ={
    if (isEmoji) return <EmojiButton />;
    if (isCoupon) return <CouponButton />;
    return isLoaded ? <ShareButton /> : null;
};

4. 避免 JSX 作爲條件

通過 props 傳遞的 React 元素能不能作爲判斷條件呢?來看一個簡單的例子:

const Wrap = (props) ={
    if (!props.children) return null;
    return <div>{props.children}</div>
};

我們希望 Wrap 在沒有包含內容時呈現 null,但 React 不是這樣工作的:

因此,不要將 JSX 作爲判斷條件,避免出現一些難以預料的問題。

5. 重新掛載還是更新?

用三元表達式編寫的 JSX 感覺就像是完全獨立的代碼:

{hasItem ? <Item id={1} /> : <Item id={2} />}

當 hasItem 改變時會發生什麼?我的猜測是 <Item id={1} /> 卸載,然後 <Item id={2} /> 安裝,因爲這裏寫了 2 個單獨的 JSX 標籤。然而,React 並不知道也不關心我們寫了什麼,它所看到的只是 Item 元素在同一個位置,所以它保持掛載的實例,更新 props。上面的代碼等價於 <Item id={hasItem ? 1:2} />

注意:如果三元表達式包含的是不同的組件,如 {hasItem ? <Item1 /> : <Item2 />},hasItem 改變時,React 會重新掛載,因爲 Item1 無法更新爲 Item2。

上述情況會導致一些意外的行爲:

{
  mode === 'name'
    ? <input placeholder="name" />
    : <input placeholder="phone" />
}

這裏,如果在 name 的 input 中輸入了一些內容,然後切換模式(mode),在 name 中輸入內容的就會泄漏到 phone 的 input 中,這可能會對依賴於先前狀態的複雜更新機制造成更大的破壞。

這裏的一種解決方法是使用 key。通常,我們用它來渲染列表,但它實際上是 React 的元素標識提示——具有相同 key 的元素是相同的邏輯元素:

{
  mode === 'name'
    ? <input placeholder="name" key="name" />
    : <input placeholder="phone" key="phone" />
}

另一種方法是用兩個單獨的 && 塊來替換三元表達式。當 key 不存在時,React 會回退到子數組中項目的索引,因此將不同的元素放在不同的位置與顯式定義 key 的效果是一樣的:

{mode === 'name' && <input placeholder="name" />}
{mode !== 'name' && <input placeholder="phone" />}

參考:

https://blog.thoughtspile.tech/2022/01/17/jsx-conditionals/

https://ordinarycoders.com/blog/article/react-conditional-rendering

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5UHSGnnAr1-j37AgJ6t0eQ