2 jsx的{}的使用 条件渲染 列表渲染

253 阅读10分钟

花括号 {} 在 JSX 中扮演着一个**“逃生舱”的角色。它允许你从 JSX 的类 HTML 语法“逃逸”回纯粹的 JavaScript 环境**。

当你需要在 JSX 结构中嵌入动态内容、变量值、执行简单的计算或调用函数来获取结果时,你就需要使用 {}。

{} 中能写什么?

关键规则: {} 内部必须是一个JavaScript 表达式 (Expression)

  • 什么是表达式? 简单来说,表达式是任何可以计算出(或者说“解析为”)一个的代码片段。

  • 可以写的常见内容:

    1. 变量 (Variables): 直接引用在组件作用域内可访问的变量。

      const name = "Alice";
      const element = <h1>Hello, {name}!</h1>; // Renders: <h1>Hello, Alice!</h1>
          
      
    2. 函数调用 (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>
          
      
    3. 属性访问 (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>
          
      
    4. 算术运算 (Arithmetic Operations): 进行数学计算。

      const price = 10;
      const quantity = 5;
      const element = <p>Total: ${price * quantity}</p>; // Renders: <p>Total: $50</p>
          
      
    5. 三元运算符 (Ternary Operator): 进行简单的条件渲染。

      const isLoggedIn = true;
      const element = <div>{isLoggedIn ? <p>Welcome!</p> : <button>Login</button>}</div>;
      // Renders: <div><p>Welcome!</p></div>
          
      
    6. 逻辑与 && 运算符 (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>
          
      
    7. 字符串拼接/模板字符串 (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>;
          
      
    8. 调用返回 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>;
          
      
    9. 布尔值、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 在更新列表时可能会遇到问题:

  1. 性能问题: React 可能需要做更多不必要的 DOM 操作。
  2. 状态丢失: 如果列表项自身是组件且拥有内部状态(如 的值),在列表重排序或增删时,没有稳定 key 可能导致状态混乱或丢失。
  3. 渲染错误: 在某些边缘情况下可能导致 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 (谨慎使用):

  1. 列表和列表项是完全静态的,永远不会重新排序或过滤。
  2. 列表项没有自己的内部状态。
  3. 列表项没有稳定的 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;
    

总结:

  1. 使用 JavaScript 的 .map() 方法将数据数组转换为 React 元素数组。
  2. 在 .map() 回调函数返回的最外层元素上添加一个唯一的、稳定的 key prop
  3. 优先使用数据项中自带的唯一 ID 作为 key。
  4. 避免使用数组索引作为 key,除非列表是完全静态且无状态的。
  5. key 对于 React 高效更新列表至关重要,可以防止性能问题和状态错误。
  6. 可以将自定义组件渲染在列表中,key 同样加在组件实例上。