花括号 {} 在 JSX 中扮演着一个**“逃生舱”的角色。它允许你从 JSX 的类 HTML 语法“逃逸”回纯粹的 JavaScript 环境**。
当你需要在 JSX 结构中嵌入动态内容、变量值、执行简单的计算或调用函数来获取结果时,你就需要使用 {}。
{} 中能写什么?
关键规则: {} 内部必须是一个JavaScript 表达式 (Expression) 。
-
什么是表达式? 简单来说,表达式是任何可以计算出(或者说“解析为”)一个值的代码片段。
-
可以写的常见内容:
-
变量 (Variables): 直接引用在组件作用域内可访问的变量。
const name = "Alice"; const element = <h1>Hello, {name}!</h1>; // Renders: <h1>Hello, Alice!</h1> -
函数调用 (Function Calls): 调用函数并将其返回值嵌入 JSX。返回值需要是 React 可以渲染的东西(字符串、数字、React 元素、null、undefined、boolean 或数组)。
function getGreeting(user) { return user ? `Welcome back, ${user}` : "Please log in."; } const user = "Bob"; const element = <div>{getGreeting(user)}</div>; // Renders: <div>Welcome back, Bob</div> -
属性访问 (Property Access): 访问对象的属性或数组的元素。
const user = { firstName: "Charlie", lastName: "Brown" }; const colors = ["red", "green", "blue"]; const element = ( <div> <p>Full Name: {user.firstName} {user.lastName}</p> <p>Primary Color: {colors[0]}</p> </div> ); // Renders: <div><p>Full Name: Charlie Brown</p><p>Primary Color: red</p></div> -
算术运算 (Arithmetic Operations): 进行数学计算。
const price = 10; const quantity = 5; const element = <p>Total: ${price * quantity}</p>; // Renders: <p>Total: $50</p> -
三元运算符 (Ternary Operator): 进行简单的条件渲染。
const isLoggedIn = true; const element = <div>{isLoggedIn ? <p>Welcome!</p> : <button>Login</button>}</div>; // Renders: <div><p>Welcome!</p></div> -
逻辑与 && 运算符 (Logical AND): 常用于“如果条件为真则渲染”的场景(利用了 && 的短路特性)。
const unreadMessages = 5; const element = ( <div> {unreadMessages > 0 && <p>You have {unreadMessages} unread messages.</p>} </div> ); // Renders: <div><p>You have 5 unread messages.</p></div> // If unreadMessages was 0, it would render: <div></div> -
字符串拼接/模板字符串 (String Concatenation/Template Literals):
const firstName = "David"; const lastName = "Lee"; const element = <p>{"User: " + firstName + " " + lastName}</p>; // Or using template literals (often cleaner): const element2 = <p>{`User: ${firstName} ${lastName}`}</p>; -
调用返回 JSX 的函数:
function renderUserStatus(isOnline) { return isOnline ? <span style={{color: 'green'}}>Online</span> : <span style={{color: 'grey'}}>Offline</span>; } const element = <div>Status: {renderUserStatus(true)}</div>; -
布尔值、null、undefined: 这些值在 JSX 中不会渲染任何内容。这对于条件渲染很有用。
const showDetails = false; const userData = null; const element = ( <div> {showDetails && <p>Details are shown.</p>} {/* Renders nothing if showDetails is false */} {userData} {/* Renders nothing */} {undefined} {/* Renders nothing */} {true} {/* Renders nothing */} </div> );
-
-
不能直接写的:
- 语句 (Statements): 如 if/else 块、for/while 循环、switch 语句、变量声明 (let x = 5;) 等不能直接放在 {} 中,因为它们本身不解析为一个值。
- 对象字面量 (Object Literals) (直接渲染): 你不能直接写 { { a: 1 } } 来期望渲染一个对象(除非在 style 属性中,这是一个特例)。
数组、对象、函数在 {} 中的表现
1. 数组 (Arrays):
-
直接放入 {}: 如果数组包含 React 可以渲染的元素(字符串、数字、React 元素),React 会自动遍历数组并将每个元素渲染出来,元素之间没有分隔符。
const numbers = [1, 2, 3]; const items = [<li key="a">Apple</li>, <li key="b">Banana</li>]; const mixed = ["Hello", " ", <strong>World</strong>, 123]; const element = ( <div> <p>{numbers}</p> {/* Renders: <p>123</p> */} <ul>{items}</ul> {/* Renders: <ul><li>Apple</li><li>Banana</li></ul> */} <p>{mixed}</p> {/* Renders: <p>Hello <strong>World</strong>123</p> */} </div> ); -
最常见用法:.map(): 通常,你会使用数组的 .map() 方法在 {} 内部将数据数组转换为 JSX 元素数组。极其重要: 在使用 .map() 生成列表时,必须为每个列表项提供一个唯一的 key prop,以帮助 React 高效地更新 DOM。
const fruits = [{id: 1, name: 'Orange'}, {id: 2, name: 'Pear'}]; const fruitList = ( <ul> {fruits.map(fruit => ( <li key={fruit.id}>{fruit.name}</li> // Key prop is essential! ))} </ul> ); // Renders: <ul><li>Orange</li><li>Pear</li></ul>
2. 对象 (Objects):
-
直接放入 {} (错误): 直接在 {} 中放置一个普通 JavaScript 对象(非 React 元素对象)会导致运行时错误。React 不知道如何将一个通用对象渲染成 UI。
const myObject = { name: "Eve", age: 30 }; // const element = <div>{myObject}</div>; // !!! ERROR: Objects are not valid as a React child -
例外:style 属性: style 属性是一个特例,它期望接收一个包含 CSS 样式的 JavaScript 对象。注意这里是双花括号 style={{...}}:外层 {} 是 JSX 嵌入 JavaScript 的语法,内层 {} 是对象字面量本身。
const divStyle = { color: 'blue', backgroundColor: 'lightgrey' }; const element = <div style={divStyle}>Styled Text</div>; // Or inline: const element2 = <div style={{ fontSize: '20px', padding: 10 }}>More Styled Text</div>; -
访问属性: 你可以在 {} 中访问对象的属性,只要该属性的值是 React 可渲染的。
const book = { title: "The Great Gatsby", author: "F. Scott Fitzgerald" }; const element = <p>'{book.title}' by {book.author}</p>; -
作为 Props 传递: 将对象作为 prop 传递给子组件是非常常见的。
const userData = { id: 'u1', name: 'Frank' }; // <UserProfile user={userData} />
3. 函数 (Functions):
-
直接放入 {} (不渲染): 直接在 {} 中放一个函数引用通常不会渲染任何内容。React 不会执行这个函数并渲染它的定义。
function sayHi() { return "Hi!"; } const element = <div>{sayHi}</div>; // Renders: <div></div> (nothing visible) -
主要用途:事件处理 (Event Handlers): 这是函数在 {} 中最常见的用途。你将一个函数引用传递给事件处理属性(如 onClick, onChange, onSubmit 等)。当事件发生时,React 会调用你提供的函数。
function handleClick() { alert('Button was clicked!'); } // Pass the function reference, don't call it here! const element = <button onClick={handleClick}>Click Me</button>; // Using an inline arrow function is also common: const element2 = <button onClick={() => console.log('Clicked inline!')}>Click Inline</button>; -
调用函数并渲染其返回值: 如前所述,你可以调用函数并渲染其返回的可渲染值。
function getStatusText(status) { return `Status: ${status}`; } const element = <p>{getStatusText("Active")}</p>; // Renders: <p>Status: Active</p> -
Render Props / Children as Function: 在更高级的模式中,函数可以作为 prop(尤其是 children)传递,并在组件内部被调用以动态生成内容。
总结:
- {} 是 JSX 中嵌入 JavaScript 表达式的通道。
- 里面必须是表达式,不能是语句。
- 变量、运算、函数调用(返回可渲染值)、三元运算、&&、.map() 是常用法。
- 数组会被自动展开渲染(.map() 最常用,记得加 key)。
- 普通对象直接放入会报错(style 是例外),应访问其属性或作为 prop 传递。
- 函数主要用于事件处理(传递引用),或调用以渲染其返回值。
React 中的条件渲染 (Conditional Rendering) 。
条件渲染是指根据应用程序的当前状态 (state) 或属性 (props) 来决定渲染哪些 UI 元素或组件。这使得你的界面能够动态地响应用户交互、数据加载状态或其他条件变化。
React 中的条件渲染并不是一个特殊的 React API,而是利用标准的 JavaScript 语法(如 if 语句、逻辑运算符、三元运算符等)来控制返回的 JSX。
以下是几种在 React 中实现条件渲染的常用方法:
1. 使用 if/else 语句
这是最基础的 JavaScript 条件逻辑。你可以在组件的渲染逻辑(函数组件的函数体内部,return 语句之前)使用 if/else 来决定渲染哪个 JSX 块。通常,你会将要渲染的 JSX 赋值给一个变量,然后在 return 语句中渲染这个变量。
function Greeting({ isLoggedIn }) {
let content; // 声明一个变量来存储条件渲染的 JSX
if (isLoggedIn) {
content = <h1>Welcome back!</h1>;
} else {
content = <h1>Please sign in.</h1>;
}
// 在 return 语句中渲染这个变量
return (
<div>
{content}
</div>
);
}
function App() {
return (
<div>
<Greeting isLoggedIn={true} />
<Greeting isLoggedIn={false} />
</div>
);
}
export default App;
- 优点: 对于复杂的条件逻辑或需要根据条件渲染多个不同元素块时,非常清晰易懂。
- 缺点: 不能直接在 JSX 内部使用 if/else 语句,需要借助变量。
2. 使用三元运算符 (condition ? exprIfTrue : exprIfFalse)
三元运算符是 JavaScript 中 if/else 语句的简洁形式,并且它是一个表达式,这意味着你可以直接在 JSX 的 {} 内部使用它。
function GreetingTernary({ isLoggedIn }) {
return (
<div>
{/* 直接在 JSX 中使用三元运算符 */}
{isLoggedIn
? <h1>Welcome back! (Ternary)</h1>
: <h1>Please sign in. (Ternary)</h1>
}
</div>
);
}
function LoginButton({ isProcessing }) {
return (
<button disabled={isProcessing}>
{isProcessing ? 'Logging in...' : 'Log In'}
</button>
);
}
function App() {
return (
<div>
<GreetingTernary isLoggedIn={true} />
<LoginButton isProcessing={false} />
<LoginButton isProcessing={true} />
</div>
);
}
export default App;
- 优点: 非常简洁,适合简单的二选一条件渲染,可以直接嵌入 JSX。
- 缺点: 对于复杂的条件或嵌套的条件,可读性会迅速下降。
3. 使用逻辑 && 运算符 (短路求值)
当你只想在某个条件为真时渲染某个元素,否则什么都不渲染时,逻辑 && 运算符非常方便。它利用了 JavaScript 的短路求值特性:condition && expression,如果 condition 为 true,则表达式的结果是 expression;如果 condition 为 false,则结果是 condition 本身 (即 false)。React 在渲染时会忽略 false、null、undefined。
function Mailbox({ unreadMessages }) {
// const unreadMessages = props.unreadMessages; // 假设 unreadMessages 是一个数组或数字
const messageCount = Array.isArray(unreadMessages) ? unreadMessages.length : unreadMessages;
return (
<div>
<h1>Hello!</h1>
{/* 仅当 messageCount > 0 时才渲染后面的 <p> 元素 */}
{messageCount > 0 &&
<p>
You have {messageCount} unread message{messageCount > 1 ? 's' : ''}.
</p>
}
{messageCount === 0 &&
<p>No new messages.</p>
}
</div>
);
}
function App() {
return (
<div>
<Mailbox unreadMessages={['msg1', 'msg2']} /> {/* Shows count */}
<Mailbox unreadMessages={0} /> {/* Shows "No new messages." */}
<Mailbox unreadMessages={[]} /> {/* Shows "No new messages." */}
</div>
);
}
export default App;
-
优点: 对于 "如果...则渲染..." 的场景非常简洁。
-
注意 (重要陷阱): 如果 && 左侧的表达式结果是 0,React 会渲染出 0!因为 0 是一个 "falsy" 值,但它不是 false、null 或 undefined,React 会将其作为文本节点渲染。
- 错误示例:
count && <Component />如果 count 是 0,会渲染 0。 - 正确做法: 确保左侧表达式的结果是布尔值:
count > 0 && <Component />
- 错误示例:
4. 阻止组件渲染 (返回 null)
如果你希望某个组件在特定条件下完全不渲染任何内容,可以让它的 render 方法(类组件)或函数体(函数组件)返回 null。
function WarningBanner({ warn }) {
if (!warn) {
// 如果 warn 是 falsey,则此组件什么也不渲染
return null;
}
// 否则,渲染警告信息
return (
<div className="warning">
Warning!
</div>
);
}
function App() {
const [showWarning, setShowWarning] = React.useState(true);
const handleToggleClick = () => {
setShowWarning(prevShow => !prevShow);
}
return (
<div>
{/* WarningBanner 组件会根据 showWarning 的值决定是否渲染 */}
<WarningBanner warn={showWarning} />
<button onClick={handleToggleClick}>
{showWarning ? 'Hide' : 'Show'} Warning
</button>
</div>
);
}
export default App;
- 优点: 清晰地表达了“什么都不渲染”的意图。
- 注意: 虽然组件不渲染 DOM,但它的实例仍然存在,其生命周期方法(或 Hooks)仍可能执行(取决于具体情况,例如 useEffect 的清理函数)。
5. 使用 switch 语句或枚举/对象映射 (处理多种条件)
对于有多种可能状态需要渲染不同 UI 的情况,switch 语句(在 return 之前使用)或创建一个状态到组件的映射对象可能比嵌套的三元运算符或多个 if/else if 更清晰。
function LoadingIndicator() { return <p>Loading...</p>; }
function ErrorMessage({ error }) { return <p>Error: {error.message}</p>; }
function UserData({ data }) { return <div>Data: {JSON.stringify(data)}</div>; }
function DataDisplay({ status, data, error }) {
// 方法一:使用 switch (在 return 前)
// let content;
// switch (status) {
// case 'loading':
// content = <LoadingIndicator />;
// break;
// case 'error':
// content = <ErrorMessage error={error} />;
// break;
// case 'success':
// content = <UserData data={data} />;
// break;
// default:
// content = null;
// }
// return <div>{content}</div>;
// 方法二:使用对象映射 (更简洁)
const statusComponents = {
loading: <LoadingIndicator />,
error: <ErrorMessage error={error} />,
success: <UserData data={data} />,
};
return <div>{statusComponents[status] || null}</div>; // 使用映射,提供默认 null
}
function App() {
// 模拟状态变化
const currentStatus = 'success'; // Try 'loading', 'error'
const sampleData = { id: 1, name: 'Sample' };
const sampleError = new Error('Failed to fetch');
return <DataDisplay status={currentStatus} data={sampleData} error={sampleError} />;
}
export default App;
选择哪种方法?
- 简单二选一: 三元运算符 (? :) 通常最简洁。
- 仅在条件为真时渲染: 逻辑 && 很方便(注意 0 的陷阱)。
- 复杂逻辑或多分支: if/else 或 switch(配合变量)更易读。
- 多种状态映射到 UI: 对象映射通常比 switch 更简洁。
- 完全不渲染: 返回 null。
React 中的列表渲染 (List Rendering) 。
在 Web 开发中,经常需要将数据集合(通常是数组)展示为列表、表格、卡片组等形式。React 允许你使用标准的 JavaScript 数组方法(主要是 .map())来高效地将数据数组转换为 UI 元素列表。
核心方法:使用 Array.prototype.map()
.map() 方法是 JavaScript 数组的一个内置方法。它接收一个回调函数,对数组中的每个元素执行该函数,并返回一个包含所有回调函数返回值的新数组。
在 React 中,我们利用 .map() 将数据数组转换成一个 React 元素(JSX)数组,然后 React 会负责将这个元素数组渲染到 DOM 中。
基本用法
假设你有一个包含字符串的数组:
function SimpleList() {
const names = ['Alice', 'Bob', 'Charlie'];
// 使用 map 将 names 数组转换为 <li> 元素数组
const listItems = names.map((name) =>
<li>{name}</li> // 为每个名字创建一个 <li> 元素
);
return (
<ul>
{/* 在 JSX 中直接渲染这个元素数组 */}
{listItems}
</ul>
);
// 最终渲染结果类似于:
// <ul>
// <li>Alice</li>
// <li>Bob</li>
// <li>Charlie</li>
// </ul>
}
export default SimpleList;
通常,你会直接在 JSX 中调用 .map():
function InlineList() {
const numbers = [1, 2, 3, 4, 5];
return (
<ul>
{/* 直接在 JSX 的 {} 中调用 map */}
{numbers.map((number) =>
<li>The number is: {number * 2}</li> // 可以进行计算或任何 JS 表达式
)}
</ul>
);
}
export default InlineList;
关键概念:key Prop
当你使用 .map() (或其他循环方式) 生成一个元素列表时,React 需要一种方法来唯一标识列表中的每个元素,以便在数据变化时能够高效地更新 DOM(例如,添加、删除或重新排序列表项)。这就是 key prop 的作用。
- key 必须是字符串或数字。
- key 在其兄弟元素中必须是唯一的。 (即,在同一次 .map() 调用返回的元素数组中,key 不能重复,但不同列表中的 key 可以相同)。
- key 应该是稳定且可预测的。 它不应该在后续渲染中随意改变,除非对应的项确实代表了一个不同的实体。
为什么 key 如此重要?
没有 key,或者使用了不稳定的 key(如数组索引),React 在更新列表时可能会遇到问题:
- 性能问题: React 可能需要做更多不必要的 DOM 操作。
- 状态丢失: 如果列表项自身是组件且拥有内部状态(如 的值),在列表重排序或增删时,没有稳定 key 可能导致状态混乱或丢失。
- 渲染错误: 在某些边缘情况下可能导致 UI 渲染不正确。
最佳实践:使用数据中唯一的 ID 作为 key
通常,你的数据源(如 API 返回的数据)会包含一个唯一的标识符(如 id, uuid 等)。这是用作 key 的最佳选择。
function TodoList({ todos }) {
// 假设 todos 是一个数组,每个元素是 { id: number|string, text: string, completed: boolean }
// 例如: [{ id: 1, text: 'Learn React', completed: true }, { id: 2, text: 'Build App', completed: false }]
if (!todos || todos.length === 0) {
return <p>No todos yet!</p>;
}
return (
<ul>
{todos.map((todo) => (
// 使用 todo.id 作为 key,它唯一且稳定
// key 应该放在 map 直接返回的最外层元素上
<li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</li>
))}
</ul>
);
}
function App() {
const initialTodos = [
{ id: 'a1', text: 'Learn React Keys', completed: false },
{ id: 'b2', text: 'Practice List Rendering', completed: true },
{ id: 'c3', text: 'Drink Water', completed: false },
];
return <TodoList todos={initialTodos} />;
}
export default App;
避免使用数组索引作为 key (除非满足特定条件)
使用数组项的索引 (map((item, index) => <li key={index}>...</li>)) 通常是不推荐的,尤其是当列表满足以下任一情况时:
- 列表项的顺序可能会改变(用户可以排序、筛选等)。
- 列表会被过滤(某些项被移除)。
- 列表项会被添加或删除(非末尾操作)。
在这些情况下,使用索引作为 key 会导致上述的性能和状态问题,因为项的索引会随着列表的变化而变化,导致 React 错误地认为元素本身发生了巨大变化。
何时可以使用索引作为 key (谨慎使用):
- 列表和列表项是完全静态的,永远不会重新排序或过滤。
- 列表项没有自己的内部状态。
- 列表项没有稳定的 ID。
即使满足这些条件,使用唯一 ID 仍然是更健壮的选择。只有在实在没有其他稳定标识符时,才考虑使用索引。
渲染组件列表
你不仅可以渲染简单的 HTML 元素,还可以使用 .map() 来渲染自定义的 React 组件列表。同样,key 必须放在你直接在 .map() 中创建的组件实例上。
// 单个产品项组件
function ProductItem({ product }) {
// 注意:key 不会作为 prop 传递给 ProductItem 组件内部
// 你不能在 ProductItem 内部通过 props.key 访问到它
return (
<div>
<h3>{product.name}</h3>
<p>Price: ${product.price}</p>
</div>
);
}
// 产品列表组件
function ProductList({ products }) {
// 假设 products = [{ id: 101, name: 'Laptop', price: 1200 }, { id: 102, name: 'Mouse', price: 25 }]
return (
<div>
<h2>Products</h2>
{products.map((product) => (
// Key 必须放在 ProductItem 组件上,而不是它内部的 div 上
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
function App() {
const sampleProducts = [
{ id: 'p1', name: 'Wireless Keyboard', price: 75 },
{ id: 'p2', name: 'Monitor', price: 300 },
];
return <ProductList products={sampleProducts} />;
}
export default App;
总结:
- 使用 JavaScript 的 .map() 方法将数据数组转换为 React 元素数组。
- 在 .map() 回调函数返回的最外层元素上添加一个唯一的、稳定的 key prop。
- 优先使用数据项中自带的唯一 ID 作为 key。
- 避免使用数组索引作为 key,除非列表是完全静态且无状态的。
- key 对于 React 高效更新列表至关重要,可以防止性能问题和状态错误。
- 可以将自定义组件渲染在列表中,key 同样加在组件实例上。