感謝 iT 邦幫忙與博碩文化,本系列文章已出版成書「從 Hooks 開始,讓你的網頁 React 起來」,首刷版稅將全額贊助 iT 邦幫忙鐵人賽,歡迎前往購書,鼓勵筆者撰寫更多優質文章。
假設現在我們想要幫計數器設定最大值和最小值,其中計數器最大只能到 10,最小只能到 0 ,而且預設起始值是 5 的話,可以怎麼做呢?
其中一種做法是當數字 >=
10 的時候,就把向上的箭頭隱藏起來,當數字 <=
0 的時候,就把向下的箭頭隱藏起來,像下圖這樣:
要把元素隱藏起來有幾種常見的做法,一種是添加 CSS 屬性把它隱藏起來,一種是讓整個 DOM 中的該元素都不要渲染出來,先讓我們來看第一種做法。
第一種作法蠻直覺的,就是透過改變 CSS 樣式來把該元素隱藏起來,讓使用者看不到,而可以把元素隱藏起來的 CSS 樣式主要包含 display: none
和 visibility: hidden
。
這兩種 CSS 屬性都可以讓使用者看不到該元素,但差別在於 display: none
在把該元素隱藏的情況下,同時會移除該元素原本佔據在網頁上的空間 ,因此當某元素原本存在而用了 display: none
的話,會因為該元素不見而導致畫面排版「跳一下」。 visibility: hidden
同樣會把該元素給隱藏起來,但是原本該元素所佔據的空間還是會保留在那裡 ,因此不會有因為東西被移除後而畫面排版「跳一下」的情況。
關於
display: none
和visibility: hidden
的差異可以參考 [CSS] display:none和visibility:hidden的差別 @ 愛流浪的小風。
這裡為了避免畫面排版會因為元素隱藏而有改變,因此使用 visibility: hidden
。那麼要怎麼在 JSX 中根據條件來改變 CSS 樣式呢?
這裡我們使用 inline-style
來修改 CSS 樣式,還記得在 [Day 05] 將計數器頁面改成用 JSX 來寫 中有提過使用行內樣式(inline-style)的方法,就是在 style={}
屬性後的 {}
中帶入帶有樣式的物件即可。
現在可以先開啟昨天完成的範例 Day 6 - Counter with useState @ CodePen,Fork 一份出來修改。可以先把行內樣式 visibility: hidden;
寫進去試試看:
// 向上箭頭的部分
<div
className="chevron chevron-up"
// 在 style={} 的 {} 中放入帶有 CSS 的樣式即可
style={{
visibility: 'hidden',
}}
onClick={() => {
setCount(count + 1);
}}
/>
修改的部份如下:
同時可以把 const [count, setCount] = useState(<預設值>)
中的預設值改成 5
。
這時候你應該會看到畫面上顯示 5,而且向上和向下的箭頭都因為 visibility: hidden
的關係而隱藏了:
接下來只需要判斷當 count >= 10
的時候把 hidden
套用到向上的箭頭;當 count <= 0
的時候把 hidden
套用到向下的箭頭即可。
既然 JSX 本質上就是 JavaScript,要做條件判斷就可以直接使用 JavaScript 中條件式完成,這裡會使用到 &&
這種寫法,我們先來完成效果,再來說明 &&
是什麼意思。
對於向上的箭頭來說,可以使用:
<div
className="chevron chevron-up"
// 使用 JavaScript 的 && 判斷式
// 當 count >= 10 的時候回傳 'hidden' 否則回傳 false
style={{
visibility: count >= 10 && 'hidden',
}}
onClick={() => {
setCount(count + 1);
}}
/>
對向下的箭頭來說,則是:
<div
className="chevron chevron-down"
// 使用 JavaScript 的 && 判斷式
// 當 count <= 0 的時候回傳 'hidden' 否則回傳 false
style={{
visibility: count <= 0 && 'hidden',
}}
onClick={() => {
setCount(count - 1);
}}
/>
完整的程式碼會像這樣:
這時候的效果會像這樣。你可能會發現 只要你手速夠快,好像就可以讓數字超過 10 或低於 0:
之所以會這樣是因為當初在寫 CSS 的時候,針對 .chevron
這個元素偷懶的用了 transition: all 0.3s;
, 讓所有的樣式都會有過渡(transition)的效果,因此這個按鈕在 0.3s
內並不會馬上消失,進而使得使用者還點擊的到。要解決這個問題就是去指定需要套用 transition 效果的樣式即可,因此你可以打開 CSS 的部分,在 .chevron
的地方,把 transition 改成 transition: background-image 0.3s;
這樣就可以了:
這樣就不會多出 0.3 秒的延遲讓使用者的快手可以點擊到了。
完整的程式碼可以參考 Day 7 - Counter with useState - conditional inline-style @ CodePen
在 JavaScript 中,&&
或 ||
這種語法稱作「邏輯運算子(Expressions - Logical operator)」,而因為在 JSX 中的 {}
內只能放入表達式(expressions),而不能寫入像是 if...else...
這種陳述句(statement),因此在 React 中很常時候都會使用邏輯運算子這種語法。
在說明 &&
之前,先來看一下大家比較常看到的 ||
。||
邏輯上是「或(or)」的意思,在 JavaScript 中常常被當做定義變數的預設值來使用,假設寫 a || b
的話,意思就是當 a
為 false(為假)時就用 b
,當 a
為 true(為真)時就直接用 a
。
同時在 **JavaScript 中的真假值在判斷會自動作轉型,因此像是 null
、NaN
、0
、空字串(""
、''
)、undefined
都會被轉型並判斷為「false(假)」。
以下面的例子來說:
// || 就是前面為假時去拿後面的那個
const a = 0 || 'iPhone'; // 因為 0 被轉型後為 false,所以 a 會是 'iPhone'
const b = 26900 || 24900; // 因為 26900 會轉型為 true,所以 b 會是 26900
const c = true || '不會輪到我'; // 因為 true 為真,所以 c 是 true
const d = false || '會輪到我'; // 因為 false 為假,所以 d 是 '會輪到我'
||
簡單來說,就是當||
前面的值為 false(假)時,就取後面的那個當值。
至於 &&
則反過來。當寫 a && b
時,當 a
為 true(為真)時,就拿後面的 b
,否則拿 a
。以下面的例子來說:
// && 就是前面為真時去拿後面的那個
const a = 0 && 'iPhone'; // 因為 0 被轉型後為 false,所以 a 會是 0
const b = 26900 && 24900; // 因為 26900 轉型為 true,所以 b 會是 24900
const c = true && '不會輪到我'; // 因為 true 為真,所以 c 是'不會輪到我'
const d = false && '會輪到我'; // 因為 false 為假,所以 d 是 false
&&
簡單來說,就是當&&
前面的值為 true 時,就取後面的那個當值。
所以回到這裡寫的 visibility: count >= 10 && 'hidden'
,意思就會是當 count >= 10
成立時就取後面那個 ,於是就 hidden
了,否則就回傳 false
,當 CSS 樣式的值是 false
時,在 React 中就不會把該樣式套到該元素上。
延伸閱讀:Logical Operators @ MDN
從瀏覽器的開發者工具中可以看到這樣的邏輯:
補充:在 React 中,除了使用
&&
和||
來進行條件渲染之外,當要做到「若 ... 則 ...,否則...」的這種功能時,則會使用三元判斷式(ternary operator),也就是... ? ... : ...
的這種寫法,這個部分會在後面其他實作使用到。
同樣的做法也可以套用到 className
上面,舉例來說,先在 CSS 的地方定義 .visibility-hidden
這個樣式:
.visibility-hidden {
visibility: hidden;
}
那麼在 <Counter />
組件中 className
的部分就可以這樣寫:
// 向上的箭頭
<div
className={`chevron chevron-up ${count >= 10 && 'visibility-hidden'}`}
onClick={() => {
setCount(count + 1);
}}
/>
// 向上的箭頭
<div
className={`chevron chevron-down ${count <= 0 && 'visibility-hidden'}`}
onClick={() => {
setCount(count - 1);
}}
/>
在不同的條件下,className
會帶入不同的值,因此可以達到一樣的效果,完整的程式碼可以參考 Day 7 - Counter with useState - conditional className @ CodePen。
前面學到的方式,是透過 CSS 樣式來把該元素隱藏起來,但使用者仍然可以透過瀏覽器的開發者工具看到該 element。如果你希望使用者透過開發者工具連 element 都看不到的話,一樣可以透過 JavaScript 的 &&
來達到這個效果,只是現在條件判斷的內容要套用在 JSX 的元素上。
因此向上的箭頭會變成這樣子:
// 當 count < 10 時才出現向上的箭頭
{count < 10 && (
<div
className="chevron chevron-up"
onClick={() => {
setCount(count + 1);
}}
/>
)}
向下的箭頭則會變這樣子:
// 當 count > 0 時才出現向下的箭頭
{count > 0 && (
<div
className="chevron chevron-down"
onClick={() => {
setCount(count - 1);
}}
/>
)}
完整的程式碼會像這樣子,也可以參考連結 Day 7 - Counter with useState - conditional rendering @ CodePen:
透過這種做法時,從瀏覽器的開發者工具你會看到當符合條件時,這個元素會整個從瀏覽器中被移掉:
至於實務上要使用 CSS 樣式讓使用者看不到元素就好,還是需要從 DOM 中整個把元素移除,主要端看這個功能的目的。如果使用的是 CSS 樣式,使用者就比較有機會自己去把樣式解開後,繼續往更高的值加上去,像是下圖這樣:
因此一般來說,如果你需要比較嚴格的去控制使用者的行為,不想要使用者透過 CSS 就能簡單修改的話,那麼就把 DOM 整個移除;但如果這個功能被使用者手動打開也不會有太大影響的話,那就使用 CSS 樣式來控制畫面就好,如此會有比較好的效能和體驗。
前幾天已經使用過了 JSX 和 React Hooks,但在當時為了文章的順暢度,有一些適用和不適用的情境並沒有進一步說明,但如果一整篇都打 JSX 或 React Hooks 的補充說明應該會相當無聊,因此決定利用每天的最後一點的篇幅來補充這些內容。
不知道你有沒有發現到,在原本計數器開始的樣版中,我們的 HTML 是寫這樣:
<div class="container">
<div class="chevron chevron-up">
</div>
<div class="number">
256
</div>
<div class="chevron chevron-down">
</div>
</div>
可是當我們搬入到 JSX 中,向上箭頭的 <div>
:
<!-- 原本的 HTML -->
<div class="chevron chevron-up"></div>
<!-- JSX 中變成 -->
<div class="chevron chevron-up" />
向下箭頭的 <div>
也一樣:
<!-- 原本的 HTML -->
<div class="chevron chevron-down"></div>
<!-- JSX 中變成 -->
<div class="chevron chevron-down" />
所以最後變成像這樣:
const Counter = () => {
return (
<div className="container">
<div className="chevron chevron-up" />
<div className="number">{count}</div>
<div className="chevron chevron-down" />
</div>
);
};
之所以會這樣,是因為在 JSX 中,當開始和結束的標籤(tag)之間沒有任何內容的時候,也就是該標籤內沒有子層元素時,可以把它自我關閉(self closing tag)起來,也就是在開頭的 HTML 標籤最後加上 /
即可,結尾的 HTML 標籤即可移除,舉例來說 <div></div>
,因為開頭和結尾的 HTML 標籤之間沒有任何內容,因此在 JSX 中會變成 <div />
。