从懵圈到精通:React中JSX的奇妙冒险😋

122 阅读57分钟

一、JSX 初印象

image.png

刚接触 React 的小伙伴们,肯定对 JSX 感到十分疑惑🤔:“这到底是个啥?看起来像 HTML,可又出现在 JavaScript 代码里,难道是两个语言的混合体?” 别担心,这篇文章就是你的 JSX 入门秘籍,保证把这个神奇的语法讲得明明白白!

在 React 的世界里,JSX 就像是一把魔法钥匙,它能让我们以一种超级直观的方式来描述用户界面。想象一下,你不用再写一堆复杂的 JavaScript 代码来创建 DOM 元素,直接像写 HTML 一样写代码,就能轻松构建出页面结构,是不是很酷炫😎?

二、JSX 是何方神圣

image.png

(一)定义揭秘

JSX,全称 JavaScript XML,是一种在 React 中用于描述用户界面的语法扩展。简单来说,它允许我们在 JavaScript 代码里直接写类似 HTML 的标签,把原本分离的 JavaScript 和 HTML 巧妙地融合在了一起。比如下面这段代码:

const element = <h1>Hello, React!</h1>;

是不是感觉很神奇?这就像是在 JavaScript 的世界里开辟了一块 HTML 的小天地,让我们能用更直观的方式构建用户界面。以往,如果我们想要创建一个标题,可能得这样写:

const h1 = document.createElement('h1');
const text = document.createTextNode('Hello, JavaScript!');
h1.appendChild(text);
document.body.appendChild(h1);

而现在,有了 JSX,只需要一行代码就能轻松搞定,是不是大大提高了开发效率呢😜?

(二)核心概念剖析

  1. JS in XML:JSX,简单来说,就是 “JavaScript 中的 XML” ,而 HTML 又是 XML 的一种形式,所以我们可以把 JSX 看作是在 JavaScript 里写 HTML 。这就好比你在一个装满 JavaScript 糖果的盒子里,突然发现了几颗长得像 HTML 的特别糖果,它们完美地融合在一起,形成了一种新的美味。这样一来,我们在处理界面逻辑的时候,就不用再在 JavaScript 和 HTML 文件之间来回切换,所有的操作都可以在同一个文件里完成,是不是超级方便🧐?
  1. 语法糖本质:JSX 本质上是一种语法糖 。什么是语法糖呢?打个比方,语法糖就像是给代码穿上了一件漂亮的外衣,让它看起来更加简洁、易读,但实际上它并没有增加新的功能。就像我们平时吃的糖果,外面的糖衣虽然好吃,但本质上还是里面的糖果。JSX 也是如此,它最终会被编译成普通的 JavaScript 函数调用。比如下面这段 JSX 代码:
const element = <div className="container"><h1>Hello, World!</h1></div>;

编译后就会变成这样:

const element = React.createElement(
  "div",
  { className: "container" },
  React.createElement("h1", null, "Hello, World!")
);

虽然编译后的代码看起来有点复杂,但这就是 JSX 的幕后真相啦😎。了解了这一点,当我们在遇到一些 JSX 相关的问题时,就可以从编译后的 JavaScript 代码入手,说不定能找到解决问题的关键哦。

  1. 声明式编程范式:JSX 采用的是声明式编程范式 ,它关注的是 “应该是什么样子”,而不是 “如何做”。这和传统的命令式编程有很大的区别。命令式编程就像是一个严格的指挥官,一步一步地告诉计算机该怎么做,比如:“先拿起这个变量,再对它进行这个操作,然后把结果放到那里……” 而声明式编程则更像是一个设计师,只需要告诉计算机 “我想要一个这样的界面”,具体的实现细节就交给计算机去处理。例如,我们用 JSX 创建一个按钮:
<button onClick={handleClick}>Click Me</button>

我们只需要声明这个按钮要有一个点击事件,当点击时执行handleClick函数,至于这个点击事件是如何被捕获和处理的,我们不需要关心。而在命令式编程中,可能就需要写很多代码来实现这个功能,比如绑定事件监听器、定义回调函数等等。声明式编程让我们的代码更加简洁、易读,也更容易维护,就像是把复杂的问题简单化,让我们能够更专注于业务逻辑的实现,是不是很棒呢🥳?

三、JSX 基本语法大冒险

image.png

(一)基本元素展示

JSX 的基本元素写法非常简单,就像写 HTML 标签一样。比如,我们要创建一个简单的标题元素,可以这样写:

const element = <h1>Hello, World!</h1>;

这里的<h1>Hello, World!</h1>就是一个 JSX 元素,它和 HTML 中的<h1>标签看起来几乎一模一样。在 React 中,我们可以把这个元素渲染到页面上,就像这样:

import React from'react';
import ReactDOM from'react-dom';
const element = <h1>Hello, World!</h1>;
ReactDOM.render(element, document.getElementById('root'));

这段代码会把<h1>Hello, World!</h1>这个元素渲染到id为root的 DOM 节点上。如果我们想要在组件中使用这个元素,也很简单,只需要把它放在组件的返回值中即可:

function Greeting() {
  return <h1>Hello, React!</h1>;
}

在这个例子中,Greeting组件返回了一个<h1>Hello, React!</h1>的 JSX 元素,当这个组件被渲染时,就会在页面上显示出这个标题。是不是感觉 JSX 很简单呢😃?就像是在 JavaScript 里玩 HTML 的游戏,轻松又有趣。

(二)嵌入 JavaScript 表达式

在 JSX 中,我们可以使用花括号{}来嵌入 JavaScript 表达式,这就像是给 JSX 元素注入了动态的活力。比如说,我们有一个变量name,想要在标题中显示这个变量的值,就可以这样写:

function App() {
  const name = "React";
  return <h1>Hello, {name}!</h1>;
}

这里的{name}就是一个 JavaScript 表达式,它会被替换成name变量的值,也就是"React"。除了变量,我们还可以嵌入各种运算表达式,比如:

function App() {
  return <p>Expression: {2 + 2}</p>;
}

这段代码会在页面上显示Expression: 4,因为{2 + 2}这个表达式会被计算出结果。如果我们有一个函数,也可以在 JSX 中调用它,像这样:

function getCurrentTime() {
  return new Date().toLocaleTimeString();
}
function App() {
  return <p>Function call: {getCurrentTime()}</p>;
}

在这个例子中,{getCurrentTime()}会调用getCurrentTime函数,并把函数的返回值显示在页面上,比如Function call: 下午3:45:20。通过嵌入 JavaScript 表达式,我们可以让 JSX 元素根据不同的数据和逻辑动态地变化,就像给页面赋予了生命一样,让它变得更加灵活和有趣😎。

(三)JSX 属性的门道

  1. 驼峰命名规则:在 JSX 中,属性名需要使用驼峰命名法 ,这和 HTML 中的属性命名有所不同。比如说,在 HTML 中,我们使用class来定义元素的类名,而在 JSX 中,要使用className,像这样:
<div className="container">
  {/* 这里的className就是JSX中的属性名 */}
</div>

还有for属性,在 HTML 中用于关联表单元素和标签,而在 JSX 中要写成htmlFor:

<label htmlFor="name">Name:</label>
<input id="name" type="text" />

另外,像tabindex属性,在 JSX 中依然是tabIndex:

<input tabIndex="1" />

为什么要使用驼峰命名法呢?这是因为 JavaScript 中的变量和函数命名通常采用驼峰命名,为了保持一致性,JSX 属性也遵循这个规则。这样做不仅符合 JavaScript 的语法习惯,还能避免一些潜在的命名冲突,让代码看起来更加整洁和规范🤓。

  1. 动态属性:在 JSX 中,我们可以根据条件动态地设置属性值和类名 ,这为我们构建灵活的用户界面提供了很大的便利。比如说,我们有一个按钮组件,需要根据disabled状态来设置它的样式和行为,就可以这样写:
function Button({ disabled, type, children }) {
  return (
    <button
      disabled={disabled}
      type={type}
      className={disabled? "btn-disabled" : "btn-active"}
    >
      {children}
    </button>
  );
}

在这个例子中,disabled属性根据传入的disabled值来决定按钮是否禁用,如果disabled为true,按钮就会被禁用,并且添加btn-disabled类名;如果disabled为false,按钮正常可用,添加btn-active类名。通过这种方式,我们可以根据不同的条件来动态地改变组件的外观和行为,让用户界面更加智能和友好😃。

(四)事件处理秘籍

在 React 中,处理事件是构建交互式用户界面的关键,而 JSX 为我们提供了一种简洁直观的方式来绑定事件处理函数。比如说,我们要创建一个计数器组件,当用户点击按钮时,计数器的值会增加或减少,就可以这样实现:

import React, { useState } from "react";
function Counter() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

在这个例子中,我们使用useState钩子来创建一个名为count的状态变量,并初始化为 0。然后定义了一个handleClick函数,当按钮被点击时,这个函数会被调用,通过setCount函数来更新count的值,从而实现计数器的增加。在<button>标签中,我们使用onClick属性来绑定事件处理函数,onClick={handleClick}表示当按钮被点击时,会执行handleClick函数。另外一个按钮则通过onClick={() => setCount(count - 1)}来实现计数器的减少。通过这种方式,我们可以轻松地在 JSX 中处理各种用户事件,让用户与界面进行交互,是不是很有趣呢😎?

(五)条件渲染技巧

在实际开发中,我们经常需要根据不同的条件来渲染不同的内容,这时候就可以使用条件渲染。在 JSX 中,有几种常见的条件渲染方式,非常实用。比如说,我们要根据用户的登录状态来显示不同的内容,可以使用三元运算符 :

function ConditionalRender({ isLoggedIn, username }) {
  return (
    <div>
      {isLoggedIn? (
        <h1>Welcome back, {username}!</h1>
      ) : (
        <h1>Please log in</h1>
      )}
    </div>
  );
}

在这个例子中,如果isLoggedIn为true,就会显示Welcome back, {username}!;如果isLoggedIn为false,则显示Please log in。除了三元运算符,我们还可以使用逻辑与&&和逻辑或||运算符来进行条件渲染 。比如说,我们只想在用户登录后显示一个提示信息,可以这样写:

function ConditionalRender({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn && <p>You are logged in</p>}
    </div>
  );
}

这里的isLoggedIn && <p>You are logged in</p>表示只有当isLoggedIn为true时,才会渲染<p>You are logged in</p>这个元素。如果我们想要在用户名为空时显示一个默认的提示信息,可以使用逻辑或运算符:

function ConditionalRender({ username }) {
  return (
    <div>
      {username || <p>Guest user</p>}
    </div>
  );
}

在这个例子中,如果username有值,就会显示username;如果username为空,就会显示Guest user。通过这些条件渲染技巧,我们可以根据不同的条件来灵活地控制页面的显示内容,让用户界面更加智能和友好😃。

(六)列表渲染魔法

当我们需要展示一组数据时,就可以使用列表渲染。在 JSX 中,使用map方法对数组进行遍历并渲染出列表项是非常常见的操作 。比如说,我们有一个 TODO 列表,要把每个 TODO 项都显示出来,可以这样做:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={todo.id}>
          {todo.text} - {todo.completed? "Done" : "Pending"}
        </li>
      ))}
    </ul>
  );
}

在这个例子中,todos是一个包含多个 TODO 项的数组,每个 TODO 项是一个对象,包含id、text和completed等属性。通过map方法,我们遍历 todos数组,为每个 TODO 项创建一个<li>元素,并在其中显示text和completed状态。这里的key属性非常重要 ,它是 React 用来识别列表项的唯一标识符。在渲染列表时,React 会根据key来判断哪些列表项发生了变化,从而进行高效的更新。如果不提供key属性,React 在更新列表时可能会出现性能问题,甚至导致一些意想不到的错误。所以,一定要记得为列表项提供唯一的key。需要注意的是,尽量避免使用数组索引作为key,因为当数组顺序发生变化时,索引也会改变,这可能会导致 React 误判列表项的变化。最好使用数据本身的唯一标识符作为key,比如这里的todo.id,这样可以确保列表渲染的正确性和高效性😎。

(七)Fragment 的妙用

在 React 中,有时候我们需要返回多个元素,但又不想添加额外的 DOM 节点,这时候就可以使用 Fragment 。Fragment 就像是一个隐形的容器,它不会在 DOM 中渲染出额外的标签,但可以用来包裹多个元素。比如说,我们有一个头部组件,包含标题和副标题,我们可以这样写:

function Header() {
  return (
    <>
      <h1>Title</h1>
      <p>Subtitle</p>
    </>
  );
}

这里的<>和</>就是 Fragment 的语法糖,它相当于<React.Fragment>。使用这种方式,我们可以在不添加额外<div>或其他 DOM 节点的情况下,返回多个元素,保持 DOM 结构的简洁。如果想要使用完整的<React.Fragment>语法,也可以这样写:

function Header() {
  return (
    <React.Fragment>
      <h1>Title</h1>
      <p>Subtitle</p>
    </React.Fragment>
  );
}

这两种方式的效果是一样的,只是语法略有不同。通过使用 Fragment,我们可以避免在 DOM 中创建不必要的冗余节点,提高页面的性能和可维护性,是不是很方便呢😃?

四、JSX 编译过程大揭秘

image.png

(一)编译前后对比

你是不是很好奇,JSX 这么神奇的语法,浏览器是怎么理解的呢🧐?其实,JSX 不能直接在浏览器中运行,它需要经过一个编译的过程,被转换成普通的 JavaScript 代码,才能被浏览器识别。就好像是把一种外语翻译成我们能听懂的语言一样,这个翻译的过程就是编译。

让我们来看看一段 JSX 代码及其编译后的 JavaScript 代码,感受一下这个神奇的变化吧😎。比如说,我们有这样一段 JSX 代码:

const element = (
  <div className="container">
    <h1>Hello, {name}!</h1>
    <button onClick={handleClick}>Click me</button>
  </div>
);

这段代码看起来很简洁直观,就像在写 HTML 一样。但是,它在浏览器中是无法直接运行的,需要经过编译。编译后的 JavaScript 代码大概是这样的:

const element = React.createElement(
  "div",
  { className: "container" },
  React.createElement("h1", null, "Hello, ", name, "!"),
  React.createElement("button", { onClick: handleClick }, "Click me")
);

哇,是不是感觉变化很大😮?原来的 JSX 代码被转换成了一系列的React.createElement函数调用。这里的React.createElement是 React 提供的一个函数,它的作用就是创建 React 元素。这个函数接收三个参数 :

  • 第一个参数:表示要创建的元素类型,比如这里的"div"、"h1"、"button",就像是我们在创建 HTML 元素时指定的标签名。
  • 第二个参数:是一个对象,包含了元素的属性,比如{ className: "container" }就是给div元素设置了一个className属性,值为"container";{ onClick: handleClick }是给button元素设置了一个点击事件的处理函数handleClick。
  • 第三个参数及之后的参数:表示元素的子元素,如果有多个子元素,就会有多个参数。比如这里h1元素的子元素是"Hello, ", name, "!",button元素的子元素是"Click me"。

通过这个编译过程,JSX 代码就被转换成了浏览器能够理解的 JavaScript 代码,从而实现了在浏览器中的运行。是不是很神奇呢😃?

(二)编译工具与原理

那么,这个神奇的编译过程是由谁来完成的呢🧐?答案就是 Babel ,它可是 JSX 编译的大功臣!Babel 是一个广泛应用的 JavaScript 编译工具,它的主要作用就是将新版本的 JavaScript 代码转换为旧版本的代码,以确保在不同浏览器和环境中都能正常运行。就像是一个万能的翻译官,不管是什么样的 JavaScript 代码,它都能翻译成各种浏览器都能听懂的 “语言”。

Babel 将 JSX 转换为 JavaScript 的过程,主要包括三个阶段 :解析(Parsing)、转换(Transformation)和生成(Code Generation)。这三个阶段就像是一场接力赛,每个阶段都有自己的任务,共同完成了 JSX 到 JavaScript 的转换。

  • 解析阶段:Babel 会使用解析器将 JSX 代码分解为标记(Tokens),然后构建出一个对应的抽象语法树(AST) 。这个 AST 就像是代码的 “骨架”,它能够表示源代码的结构和语法,是进一步处理的基础。比如说,对于const element = <div className="container">Hello, world!</div>;这段 JSX 代码,在解析阶段,Babel 会把它分解成一个个的标记,然后构建出如下的 AST:
{
  type: 'JSXElement',
  openingElement: {
    type: 'JSXOpeningElement',
    name: {
      type: 'JSXIdentifier',
      name: 'div'
    },
    attributes: [
      {
        type: 'JSXAttribute',
        name: {
          type: 'JSXIdentifier',
          name: 'className'
        },
        value: {
          type: 'StringLiteral',
          value: 'container'
        }
      }
    ],
    selfClosing: false
  },
  children: [
    {
      type: 'JSXText',
      value: 'Hello, world!',
      raw: 'Hello, world!'
    }
  ],
  closingElement: {
    type: 'JSXClosingElement',
    name: {
      type: 'JSXIdentifier',
      name: 'div'
    }
  }
}

这个 AST 以一种结构化的方式描述了 JSX 代码的各个部分,包括元素类型、属性和子元素等,为后续的转换阶段提供了便利。

  • 转换阶段:Babel 会使用插件将 JSX 相关的语法转换为普通的 JavaScript 代码 。这个过程包括将 JSX 元素转换为React.createElement调用以及处理 JSX 属性等。在这个阶段,Babel 会遍历 AST,根据需要插入、修改、移除节点,从而将 JSX 语法转化为通用的 JavaScript 代码。比如说,对于上面的 AST,Babel 会将JSXElement节点转换为React.createElement函数调用,将属性和子元素作为参数传递进去,最终得到类似于const element = React.createElement("div", { className: "container" }, "Hello, world!");这样的 JavaScript 代码。
  • 生成阶段:Babel 会使用生成器遍历 AST,并根据 AST 节点生成相应的 JavaScript 代码 。生成器会将 AST 节点映射到合适的 JavaScript 代码,包括变量、函数调用、运算符等。通过生成阶段,Babel 将转换后的 AST 转化为最终的 JavaScript 代码,也就是我们在浏览器中运行的代码。

通过这三个阶段的协同工作,Babel 成功地将 JSX 代码转换为了浏览器能够理解的 JavaScript 代码,让我们能够在 React 开发中尽情享受 JSX 带来的便利。是不是觉得 Babel 很厉害呢😎?掌握了 JSX 的编译过程,我们在开发中遇到问题时,就能更好地理解和解决了,也能让我们的代码更加高效和健壮。

五、JSX 优势大放送

image.png

(一)可读性强

想象一下,你要创建一个简单的按钮,用普通 JavaScript 创建按钮的代码可能是这样的:

const button = document.createElement('button');
button.textContent = 'Click Me';
button.addEventListener('click', function () {
  console.log('Button clicked!');
});
document.body.appendChild(button);

这段代码虽然能实现功能,但看起来有点繁琐,需要记住很多 DOM 操作的方法。而使用 JSX 创建按钮就简洁多了:

<button onClick={() => console.log('Button clicked!')}>Click Me</button>

是不是一目了然😃?就像在 HTML 里直接写按钮一样,非常直观,代码的可读性大大提高。这样的代码,即使是刚接触 React 的小伙伴,也能轻松理解。

(二)类型安全

当我们配合 TypeScript 使用 JSX 时,就像是给代码穿上了一层坚固的铠甲,它能提供更好的类型检查 。比如说,我们有一个组件接收一个数字类型的属性count,如果不小心传入了一个字符串,TypeScript 会立刻给我们报错,让我们及时发现并改正错误。这样就能在开发阶段就避免很多运行时的错误,大大提高了代码的质量和稳定性。就好比你在搭建积木城堡的时候,每一块积木都有特定的形状和位置,一旦放错了,马上就能发现,而不是等城堡搭好了才发现问题,是不是很实用呢🧐?

(三)开发效率高

在开发过程中,效率可是至关重要的。JSX 就像是一个高效的开发利器,它减少了模板字符串的复杂性 。在没有 JSX 之前,我们可能需要用模板字符串来拼接 HTML 结构,不仅容易出错,而且代码看起来也很混乱。而有了 JSX,我们可以直接在 JavaScript 代码里使用熟悉的 HTML 标签结构来构建界面,就像搭积木一样,把不同的组件组合在一起,快速搭建出我们想要的页面。再加上代码编辑器的语法提示和自动补全等功能,开发速度简直起飞🛫!比如说,当我们在 VS Code 里写 JSX 代码时,只要输入<,编辑器就会自动提示我们可以使用的标签,还能帮我们补全标签和属性,是不是超级方便呢😎?

(四)组件化友好

在 React 的世界里,组件化开发可是核心思想,而 JSX 就像是组件化的最佳拍档,它更好地支持组件化开发模式 。我们可以把页面拆分成一个个小的组件,每个组件都有自己的功能和职责,然后用 JSX 把这些组件组合起来,构建出复杂的用户界面。比如说,我们有一个导航栏组件、一个内容组件和一个底部组件,我们可以分别用 JSX 来定义这些组件,然后在一个父组件里把它们组合起来,就像拼拼图一样,轻松构建出一个完整的页面。这样的开发模式,不仅让代码的结构更加清晰,而且方便我们复用组件,提高开发效率。就好比搭乐高积木,我们可以用不同的积木块组合出各种各样的造型,每个积木块都可以重复使用,是不是很有趣呢😃?

六、使用 JSX 的注意事项

image.png

(一)根元素要求

在使用 JSX 时,有一个非常重要的规则:JSX 表达式必须有一个根元素 ,就像是大树必须有一个主干一样。这个根元素就像是一个 “大管家”,把所有的子元素都管理起来。如果没有根元素,React 就会变得 “不知所措”,不知道该如何处理这些零散的元素。比如说,下面这段代码就是错误的:

function WrongComponent() {
  return (
    <h1>Title</h1>
    <p>Content</p>
  );
}

这里没有一个根元素来包裹<h1><p>,React 在渲染时就会报错。那怎么解决呢?我们可以使用一个<div>元素来作为根元素 ,把其他元素都包裹起来,就像这样:

function CorrectComponent() {
  return (
    <div>
      <h1>Title</h1>
      <p>Content</p>
    </div>
  );
}

这样,<div>就成为了根元素,把<h1><p>都管理得井井有条。除了<div>,我们还可以使用 Fragment 来作为根元素 ,它就像是一个隐形的 “大管家”,不会在 DOM 中渲染出额外的标签,但能起到包裹元素的作用。比如:

function CorrectComponent() {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  );
}

这里的<>和</>就是 Fragment 的简写形式,效果和<div>一样,但不会增加额外的 DOM 节点,让 DOM 结构更加简洁。所以,在写 JSX 代码时,一定要记得给你的元素找一个 “大管家” 哦😎!

(二)标签闭合规则

在 JSX 的世界里,所有的标签都必须遵守一个严格的规则:必须闭合 ,这就像是每个人都必须遵守交通规则一样。无论是普通的标签,还是自闭合标签,都不能例外。比如说,<div>标签必须有对应的</div>来闭合 ,就像这样:

<div>这是一个有闭合标签的div</div>

如果忘记写结束标签,就会像这样:

<div>这是一个没有闭合的div // 错误!

React 看到这样的代码,就会像交警看到违规的车辆一样,马上给出错误提示。对于自闭合标签,比如<img>、<input>、<br>等,它们不需要结束标签,但必须以/>结尾 ,表示这个标签是自闭合的。例如:

<img src="image.jpg" alt="图片" />
<input type="text" />
<br />

如果不这样写,像<img src="image.jpg" alt="图片">,就会导致语法错误。所以,在写 JSX 代码时,一定要仔细检查标签是否闭合,不要让 React “抓包” 哦😜!

(三)属性命名规范

在 JSX 中,属性名的命名可不是随随便便的,它有自己的一套规范 ,就像是给物品贴上标签,要按照一定的规则来写。属性名需要使用驼峰命名法 ,这和 JavaScript 中的变量命名规则是一致的。比如说,在 HTML 中,我们用class来定义元素的类名,而在 JSX 中,要写成className ,像这样:

<div className="container">这是一个有类名的div</div>

如果写成class,React 就会不认识,然后报错。再比如,for属性在 JSX 中要写成htmlFor ,因为for是 JavaScript 的关键字,为了避免冲突,就采用了htmlFor。例如:

<label htmlFor="input">输入框</label>
<input id="input" type="text" />

除了这些,还有很多属性名在 JSX 中都有对应的驼峰命名形式,比如tabindex要写成tabIndex,onclick要写成onClick等等。另外,一定要避免使用 JavaScript 关键字作为属性名 ,不然就会像在马路上乱闯红灯一样,引发错误。所以,在给元素设置属性名时,一定要按照驼峰命名法来,不要 “闯红灯” 哦😎!

(四)key 属性的重要性

在进行列表渲染时,key属性可是非常重要的 ,它就像是每个列表项的 “身份证”,用来唯一标识每个列表项。当 React 渲染列表时,它会根据key来判断哪些列表项发生了变化,从而进行高效的更新。比如说,我们有一个 TODO 列表,要把每个 TODO 项都显示出来,代码可能是这样的:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>
          {todo.text} - {todo.completed? "Done" : "Pending"}
        </li>
      ))}
    </ul>
  );
}

这里我们使用了index作为key,虽然这样看起来没什么问题,但其实隐藏着一个大问题。当 todos数组的顺序发生变化,或者有新的 TODO 项添加或删除时,index也会跟着变化,这就会让 React 误以为列表项发生了很大的变化,从而进行不必要的重新渲染,影响性能。所以,我们最好使用数据本身的唯一标识符作为key,比如todo.id ,像这样:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.text} - {todo.completed? "Done" : "Pending"}
        </li>
      ))}
    </ul>
  );
}

这样,无论 todos数组怎么变化,每个列表项的key都是唯一不变的,React 就能准确地判断哪些列表项真正发生了变化,从而进行高效的更新,就像是警察通过身份证能准确地识别每个人一样。所以,在列表渲染时,一定要给每个列表项一个独一无二的 “身份证”——key属性哦😎!

(五)dangerouslySetInnerHTML 的风险

dangerouslySetInnerHTML这个属性就像是一把双刃剑 ,它可以用来渲染 HTML 字符串,给我们带来一些便利,但同时也存在着很大的风险。比如说,我们有一个富文本编辑器,用户可以输入 HTML 格式的内容,然后我们想把这些内容显示出来,就可以使用dangerouslySetInnerHTML,代码可能是这样的:

function RichText({ content }) {
  return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// 使用示例
<RichText content="<p>This is <strong>bold</strong> text</p>" />;

这里的content是一个包含 HTML 标签的字符串,通过dangerouslySetInnerHTML属性,我们可以把它渲染成真正的 HTML 内容,在页面上显示出加粗的文本。但是,如果这个content是用户输入的,而且没有经过严格的过滤和验证,就有可能被恶意用户利用,插入一些恶意的 JavaScript 代码,比如:

<RichText content="<script>alert('You are hacked!')</script>" />;

这样,当页面渲染时,就会弹出一个警告框,用户的隐私和安全就会受到威胁,这就是所谓的 XSS(跨站脚本攻击)风险 。所以,在使用dangerouslySetInnerHTML时,一定要非常谨慎,确保渲染的内容是可信的,最好对用户输入的内容进行严格的过滤和验证,避免让恶意代码有可乘之机。就像我们在使用一把锋利的刀时,一定要小心操作,不要伤到自己哦😟!

(六)ref 属性的用途

ref属性就像是一个 “秘密通道”,它可以让我们获取 DOM 元素或组件实例的引用 ,从而对它们进行一些特殊的操作。比如说,我们有一个输入框,想要在页面加载时自动聚焦到这个输入框上,就可以使用ref属性来实现。代码如下:

import React, { useRef, useEffect } from "react";
function InputWithFocus() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={() => inputRef.current.focus()}>Focus Input</button>
    </div>
  );
}

在这个例子中,我们首先使用useRef创建了一个inputRef,初始值为null。然后在useEffect钩子函数中,通过inputRef.current.focus()来获取输入框的引用,并调用它的focus方法,实现页面加载时自动聚焦。在<input>标签中,我们通过ref={inputRef}将inputRef和输入框关联起来。这样,inputRef.current就指向了这个输入框,我们就可以对它进行操作了。除了获取 DOM 元素,ref还可以用于获取组件实例的引用,比如获取一个自定义组件的实例,调用它的方法等。就像有了一个秘密通道,我们可以直接进入到 DOM 元素或组件实例的内部,进行一些特殊的操作,是不是很神奇呢😎?

(七)style 属性的写法

在 JSX 中,style属性用于设置元素的内联样式 ,它的写法和我们平时写 CSS 有点不一样哦。style属性接受一个 JavaScript 对象 ,属性名使用驼峰命名法 。比如说,我们要设置一个div的背景颜色为红色,字体大小为 16 像素,可以这样写:

function StyledComponent() {
  const styles = {
    backgroundColor: "red",
    fontSize: "16px",
  };
  return <div style={styles}>这是一个有样式的div</div>;
}

这里的styles是一个 JavaScript 对象,backgroundColor和fontSize就是 CSS 属性的驼峰命名形式。注意,这里的属性值要用引号括起来,因为它们是字符串类型。如果我们想要动态地改变样式,也很简单,只需要根据条件改变styles对象的值就可以了。例如:

function StyledComponent() {
  const isHovered = true;
  const styles = {
    backgroundColor: isHovered? "blue" : "red",
    fontSize: "16px",
  };
  return <div style={styles}>鼠标悬停试试</div>;
}

在这个例子中,当isHovered为true时,背景颜色为蓝色;当isHovered为false时,背景颜色为红色。通过这种方式,我们可以根据不同的条件来动态地设置元素的样式,就像给元素穿上了不同的 “衣服” 一样,让页面更加丰富多彩😃。

(八)注释语法

在 JSX 中写注释,和在普通 JavaScript 中有点不一样哦 。我们不能直接使用 HTML 的注释方式<!-- -->,而是要使用{/* */}这种特殊的注释语法 。比如说,我们要给一段 JSX 代码添加注释,可以这样写:

function ComponentWithComments() {
  return (
    <div>
      {/* 这是一个单行注释 */}
      <h1>Title</h1>
      {/* 
        这是一个多行注释
        可以跨越多行
        用来解释复杂的逻辑
      */}
      <p>Content</p>
    </div>
  );
}

这里的{/* 这是一个单行注释 /}就是单行注释,{/ 这是一个多行注释 可以跨越多行 用来解释复杂的逻辑 */}就是多行注释。这种注释语法不会在渲染后的 DOM 中显示出来,只会在我们编写代码时起到注释说明的作用,就像是给代码写了一个 “小笔记”,方便我们自己和其他开发者理解代码的含义。所以,在写 JSX 代码时,不要忘记使用正确的注释语法哦,这样可以让我们的代码更加清晰易懂😎。

(九)布尔属性处理

在表单元素中,我们经常会遇到一些布尔属性,比如disabled、checked等 ,它们就像是开关一样,只有开和关两种状态。在 JSX 中,处理这些布尔属性非常简单,我们可以省略它们的值,直接写属性名就表示true 。比如说,我们有一个输入框,想要让它在某些条件下禁用,可以这样写:

function FormExample() {
  const [isDisabled, setIsDisabled] = useState(false);
  return (
    <form>
      <input type="text" disabled={isDisabled} />
      <button type="submit">Submit</button>
    </form>
  );
}

这里的disabled={isDisabled}表示当isDisabled为true时,输入框会被禁用;当isDisabled为false时,输入框正常可用。如果我们想要让一个复选框默认被选中,可以这样写:

function FormExample() {
  const [isChecked, setIsChecked] = useState(true);
  return (
    <form>
      <input type="checkbox" checked={isChecked} />
      <button type="submit">Submit</button>
    </form>
  );
}

这里的checked={isChecked}表示当isChecked为true时,复选框会被默认选中。通过这种方式,我们可以很方便地控制表单元素的状态,就像控制开关一样简单。所以,在处理布尔属性时,记得可以省略值,直接写属性名哦😃。

七、高级 JSX 特性探索

image.png

(一)dangerouslySetInnerHTML 深入

在前面我们提到了dangerouslySetInnerHTML这个属性,它就像是一把双刃剑,虽然能让我们方便地渲染 HTML 字符串,但也隐藏着巨大的 XSS 风险。现在,我们来更深入地了解一下它,看看如何在安全可控的情况下使用它来渲染富文本内容。

比如说,我们有一个博客文章展示组件,文章内容是从后端获取的 HTML 格式的富文本。为了展示这些内容,我们可以使用dangerouslySetInnerHTML,代码如下:

function BlogPost({ content }) {
  return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// 假设从后端获取的文章内容
const articleContent = "<p>This is a <strong>blog post</strong> with some <em>italic</em> text.</p>";
// 使用组件
<BlogPost content={articleContent} />;

在这个例子中,articleContent是从后端获取的 HTML 字符串,通过dangerouslySetInnerHTML属性,我们成功地将其渲染成了富文本内容展示在页面上。但是,这里存在一个很大的问题,如果articleContent是用户输入的,而且没有经过严格的过滤和验证,就有可能被恶意用户利用,插入一些恶意的 JavaScript 代码,比如:

// 恶意用户输入的内容
const maliciousContent = "<script>alert('You are hacked!')</script>";
// 使用组件,可能会导致XSS攻击
<BlogPost content={maliciousContent} />;

当页面渲染这段恶意内容时,就会弹出一个警告框,用户的隐私和安全就会受到威胁。为了避免这种情况,我们需要对用户输入的内容进行严格的过滤和验证,确保渲染的内容是安全的。可以使用一些第三方库,比如DOMPurify,它可以帮助我们净化 HTML 字符串,去除其中的恶意代码。使用DOMPurify的示例代码如下:

import DOMPurify from 'dompurify';
function BlogPost({ content }) {
  const cleanContent = DOMPurify.sanitize(content);
  return <div dangerouslySetInnerHTML={{ __html: cleanContent }} />;
}

通过DOMPurify.sanitize方法,我们对content进行了净化,去除了其中可能存在的恶意代码,这样就可以安全地使用dangerouslySetInnerHTML来渲染富文本内容了。所以,在使用dangerouslySetInnerHTML时,一定要谨慎操作,做好安全防护措施,不要让这把双刃剑伤到自己哦😟!

(二)ref 属性进阶

在前面,我们已经了解了ref属性可以用于获取 DOM 元素或组件实例的引用,比如实现自动聚焦输入框。现在,我们来进一步探索ref属性在函数组件中的高级用法,结合useRef和其他属性实现更多有趣的交互功能。

除了自动聚焦输入框,我们还可以使用ref来操作 DOM 元素的其他属性和方法 。比如说,我们有一个图片元素,想要在用户点击按钮时,动态地改变图片的src属性,就可以借助ref来实现。代码如下:

import React, { useRef } from "react";
function ImageComponent() {
  const imageRef = useRef(null);
  const handleClick = () => {
    imageRef.current.src = "new-image.jpg";
  };
  return (
    <div>
      <img ref={imageRef} src="old-image.jpg" alt="图片" />
      <button onClick={handleClick}>Change Image</button>
    </div>
  );
}

在这个例子中,我们通过useRef创建了一个imageRef,并将其绑定到<img>元素上。当用户点击按钮时,handleClick函数被调用,通过imageRef.current.src,我们可以直接操作图片元素的src属性,实现图片的动态切换。

ref还可以用于获取自定义组件的实例 ,调用组件内部的方法。比如说,我们有一个自定义的MyComponent组件,它内部有一个printMessage方法,我们想要在父组件中调用这个方法,就可以这样做:

import React, { useRef } from "react";
// 自定义组件
function MyComponent() {
  const printMessage = () => {
    console.log("This is a message from MyComponent");
  };
  return <div>My Component</div>;
}
// 父组件
function ParentComponent() {
  const myComponentRef = useRef(null);
  const handleClick = () => {
    if (myComponentRef.current) {
      myComponentRef.current.printMessage();
    }
  };
  return (
    <div>
      <MyComponent ref={myComponentRef} />
      <button onClick={handleClick}>Call Component Method</button>
    </div>
  );
}

在这个例子中,我们在ParentComponent中通过useRef创建了一个myComponentRef,并将其绑定到MyComponent组件上。当用户点击按钮时,handleClick函数被调用,通过myComponentRef.current.printMessage(),我们成功地调用了MyComponent组件内部的printMessage方法。通过这些高级用法,ref属性就像是给我们打开了一扇通往 DOM 元素和组件内部的大门,让我们可以实现更多复杂的交互功能,是不是很强大呢😎?

(三)style 属性高级用法

在前面,我们已经知道style属性接受一个 JavaScript 对象,属性名使用驼峰命名法来设置元素的内联样式。现在,我们来探索一下style属性的高级用法,通过对象字面量和计算属性动态生成复杂的内联样式,实现更灵活的样式控制。

比如说,我们有一个按钮组件,想要根据按钮的状态(是否被点击、是否被禁用等)来动态地改变按钮的样式,就可以使用对象字面量和计算属性来实现。代码如下:

import React, { useState } from "react";
function Button() {
  const [isClicked, setIsClicked] = useState(false);
  const [isDisabled, setIsDisabled] = useState(false);
  const buttonStyles = {
    backgroundColor: isClicked ? "green" : isDisabled ? "gray" : "blue",
    color: "white",
    padding: "10px 20px",
    borderRadius: "5px",
    border: "none",
    cursor: isDisabled? "not-allowed" : "pointer",
  };
  const handleClick = () => {
    if (!isDisabled) {
      setIsClicked(!isClicked);
    }
  };
  const handleDisable = () => {
    setIsDisabled(!isDisabled);
  };
  return (
    <div>
      <button style={buttonStyles} onClick={handleClick} disabled={isDisabled}>
        {isClicked? "Clicked" : "Click me"}
      </button>
      <button onClick={handleDisable}>
        {isDisabled? "Enable Button" : "Disable Button"}
      </button>
    </div>
  );
}

在这个例子中,我们定义了一个buttonStyles对象,通过计算属性根据isClicked和isDisabled的状态来动态地设置按钮的背景颜色、文本颜色、内边距、边框半径、边框样式和鼠标指针样式。当按钮被点击时,isClicked状态改变,按钮的背景颜色会变成绿色;当按钮被禁用时,isDisabled状态改变,按钮的背景颜色会变成灰色,鼠标指针会变成禁止状态。通过这种方式,我们可以根据不同的条件来动态地生成复杂的内联样式,实现更加灵活和丰富的样式控制,就像给按钮穿上了不同的 “魔法外衣”,让它在不同的状态下展现出不同的风格😎。

(四)注释语法高级应用

在前面,我们已经了解了在 JSX 中使用{/* */}这种特殊的注释语法来添加注释。现在,我们来看看注释语法在条件渲染、复杂逻辑处的高级应用,通过注释来解释代码逻辑和用途,提高代码的可读性。

比如说,在一个用户信息展示组件中,我们需要根据用户的登录状态和权限来显示不同的内容,这里的逻辑比较复杂,我们就可以使用注释来解释代码的意图。代码如下:

function UserInfo({ isLoggedIn, userRole }) {
  return (
    <div>
      {/* 根据用户的登录状态进行条件渲染 */}
      {isLoggedIn? (
        <div>
          {/* 如果用户已登录,根据用户角色显示不同内容 */}
          {userRole === "admin"? (
            <p>Welcome, Admin! You have full access.</p>
          ) : (
            <p>Welcome, User! You have limited access.</p>
          )}
        </div>
      ) : (
        <p>Please log in to access your information.</p>
      )}
    </div>
  );
}

在这个例子中,我们在条件渲染的地方添加了注释,解释了代码的逻辑。首先,根据isLoggedIn判断用户是否登录,如果已登录,再根据userRole判断用户角色,显示不同的内容。这样,当其他开发者查看这段代码时,就能很容易地理解代码的意图,提高了代码的可读性和可维护性。

再比如,在一个复杂的表单验证函数中,我们也可以使用注释来解释每一步的验证逻辑 。代码如下:

function validateForm({ username, password }) {
  // 验证用户名是否为空
  if (!username) {
    return "Username is required";
  }
  // 验证用户名长度是否符合要求
  if (username.length < 3) {
    return "Username must be at least 3 characters long";
  }
  // 验证密码是否为空
  if (!password) {
    return "Password is required";
  }
  // 验证密码长度是否符合要求
  if (password.length < 6) {
    return "Password must be at least 6 characters long";
  }
  return true;
}

在这个表单验证函数中,我们在每一步验证逻辑的地方都添加了注释,解释了验证的目的和条件。这样,当代码需要修改或调试时,我们就能快速地定位到问题所在,提高了开发效率。所以,不要小看注释的作用,它就像是代码的 “说明书”,能够帮助我们更好地理解和维护代码😎。

(五)布尔属性处理技巧

在前面,我们已经了解了在表单元素中处理布尔属性的基本方法,比如disabled、checked等属性,通过省略值来表示true。现在,我们通过更多表单元素示例,来展示布尔属性在不同场景下的灵活处理方式,如根据状态动态切换属性值。

比如说,我们有一个多选框组,需要根据用户的选择来动态地禁用或启用提交按钮 。代码如下:

import React, { useState } from "react";
function CheckboxGroup() {
  const [isChecked1, setIsChecked1] = useState(false);
  const [isChecked2, setIsChecked2] = useState(false);
  const handleCheck1 = () => {
    setIsChecked1(!isChecked1);
  };
  const handleCheck2 = () => {
    setIsChecked2(!isChecked2);
  };
  const isAllChecked = isChecked1 && isChecked2;
  return (
    <div>
      <input
        type="checkbox"
        checked={isChecked1}
        onChange={handleCheck1}
      />{" "}
      Option 1
      <input
        type="checkbox"
        checked={isChecked2}
        onChange={handleCheck2}
      />{" "}
      Option 2
      <button disabled={!isAllChecked}>Submit</button>
    </div>
  );
}

在这个例子中,我们有两个多选框,通过useState来管理它们的选中状态。isAllChecked变量用于判断两个多选框是否都被选中,如果都被选中,isAllChecked为true,提交按钮的disabled属性为false,按钮可用;如果有一个多选框未被选中,isAllChecked为false,提交按钮的disabled属性为true,按钮禁用。通过这种方式,我们根据用户的选择状态动态地切换了提交按钮的disabled属性值,实现了更灵活的表单交互。

再比如,我们有一个单选框组,需要根据用户选择的性别来显示不同的提示信息 。代码如下:

import React, { useState } from "react";
function RadioGroup() {
  const [selectedGender, setSelectedGender] = useState("");
  const handleGenderChange = (e) => {
    setSelectedGender(e.target.value);
  };
  return (
    <div>
      <input
        type="radio"
        value="male"
        checked={selectedGender === "male"}
        onChange={handleGenderChange}
      />{" "}
      Male
      <input
        type="radio"
        value="female"
        checked={selectedGender === "female"}
        onChange={handleGenderChange}
      />{" "}
      Female
      {selectedGender === "male" && (
        <p>You selected male. Some male - specific information here.</p>
      )}
      {selectedGender === "female" && (
        <p>You selected female. Some female - specific information here.</p>
      )}
    </div>
  );
}

在这个例子中,我们有两个单选框,通过useState来管理用户选择的性别。当用户选择不同的性别时,selectedGender的值会改变,通过条件渲染,根据selectedGender的值显示不同的提示信息。这里,单选框的checked属性根据selectedGender的值动态地切换,实现了根据用户选择来展示不同内容的功能。通过这些示例,我们可以看到布尔属性在表单元素中的灵活应用,能够根据不同的状态和条件来动态地控制表单的行为和展示,让表单更加智能和友好😃。

(六)子元素传递

在 React 中,使用children prop 传递子元素是一种非常强大的组件组合和复用方式。现在,我们以卡片组件为例,来展示如何使用children prop 传递子元素,实现组件的灵活组合和复用。

比如说,我们有一个通用的卡片组件Card,它可以包含不同的内容,如标题、描述、按钮等。我们可以通过children prop 将这些内容传递给Card组件,代码如下:

function Card({ title, children }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-content">{children}</div>
    </div>
  );
}

在这个Card组件中,title属性用于设置卡片的标题,children prop 用于接收传递进来的子元素。这些子元素可以是任何 JSX 元素,比如<p>、<button>等。使用Card组件时,我们可以这样传递子元素:

<Card title="User Info">
  <p>Name: John Doe</p>
  <p>Email: john@example.com</p>
  <button>Edit</button>
</Card>

在这个例子中,我们将用户信息和一个编辑按钮作为子元素传递给了Card组件。Card组件会将User Info作为标题显示,然后将<p>Name: John Doe</p>、<p>Email: <john@example.com></p>和<button>Edit</button>这些子元素渲染在卡片内容区域。通过这种方式,我们可以轻松地复用Card组件,根据不同的需求传递不同的子元素,实现各种不同的卡片布局。比如,我们还可以传递一个图片和一段描述:

<Card title="Product">
  <img src="product.jpg" alt="Product" />
  <p>Description: This is a great product.</p>
</Card>

这样,Card组件就变成了一个产品展示卡片。通过使用children prop 传递子元素,我们可以将不同的组件组合在一起,实现组件的灵活复用,就像搭积木一样,用不同的积木块组合出各种各样的造型,让我们的代码更加简洁和高效😎。

(七)条件渲染的高级技巧

在前面,我们已经了解了使用三元运算符、逻辑与和逻辑或运算符进行条件渲染的基本方法。现在,我们通过用户信息展示组件示例,来展示使用函数进行条件渲染的方式,使代码逻辑更清晰,易于维护。

比如说,我们有一个用户信息展示组件UserProfile,需要根据用户的登录状态、是否正在加载以及是否有错误来显示不同的内容。如果直接在 JSX 中使用三元运算符或逻辑运算符,代码可能会变得比较复杂,难以阅读和维护。这时候,我们可以使用函数来进行条件渲染,代码如下:

function UserProfile({ user, isLoading, error }) {
  const renderContent = () => {
    if (isLoading) {
      return <div>Loading...</div>;
    }
    if (error) {
      return <div>Error: {error.message}</div>;
    }
    if (!user) {
      return <div>No user found</div>;
    }
    return (
      <div>
        <h2>{user.name}</h2>

八、实际应用示例展示

image.png

现在,让我们通过一个完整的用户个人资料编辑组件示例,来更深入地感受JSX在实际项目中的魅力。这个组件综合运用了条件渲染、事件处理、状态管理等核心概念,就像是一个小型的React应用生态系统,麻雀虽小,五脏俱全😎。 假设我们正在开发一个社交平台,用户可以在个人资料页面查看和编辑自己的信息。下面是这个用户个人资料编辑组件的代码实现:

import React, { useState } from "react";
function UserProfile({ user }) {
  const [isEditing, setIsEditing] = useState(false);
  const [name, setName] = useState(user.name);
  const handleSave = () => {
    // 这里可以添加保存逻辑,比如发送请求到后端更新用户信息
    // 暂时先模拟保存成功的操作
    setIsEditing(false);
    console.log(`Name updated to: ${name}`);
  };
  return (
    <div className="user-profile">
      <div className="profile-header">
        <img
          src={user.avatar}
          alt={`${user.name}'s avatar`}
          className="avatar"
        />
        <div className="user-info">
          {isEditing ? (
            <input
              value={name}
              onChange={(e) => setName(e.target.value)}
              className="name-input"
            />
          ) : (
            <h2 className="user-name">{name}</h2>
          )}
          <p className="user-email">{user.email}</p>
        </div>
      </div>
      <div className="profile-actions">
        {isEditing? (
          <>
            <button onClick={handleSave} className="btn-save">
              Save
            </button>
            <button onClick={() => setIsEditing(false)} className="btn-cancel">
              Cancel
            </button>
          </>
        ) : (
          <button onClick={() => setIsEditing(true)} className="btn-edit">
            Edit Profile
          </button>
        )}
      </div>
    </div>
  );
}
export default UserProfile;

在这个组件中,我们使用了useState钩子来管理组件的状态 。isEditing状态用于判断用户是否处于编辑模式,初始值为false;name状态用于存储用户的姓名,初始值为从父组件传递过来的user.name。

在profile-header部分,我们通过条件渲染来展示不同的内容 。当isEditing为true时,显示一个输入框,用户可以在其中编辑姓名,并且输入框的值会随着用户的输入实时更新;当isEditing为false时,显示用户的姓名。

在profile-actions部分,同样使用条件渲染来显示不同的按钮 。如果用户处于编辑模式,显示 “Save” 和 “Cancel” 按钮;如果用户不在编辑模式,显示 “Edit Profile” 按钮。

点击 “Edit Profile” 按钮,会触发onClick事件,调用setIsEditing(true),将isEditing状态设置为true,进入编辑模式;点击 “Save” 按钮,会触发handleSave函数,在这个函数中,我们可以添加实际的保存逻辑,比如发送请求到后端更新用户信息,这里我们先简单地模拟保存成功的操作,将isEditing状态设置为false,并在控制台打印更新后的姓名;点击 “Cancel” 按钮,会触发onClick事件,调用setIsEditing(false),取消编辑,回到查看模式。

通过这个示例,我们可以看到 JSX 如何与 React 的状态管理和事件处理机制紧密结合,构建出一个完整的、交互性强的用户界面 。它让我们能够以一种直观、简洁的方式来描述界面的结构和行为,大大提高了开发效率和代码的可读性。就像搭建一个乐高城堡,我们可以用不同的积木块(JSX 元素),根据不同的条件(状态)和用户的操作(事件),组合出各种各样的功能和界面,是不是很有趣呢😃?

九、常见问题与解决方案

image.png

(一)JSX 中的常见错误

在使用 JSX 的过程中,我们难免会遇到一些错误,就像是在游戏中遇到了小怪兽,需要我们去一一打败它们😜。下面我们就来看看一些常见的错误,以及如何解决它们。

// ❌ 错误:多个根元素
function WrongComponent() {
  return (
    <h1>Title</h1>
    <p>Content</p>
  )
}

在这个例子中,WrongComponent组件返回了两个没有被包裹在同一个根元素中的元素,这是 JSX 不允许的。就好比你要把两个物品放进一个盒子里,但却没有找到一个足够大的盒子,这肯定是不行的😅。解决这个问题的方法很简单,我们只需要给它们找一个 “大盒子”,也就是一个根元素,比如<div>或者<React.Fragment>。

// ✅ 正确:使用Fragment
function CorrectComponent() {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  )
}

这里我们使用了<>和</>(也就是<React.Fragment>的简写形式)作为根元素,把<h1>和<p>包裹起来,这样就符合 JSX 的规则啦,就像是给两个物品找到了合适的盒子,它们可以和谐共处了😎。

(二)属性名错误

在 JSX 中,属性名的命名规则和 HTML 有所不同,这也是我们容易犯错的地方。比如,在 HTML 中,我们使用class来定义元素的类名,而在 JSX 中,要使用className;在 HTML 中,for属性用于关联表单元素和标签,而在 JSX 中要写成htmlFor。如果我们不小心使用了 HTML 的属性名,就会出现错误。

// ❌ 错误:使用HTML属性名
<div class="container">
<button onclick={handleClick}>

在这段代码中,class和onclick都是 HTML 中的属性名,在 JSX 中是错误的用法。React 看到这样的代码,就会像老师看到学生写错作业一样,马上给出错误提示😣。正确的写法应该是:

// ✅ 正确:使用JSX属性名
<div className="container">
<button onClick={handleClick}>

这里我们把class改成了className,把onclick改成了onClick,遵循了 JSX 的属性命名规则,这样代码就能正常运行啦,就像是学生改正了作业,得到了老师的表扬😎。

(三)表达式错误

在 JSX 中,我们不能直接使用if语句 ,这也是很多新手容易犯的错误。因为 JSX 本质上是 JavaScript 的语法扩展,它最终会被编译成 JavaScript 函数调用,而在函数调用中是不能直接使用if语句的。就好比你不能在餐厅里直接要求厨师按照你的独特秘方做菜,你得按照餐厅的菜单来点单😅。

// ❌ 错误:在JSX中使用if语句
function WrongExample() {
  return (
    <div>
      {if (condition) {
        return <p>True</p>
      }}
    </div>
  )
}

在这个例子中,我们试图在 JSX 中直接使用if语句,这是不允许的。那么,我们该如何在 JSX 中进行条件判断呢🧐?答案是使用三元运算符或逻辑运算符 。

// ✅ 正确:使用三元运算符或逻辑运算符
function CorrectExample() {
  return (
    <div>
      {condition? <p>True</p> : <p>False</p>}
      {condition && <p>True</p>}
    </div>
  )
}

这里我们使用了三元运算符condition? <p>True</p> : <p>False</p>,根据condition的值来决定渲染<p>True</p>还是<p>False</p>;还使用了逻辑与运算符condition && <p>True</p>,只有当condition为true时,才会渲染<p>True</p>。通过这些方式,我们可以在 JSX 中实现条件判断,就像是按照餐厅的菜单点到了自己想吃的菜,满足又开心😎。

十、JSX 与 TypeScript 的奇妙组合

image.png

在 React 开发中,JSX 已经为我们带来了很多便利,而当它与 TypeScript 携手合作时,就像是超级英雄组队,能发挥出更强大的力量!TypeScript 为 JavaScript 添加了静态类型检查,让我们的代码更加健壮和可靠,与 JSX 搭配使用,简直是天作之合😎。下面我们就来看看它们是如何强强联手的。

(一)类型定义

在 TypeScript 中,为 JSX 组件定义类型就像是给每个组件贴上了清晰的标签,让我们在开发过程中能更好地理解和使用它们。我们可以通过接口(interface)或类型别名(type alias)来定义组件的属性类型和函数返回值类型 ,从而提高代码的类型安全性。

比如说,我们有一个用户组件UserComponent,它接收name、age、email和onUpdate等属性,我们可以这样定义它的属性类型:

interface UserProps {
  name: string;
  age: number;
  email?: string; // 这里的?表示email属性是可选的,可有可无
  onUpdate: (id: string) => void; // 定义onUpdate属性是一个函数,接收一个string类型的id参数,返回值类型为void,表示没有返回值
}
function UserComponent({ name, age, email, onUpdate }: UserProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      {email && <p>Email: {email}</p>} {/* 如果email存在,就渲染这个p元素显示邮箱 */}
      <button onClick={() => onUpdate("user-id")}>Update</button> {/* 点击按钮时调用onUpdate函数,并传入"user-id" */}
    </div>
  );
}

在这个例子中,我们通过interface定义了UserProps接口,它描述了UserComponent组件所接收的属性及其类型。name是字符串类型,age是数字类型,email是可选的字符串类型,onUpdate是一个函数类型。这样,当我们在使用UserComponent组件时,如果传入的属性类型不符合定义,TypeScript 就会立刻报错,提醒我们进行修正。比如,如果我们不小心把age写成了字符串类型:

<UserComponent name="John" age="25" email="john@example.com" onUpdate={() => {}} /> // 这里会报错,因为age应该是number类型

TypeScript 就会给出错误提示,让我们及时发现并解决问题,避免在运行时出现难以调试的错误。通过这种方式,我们可以在开发阶段就保证代码的正确性,大大提高了开发效率和代码质量,就像是给代码穿上了一层坚固的铠甲,让它更加安全可靠😎。

(二)泛型组件

泛型组件就像是一个万能的模板,它可以根据不同的需求生成不同类型的组件,让我们的组件更具通用性和可复用性 。在 TypeScript 中,使用泛型组件可以让我们的代码更加灵活,适应不同类型的数据渲染。

比如说,我们有一个列表组件List,它可以用来渲染不同类型的数据列表,我们可以通过泛型来实现这个功能:

interface ListProps<T> {
  items: T[]; // 定义items属性是一个T类型的数组,T是泛型,具体类型由使用时决定
  renderItem: (item: T) => React.ReactNode; // 定义renderItem属性是一个函数,接收一个T类型的item参数,返回值类型为React.ReactNode,表示可以返回任何React节点
}
function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

在这个例子中,我们定义了一个泛型组件List,它接收一个ListProps<T>类型的参数。ListProps<T>接口中,items属性是一个T类型的数组,renderItem属性是一个函数,用于渲染每个列表项。T是一个泛型参数,它可以代表任何类型,具体的类型在使用List组件时确定。

使用这个泛型组件时,我们可以传入不同类型的数据列表和渲染函数,比如:

// 渲染字符串列表
<List
  items={["apple", "banana", "orange"]}
  renderItem={(item) => <span>{item}</span>}
/>;
// 渲染数字列表
<List
  items={[1, 2, 3]}
  renderItem={(item) => <span>{item * 2}</span>}
/>;
// 渲染对象列表
interface User {
  name: string;
  age: number;
}
const users: User[] = [
  { name: "John", age: 25 },
  { name: "Jane", age: 30 },
];
<List
  items={users}
  renderItem={(item) => (
    <div>
      <p>Name: {item.name}</p>
      <p>Age: {item.age}</p>
    </div>
  )}
/>;

通过使用泛型组件,我们可以复用List组件的代码,根据不同的数据类型和渲染需求,灵活地生成不同的列表,大大提高了代码的复用性和开发效率。就像是有了一个万能的模具,可以根据我们的需要制作出各种形状的零件,是不是很厉害呢😎?

十一、最佳实践总结

image.png

在使用 JSX 进行 React 开发的过程中,遵循一些最佳实践可以让我们的代码更加简洁、高效、易维护。就像是在建造一座房子时,遵循正确的建筑规范和设计原则,才能让房子既美观又坚固。下面我们就来总结一下使用 JSX 的最佳实践,帮助大家写出高质量的 React 代码😎。

(一)保持组件简洁

每个组件都应该只负责一个单一的功能 ,这就像是一个人只专注于一项任务,才能把它做到最好。如果一个组件承担了过多的功能,就会变得臃肿和难以维护。比如,我们有一个用户管理组件,它既要负责用户信息的展示,又要处理用户的登录、注册、删除等操作,这样的组件就会变得非常复杂,代码也会变得难以理解和修改。我们应该将这些功能拆分成多个小的组件,每个组件只负责一个功能,比如UserInfoComponent负责用户信息展示,LoginComponent负责用户登录,RegisterComponent负责用户注册等。这样,每个组件都很简洁,易于维护和复用,就像是把一个大工程拆分成了多个小任务,每个任务都能高效完成😃。

(二)使用有意义的组件名

给组件取一个有意义的名字,就像是给一个人取一个容易记住的名字一样重要 。使用 PascalCase 命名组件,能够让我们一眼就看出这是一个组件,并且能够从名字中大致了解组件的功能。比如,HeaderComponent表示这是一个头部组件,ButtonComponent表示这是一个按钮组件。避免使用一些模糊的名字,比如Component1、MyComponent等,这样的名字很难让人理解组件的用途。一个好的组件名能够提高代码的可读性,让其他开发者能够快速理解代码的结构和功能,就像是一个好的地图能够让我们快速找到目的地一样😎。

(三)合理使用 Fragment

在需要返回多个元素时,合理使用 Fragment 可以避免添加不必要的 DOM 节点 ,就像是在打包行李时,只带必要的物品,避免增加不必要的负担。比如,我们有一个组件需要返回一个标题和一个描述,使用 Fragment 可以这样写:

function MyComponent() {
  return (
    <>
      <h1>Title</h1>
      <p>Description</p>
    </>
  );
}

这样,在 DOM 中不会增加额外的节点,保持了 DOM 结构的简洁,同时也提高了性能。如果不使用 Fragment,我们可能需要添加一个<div>来包裹这两个元素,这样就会增加一个不必要的 DOM 节点。所以,在合适的场景下使用 Fragment,能够让我们的代码更加优雅和高效😃。

(四)正确处理 key 属性

在列表渲染时,正确处理key属性是非常重要的 ,它就像是给每个列表项贴上了一个独一无二的标签,让 React 能够准确地识别和管理它们。使用稳定的唯一标识符作为key,比如数据本身的id,可以确保 React 在更新列表时能够高效地进行,避免出现不必要的重新渲染。比如,我们有一个 TODO 列表,每个 TODO 项都有一个唯一的id,我们可以这样设置key:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.text} - {todo.completed? "Done" : "Pending"}
        </li>
      ))}
    </ul>
  );
}

这样,无论 todos数组如何变化,React 都能根据id准确地判断哪些列表项发生了变化,从而进行高效的更新。如果使用数组索引作为key,当数组顺序发生变化时,React 可能会误判列表项的变化,导致性能问题。所以,一定要正确处理key属性,让列表渲染更加高效和稳定😎。

(五)避免内联样式

对于复杂的样式,尽量避免使用内联样式 ,就像是在装修房子时,不要在每个房间都单独刷漆,而是统一规划。内联样式会让代码变得杂乱无章,难以维护和复用。比如,我们有一个按钮组件,需要设置多种样式,如果使用内联样式,代码可能会变得很长很复杂:

<button
  style={{
    backgroundColor: "blue",
    color: "white",
    padding: "10px 20px",
    borderRadius: "5px",
    border: "none",
    cursor: "pointer",
  }}
>
  Click me
</button>

我们可以将这些样式提取到一个 CSS 类中,然后在组件中使用这个类,这样代码就会更加简洁和易读:

<button className="my-button">Click me</button>

在 CSS 文件中定义.my-button的样式:

.my-button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
  border: none;
  cursor: pointer;
}

通过这种方式,我们可以将样式和逻辑分离,提高代码的可维护性和复用性,就像是将不同的功能区域分开,让整个房子更加整洁有序😃。

(六)使用条件渲染

在 JSX 中,使用条件渲染来根据不同的条件显示或隐藏元素,而不是在 JSX 中使用if语句 ,就像是根据天气情况选择合适的衣服。比如,我们有一个用户组件,需要根据用户的登录状态显示不同的内容,使用条件渲染可以这样写:

function UserComponent({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn? (
        <p>Welcome, user!</p>
      ) : (
        <p>Please log in.</p>
      )}
    </div>
  );
}

这样,代码简洁明了,逻辑清晰。如果在 JSX 中直接使用if语句,会导致语法错误。所以,合理使用条件渲染,能够让我们的代码更加简洁和易于理解😎。

(七)合理使用注释

在代码中合理使用注释,就像是在旅行中标记重要的景点,能够帮助我们更好地理解代码的逻辑和用途 。特别是在一些复杂的逻辑处,添加注释可以让其他开发者快速理解代码的意图。比如,在一个复杂的表单验证函数中,我们可以这样添加注释:

function validateForm({ username, password }) {
  // 验证用户名是否为空
  if (!username) {
    return "Username is required";
  }
  // 验证用户名长度是否符合要求
  if (username.length < 3) {
    return "Username must be at least 3 characters long";
  }
  // 验证密码是否为空
  if (!password) {
    return "Password is required";
  }
  // 验证密码长度是否符合要求
  if (password.length < 6) {
    return "Password must be at least 6 characters long";
  }
  return true;
}

通过这些注释,我们可以清楚地看到每个验证步骤的目的和条件,方便代码的维护和修改。所以,不要吝啬你的注释,它是代码的好帮手😃。

(八)性能优化

在 React 开发中,性能优化是非常重要的,就像是给汽车保养,让它跑得更快更稳 。使用memo、useMemo、useCallback等方法可以有效地优化组件的性能。memo可以用于函数组件,它会对组件的props进行浅比较,如果props没有变化,就不会重新渲染组件,从而提高性能。比如:

const MyComponent = memo((props) => {
  // 组件逻辑
});

useMemo可以缓存计算结果,避免不必要的重复计算 。比如,我们有一个组件需要计算一个复杂的值,如果每次渲染都重新计算,会消耗大量的性能,我们可以使用useMemo来缓存这个计算结果:

const value = useMemo(() => {
  // 复杂的计算逻辑
  return result;
}, [dependency]);

useCallback可以缓存函数,避免函数的重复创建 。比如,我们有一个函数需要作为props传递给子组件,如果每次渲染都重新创建这个函数,会导致子组件不必要的重新渲染,我们可以使用useCallback来缓存这个函数:

const handleClick = useCallback(() => {
  // 点击事件处理逻辑
}, [dependency]);

通过这些性能优化方法,我们可以让 React 应用更加高效地运行,提升用户体验😎。

(九)错误处理

使用错误边界来捕获组件错误,就像是给房子安装了一个安全警报系统,能够及时发现并处理问题 。错误边界是一种 React 组件,它可以捕获子组件树中的 JavaScript 错误,并展示一个友好的错误提示,而不会导致整个应用崩溃。比如,我们有一个ErrorBoundary组件:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    console.log("Error:", error);
    console.log("Error Info:", errorInfo);
  }
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

在使用其他组件时,我们可以将其包裹在ErrorBoundary组件中:

<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

这样,如果MyComponent中发生错误,ErrorBoundary会捕获这个错误,并显示错误提示,而不会影响整个应用的运行。通过使用错误边界,我们可以提高应用的稳定性和用户体验😃。

(十)类型安全

配合 TypeScript 使用 JSX,可以获得更好的类型安全和开发体验 ,就像是给代码穿上了一层坚固的铠甲,让它更加可靠。TypeScript 可以在开发阶段就发现类型错误,避免在运行时出现难以调试的错误。比如,我们可以为组件定义明确的类型:

interface UserProps {
  name: string;
  age: number;
}
function UserComponent({ name, age }: UserProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
    </div>
  );
}

这样,当我们在使用UserComponent时,如果传入的props类型不符合定义,TypeScript 就会报错,提醒我们进行修正。通过使用 TypeScript,我们可以提高代码的质量和可维护性,让开发过程更加顺畅😎。

十二、JSX 学习的最终秘籍

image.png

到这里,关于 JSX 的知识就给大家介绍得差不多啦🎉!回顾一下,我们从 JSX 的基本概念出发,深入探讨了它的语法规则、编译过程、优势、注意事项,还探索了它的高级特性、在实际项目中的应用,以及和 TypeScript 的搭配使用。JSX 就像是 React 开发中的魔法咒语,掌握了它,我们就能轻松构建出各种各样炫酷的用户界面😎。

在 React 开发中,JSX 绝对是核心中的核心,它让我们的代码变得更加直观、高效和易于维护。通过这篇文章,希望大家已经对 JSX 有了全面而深入的理解。但是,学习编程可不能光说不练哦,就像学游泳不能只在岸上看,得跳进水里才能真正掌握。所以,大家一定要多多实践,在实际项目中不断运用 JSX,才能真正掌握它的各种特性和最佳实践🧐。

当你在开发中遇到问题时,不要害怕,要善于利用各种资源,比如官方文档、技术论坛、Stack Overflow 等,那里有无数的开发者在分享自己的经验和解决方案,说不定就能找到你问题的答案。而且,学习的过程中,要多和其他开发者交流,分享自己的见解和心得,这样不仅能帮助别人,也能让自己进步得更快😃。

最后,希望大家在 React 开发的道路上越走越远,用 JSX 创造出更多精彩的应用,成为 React 开发的大神!如果在学习过程中有任何疑问或者心得,都欢迎在评论区留言,让我们一起交流,共同进步💪!