Day 17 最後,我們把登入流程接成這樣:
import { useState } from 'react'
function App() {
const [user, setUser] = useState(null)
function login() {
setUser({
name: 'Anthony'
})
}
return (
<div>
<p>
{user ? `Hello, ${user.name}` : '尚未登入'}
</p>
<button onClick={login}>
Sign In
</button>
</div>
)
}
export default App
前兩天我都把注意力放在:
useState
onClick
login
setUser
但其實上面還有一行,我一直在用,卻沒有真的拆過:
{user ? `Hello, ${user.name}` : '尚未登入'}
這行很神奇。
user 一開始是:
null
登入後卻變成:
{
name: 'Anthony'
}
它根本不是:
true
false
那 JavaScript 到底是在判斷什麼?
先把 JSX 拿掉,看最普通的 JavaScript:
user ? '已登入' : '尚未登入'
這叫做 ternary operator(三元運算子)。
先用人話讀就好:
條件 ? 條件成立時的結果 : 條件不成立時的結果
所以:
user ? '已登入' : '尚未登入'
其實是在問:
user現在如果被 JavaScript 判斷成成立,就選左邊;不成立,就選右邊。
React 沒有發明這個判斷方式。
JavaScript 先把 ternary 算出結果,React 再把那個結果 render 出來。
user 又不是 boolean,為什麼可以判斷?這就是我真正卡住的地方。
如果是這樣,我很好懂:
const isLoggedIn = true
isLoggedIn ? '已登入' : '尚未登入'
因為 isLoggedIn 本來就是 boolean。
但我們現在是:
const [user, setUser] = useState(null)
後來:
setUser({
name: 'Anthony'
})
所以 user 可能是:
null
也可能是一個 Object:
{ name: 'Anthony' }
JavaScript 在需要做條件判斷時,會把這些值看成「像 false」或「像 true」。
常看到兩個詞:
falsy
truthy
今天先不用背完整清單。
只看會員系統現在真的會遇到的兩個值:
null
→ falsy
→ 條件判斷時當成不成立
Object
→ truthy
→ 條件判斷時當成成立
所以:
user ? '已登入' : '尚未登入'
在這兩種狀態下其實就是:
user = null
↓
選右邊
↓
尚未登入
以及:
user = { name: 'Anthony' }
↓
選左邊
↓
已登入
原來不需要另外再存一個:
const isLoggedIn = true
因為「user 有沒有資料」本身就已經可以拿來表示現在有沒有登入。
原本:
<p>
{user ? `Hello, ${user.name}` : '尚未登入'}
</p>
大括號:
{ ... }
代表我要在 JSX 裡放一個 JavaScript expression。
裡面的:
user ? `Hello, ${user.name}` : '尚未登入'
會先算出其中一個結果。
如果:
user === null
那結果是:
尚未登入
如果:
user = {
name: 'Anthony'
}
那結果變成:
Hello, Anthony
所以 React 最後收到的不是「一個神祕的判斷式」。
而是 JavaScript 已經選好的結果。
如果條件渲染只能拿來切換一行字,好像還沒什麼感覺。
真正做會員系統時,我想要的是:
未登入
→ 顯示登入區塊
已登入
→ 顯示會員區塊
可以直接寫:
import { useState } from 'react'
function App() {
const [user, setUser] = useState(null)
function login() {
setUser({
name: 'Anthony'
})
}
function logout() {
setUser(null)
}
return (
<main>
{user ? (
<section>
<h2>Hello, {user.name}</h2>
<p>你已經登入會員系統。</p>
<button onClick={logout}>
Sign Out
</button>
</section>
) : (
<section>
<h2>尚未登入</h2>
<button onClick={login}>
Sign In
</button>
</section>
)}
</main>
)
}
export default App
這次 ternary 左右兩邊不再只是字串。
而是兩塊 JSX。
可以把它直接讀成:
user 有資料嗎?
有
→ render 會員畫面
沒有
→ render 登入畫面
這就是「條件渲染」。
現在前三天的東西終於可以連成一條。
const [user, setUser] = useState(null)
回答:
React 現在要記住什麼資料?
<button onClick={login}>
回答:
使用者做了什麼事情之後,我才要改資料?
{user ? <Member /> : <Login />}
概念上回答:
資料不同時,我現在應該顯示哪一個畫面?
三個接起來就是:
state
user = null
↓
React render
↓
條件判斷 user
↓
顯示登入畫面
↓
使用者 click Sign In
↓
onClick 呼叫 login
↓
setUser({ name: 'Anthony' })
↓
state 改變
↓
React 再 render
↓
再次判斷 user
↓
這次 user 是 Object
↓
顯示會員畫面
以前看 React,我很容易只看到:
useState
onClick
? :
三坨不同的語法。
現在比較像是在看一條資料流。
if 不行嗎?可以。
例如先在 return 前面決定:
let content
if (user) {
content = <p>Hello, {user.name}</p>
} else {
content = <p>尚未登入</p>
}
return (
<div>
{content}
</div>
)
這完全可以。
那為什麼 JSX 裡常看到 ternary?
因為這個:
user ? A : B
本身會產生一個值。
所以很適合直接放進:
{ ... }
裡面。
但 if 比較像是在控制程式流程,不是直接產生一個可以塞進 JSX 的值。
所以這樣不能直接寫:
{
if (user) {
<Member />
} else {
<Login />
}
}
這不是「React 討厭 if」。
而是 JSX 的大括號這個位置要的是 JavaScript expression。
ternary 剛好是 expression,所以很好放。
這又是一次「看起來像 React,其實裡面藏的是 JavaScript」。
!user 又是在幹嘛?會員選單有時會看到:
if (!user) {
return null
}
! 可以先讀成「不是」。
但更接近現在情境的讀法是:
user
→ 有會員資料嗎?
!user
→ 沒有會員資料嗎?
所以:
user = null
時:
!user
會得到 true。
於是我們甚至可以做:
function MemberMenu({ user }) {
if (!user) {
return null
}
return (
<nav>
<a href="/member">會員中心</a>
</nav>
)
}
這裡的:
return null
可以先理解成:
這個 Component 這次不要顯示任何東西。
不過要注意一件很重要的事:
把「會員中心」藏起來,只是 UI 顯示邏輯,不等於真的完成權限保護。
真正的登入驗證和權限控制是後面的問題,今天先不要混在一起。
不是。
這個:
condition ? A : B
本來就是 JavaScript。
React 只是常常把它放進 JSX:
{condition ? <A /> : <B />}
true 或 false?不一定。
JavaScript 做條件判斷時會看 truthy / falsy。
今天我們只需要先記:
null
→ falsy
Object
→ truthy
這剛好就能讓會員資料同時扮演:
會員是誰
+
現在有沒有人登入
setUser 自己把畫面改掉了?不是直接去改某個 <p> 或 <section>。
比較像:
setUser
↓
state 更新
↓
React 再 render
↓
JavaScript 再跑一次條件判斷
↓
這次選到另一塊 JSX
所以畫面改變,是整條流程的結果。
我會記這一句:
條件渲染不是 React 在「猜」要顯示什麼,而是 JavaScript 先根據目前的資料選出結果,React 再把那個結果 render 出來。
放回今天的會員系統:
{user ? <Member /> : <Login />}
可以直接讀成:
有 user
→ Member
沒有 user
→ Login
醬子