React JSX语法与组件

简介: JSX的语法是一种JavaScript的扩展。在React中使用JSX描述一个UI是什么样子的,就好像HTML告诉浏览器我们看到的页面是什么样子。最开始接触JSX时会感觉它很像一种模板语言,但是除了提供模板能力之外,他拥有JavaScript所有的能力。

JSX基础介绍

先看看一个最简单的例子:

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

上面这段有趣的例子既不是标准的JavaScript也不是HTML,它就是我们接下来要介绍的JSX的语法,是一种JavaScript的扩展。在React中使用JSX描述一个UI是什么样子的,就好像HTML告诉浏览器我们看到的页面是什么样子。最开始接触JSX时会感觉它很像一种模板语言,但是除了提供模板能力之外,他拥有JavaScript所有的能力。

JSX用于产生React的组件,JSX最大的特色就是就是在JavaScript中嵌入和HTML表达式。我们先看下面这个例子:

function formatName(user) {
  //将参数合并成一个srting
  return user.firstName + ' ' + user.lastName;
}

//创建user对象
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

//创建element对象,并用JSX语法标识为一个html内容。
//h1标签中会包含方法计算之后的内容
const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

测试代码

这个例子将JSX语法分成了很多部分,element就是一个HTML的JSX表达式,HTML标签最好使用一组()括号包裹起来以避免分号导致的问题(分号可能会在编译时成为HTML内容的一部分)。ReactDOM是一个react工具,用于提供Dom渲染功能。ReactDOM.render 方法接受2个参数,一个是要渲染的JSX元素,另外一个是Dom对象,render会在这个Dom对象中添加由JSX定义的HTML。

JSX是一种丰富的表达式,他可以随意嵌套JavaScript和HTML使用,例如if、for等等,比如:

function getGreeting(user) {
  // 使用if来判断输入参数,根据判断结果来输出HTML内容
  if (user) {
    return <h1>Hello, {formatName(user)}</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

源生的HTML可以任意指定属性,同样在JSX中也有这个能力,例如:

const element = <div tabIndex="0"></div>;
//或
const element = <img src={user.avatarUrl}></img>;

也可以直接用 />表示一个HTML标签的闭环:

const element = <img src={user.avatarUrl} />;

当然也可以同时声明父元素和子元素:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

需要注意的是:由于JSX更像JavaScript,在使用JSX语法时建议使用驼峰规范来命名。例如将标签上的"class"命名为"className"。

JSX天生具备防止注入攻击的能力。ReactDom在渲染之前会转义所有嵌入JSX中的值,所以他能确保没有任何特殊的内容被注入到最终的HTML代码中。在渲染之前,所有的东西都会转换成string类型,这将能有效的防止XSS攻击。

JSX对象

首先需要强调的是,JSX对象就是一个JavaScript对象,所有的JSX表达式最终都会转义成JavaScript。有两种方法可以创建JSX对象:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

和

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

上面2种创建JSX对象的方法结果都是一样的。使用React.createElement() 方法的好处是它会执行一些检查,以帮助我们编写无错误的代码。最终通过转义,他会输出这样一个结构:

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};

官方将这个对象的结构称为React元素。React通过这个对象来控制浏览器对dom的渲染,最终显示我们想要的内容。

渲染React元素

前一小节提到的React元素是React的基本单元,React会由一个一个的基本单元组成,最终构建成一个有效的体系(组件化)。每一个元素用来描述想在屏幕上展示什么。

和Dom结构不同的是, React元素是一个纯粹的对象并且比创建一个Dom花费的资源更少。React会全局维护所有的元素,并在合适的时候更新到浏览器的Dom,这就是虚拟Dom管理机制。

将一个元素渲染成为Dom

从一个简单的div标签开始:

<div id="root"></div>

这是一个“根元素”,我们将通过ReactDom来管理他的所有子元素。一个RreactDom.render方法只能用来渲染一个Dom元素。如果想同时对多个元素进行渲染,可以使用互不关联的RreactDom.render方法来对不同的Dom元素进行操作。

下面的例子将一个JSX元素渲染到Dom中,完成后,会在页面中显示Hello world:

const element = <h1>Hello, world</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);

测试代码

更新已被渲染的元素

React元素是不可变对象,一旦创建,将不再能够修改,包括其属性和子元素。更新UI的方法就是创建一个新的元素并用ReactDom.render()再次渲染他。如下面的例子:

//创建一个tick方法,用于执行重复方法
function tick() {
  //创建一个JSX对象
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

测试代码

上面代码中创建了一个tick方法,并使用setInterval让这个方法每1秒执行一次。tick中创建了一个用于显示时间的JSX对象,然后将其渲染到#root节点中。运行代码可以看到例子实现了一个时钟功能,每秒都会调用ReactDom.render动态修改时钟的数字。

需要强调的是:重复使用ReactDom.render方法来多次渲染Dom并不是React推崇的方法。后续的内容中会介绍更合理的方法。

React只执行必要的更新

ReactDom会将当前的元素与之前的元素进行比对,并且只会更新被改动部分的Dom以避免全局渲染和多次重复渲染。我们可以通过浏览器工具来验证最后一个例子——我们使用render方法创建了整个Dom结构,但是仅仅只有表示时间的文字部分发生了变动。

组件与属性

组件是React的重要概念,组件能让我们将整个页面的UI分解成独立、可复用、可继续分割的对象。从概念上来说,组件很像JavaScript的一个方法,他可以接受任意的参数输入(React中将这些参数称呼为属性——Props)并返回一个用于UI展示的React元素。

使用函数或类声明组件

在React中既可以使用function来声明一个组件,也可以使用ES6规范的class关键字来声明一个组件。下面的例子是使用function创建一个组件:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

例子中使用function声明了一个名为Welcome的组件,他只能接收一个参数用于描述组件的元素。在React中,我们将通过function创建的组件命名为“functional”,因为从字面上看它实际上就是一个JavaScript的函数。

下面的例子是使用ES6 class方式声明一个组件:

//ES6
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {
   this.props.name}</h1>;
  }
}

//JavaScript 非官方内容
var Welcome = React.createClass({
   render: function () {
     return <h1>Hello, {
   this.props.name}</h1>;
   }
});
//

上面两种创建组件的方式,从React的角度来说是一样的。

与使用方法创建组件相比,使用ES6 class的方式创建组件有更多特性,后续篇幅会说明。

渲染一个组件

为了便于说明,我们先用

标签创建一个最简单的组件:
const element = <div />;

此时,element即可认为是一个组件,组件中只有一个div元素。根据这个定义,我们可以使用用户自定义的组件,比如使用上面的Welcome:

const element = <Welcome name="Sara" />;

当React发现element中有用户自定义的组件,它会使用JSX语法解析element并将标签上的属性转换成一个JSX对象,这个对象被称为“props”。

例如下面这个例子,我们经使用组件在屏幕上输出"Hello, Sara":

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

测试代码

看看发生了什么:

  1. 使用ReactDOM.render()方法渲染。
  2. React调用Welcome方法,并传递了一个参数:{name: 'Sara'}。
  3. 在Welcome组件中合并了参数,并返回一个

    Hello, Sara

    。
  4. ReactDom将

    Hello, Sara

    更新到浏览器的Dom树中。

需要注意的是,使用React组件时一定要将组件名称首字母大写。例如在html标签中

是一个标准的Dom,但是并不是一个标准的html标签,而是一个React组件。React通过判断组件名称的首字母加以区分。

组件组合

一个组件能够被其他的组件引用,就像使用普通的html标签一样。我们可以把组件抽象成各种抽象功能在任何地方使用,例如一个按钮、一个弹出框、一个表单。下面的例子展示了React组件的组合使用:

function Welcome(props) {
    //创建Welcome组件
  return <h1>Hello, {props.name}</h1>;
}

function App() {
    //创建App组件
  return (
    
"Sara" /> //使用Welcome组件 "Cahal" /> "Edite" />
); } ReactDOM.render( //向Dom渲染App组件 , document.getElementById('root') );

测试代码    

在例子中,首先创建了一个Welcome组件,然后在App组件中重复使用它,最后向浏览器渲染App。App组件中整合使用了Welcome组件。基于组件可以层层封装,建议在使用React开始新项目时先从封装一些小的组件开始,比如按钮、弹出框等,这会对后面开发高层次的业务逻辑时有巨大的帮助。

一个组件只能返回一个根元素,不能同时包含2个根元素。因此上面的例子中App组件中增加了一个

元素将Welcome组件包裹起来。

抽象提取组件

不必担心组件的分割粒度太小,开发组件时我们最好是通过多个层次的组件组合实现一个更高层次的组件。

看下面这个例子——封装一个Comment组件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name} 
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

尝试代码

Comment组件很难维护,因为它内部的代码都前台和交织在一起,也无法实现代码复用。现在我们稍微修改组件中的Avator,将其提取成一个组件:

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avator组件不需知道他是在哪被使用,它只关心输入的参数,并使用参数生成一个Dom结构。现在可以像下面这样声明一个Comment组件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />//使用Avatar组件
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

我们再将UserInfo也提取成一个组件:

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} /> //使用Avatar组件
        {props.user.name}
      </div>
    </div>
  );
}

此时的Comment:

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} /> //使用UserInfo组件
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

测试代码

属性(props)只读

无论是使用函数(function)还是类(class)声明组件,它都不能通过修改props参数来改变值。例如下面这个sum方法:

function sum(a, b) {
  return a + b;
}

var param1 = 1,
    param2 = 2,
    result = sum(1, 2);

在第一次计算得到结果之后,无论怎么修改param1、param2的值,result都不会改变。

React相当的灵活自由,但是它有一条必须遵守的规则:

所有的React组件必须像上面的sum方法这样保证传入的属性(props)参数只读。

相关文章
|
算法 关系型数据库 MySQL
TiDB保证数据一致性的策略与优势
【2月更文挑战第28天】TiDB作为一款分布式数据库,通过其独特的策略和优势,确保在分布式环境下数据的一致性。本章将详细探讨TiDB保证数据一致性的核心策略,包括其采用的分布式一致性协议、数据复制机制以及容错处理等方面,并阐述这些策略所带来的优势。通过理解TiDB的数据一致性保证机制,读者将能更深入地认识其作为分布式数据库的价值。
|
Docker 容器
docker镜像的备份恢复迁移
docker镜像的备份恢复迁移
605 0
Kam
|
druid
druid数据连接池异常 connection holder is null 解决方案
druid数据连接池异常 connection holder is null 解决方案
Kam
2992 0
|
7月前
|
网络协议 前端开发 网络安全
B/S端和C/S端两种架构的应用详解,架构对于网络安全以及系统本身的详解-优雅草卓伊凡
本文详解B/S(浏览器/服务器)与C/S(客户端/服务器)两大主流架构:B/S依托HTTP、跨平台易维护,主导Web应用;C/S基于TCP、功能强定制高,适用于专业软件与游戏。对比历史演进、技术特点及适用场景,助你理解架构选型逻辑。(239字)
1046 12
|
8月前
|
人工智能 弹性计算 安全
2026年阿里云OpenClaw一键快速部署教程,轻松搭建专属AI助理!
2026年,打造专属AI数字员工超简单:仅需一台阿里云服务器,几分钟用OpenClaw一键部署,接入百炼大模型,即可实现文档编写、资料检索、脚本运行、报表整理等智能办公能力。本地优先、安全可控、7×24在线。
999 5
|
5月前
|
自然语言处理 搜索推荐 数据挖掘
2026年电商行业有哪些agent应用?从客服、营销到数据决策的实战指南
本文详解电商智能体(Agent)实战应用:瓴羊Quick Service实现客服从应答到经营跃迁;Quick Audience推动营销从圈人到共情进化;Quick BI“智能小Q”助力数据决策从看报表到问答案革命。三者协同构建闭环智能体系,为电商企业提供可复用的分阶段落地指南。(239字)
|
9月前
|
SQL 存储 缓存
PL/pgSQL 入门教程(六):从避坑到吃透,聊聊事务、错误处理和底层那些事儿
本文深度解析PL/pgSQL开发避坑指南:详解RAISE多级错误处理与USING增强提示、EXCEPTION事务恢复机制、变量替换限制与计划缓存陷阱,并分享美元符引号、CREATE OR REPLACE调试、extra_warnings预警等实战技巧,助你写出健壮高效存储过程。
|
存储 SQL 人工智能
DeepSeek开源周第五弹之二!Smallpond:构建于3FS之上的轻量级数据处理框架,高效处理PB级数据
Smallpond 是 DeepSeek 开源的轻量级数据处理框架,基于 DuckDB 和 3FS 构建,支持 PB 级数据处理,提供高性能的数据加载、查询和转换功能,适合大规模数据预处理和实时分析。
1370 1
DeepSeek开源周第五弹之二!Smallpond:构建于3FS之上的轻量级数据处理框架,高效处理PB级数据
|
数据采集 Java PHP
使用Python+requests简单实现模拟登录以及抓取接口数据
本文通过Python的requests库演示了如何实现模拟登录和抓取接口数据的过程,包括设置请求头、发送POST请求进行登录以及使用登录后的会话进行GET请求获取数据。
1401 1
|
存储 XML JSON
解锁Winform应用全球化:高效实现多语言处理的实战案例与技巧,让你的软件跨越语言障碍
【8月更文挑战第3天】随着全球化的推进,软件需支持多语言以满足国际用户需求。在Winform中,通过精心设计和工具辅助可简化多语言实现。本文以一个支持中、英、日、韩四语的应用为例,介绍如何使用JSON文件存储语言资源,并借助自定义类库动态加载和切换语言。通过准备不同语言的JSON文件、编写`LanguageHelper`类管理和更新UI文本,以及在配置文件中保存用户语言偏好等步骤,展示了实现多语言支持的有效途径。此方案不仅结构清晰,而且易于维护和扩展。
800 2