一. 认识jsx
语法
1. 认识jsx
// 1. 定义根组件
const el = <div>hello world</div>
// 2. 渲染组件
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(el)
- 这段
el
变量的声明右侧赋值的标签语法是什么呢?- 它不是一段字符串(因为没有使用引号包裹)
- 它看起来是一段
HTML
元素,但是我们能在js
中直接给一个变量赋值html
吗? - 其实是不可以的,如果我们将
type="text/babel"
去除掉,那么就会出现语法错误 - 它到底是什么呢?其实它是一段
jsx
的语法
jsx
是什么?jsx
是一种**js
的语法扩展**(eXtension
),也在很多地方称之为JavaScript XML
,因为看起就是一段XML
语法- 它用于描述我们的
UI
界面,并且其完成可以和js
融合在一起使用 - 它不同于
Vue
中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind
)
2. 为什么React选择了jsx
React
认为渲染逻辑本质上与其他UI
逻辑存在内在耦合- 比如
UI
需要绑定事件(button
、a
原生等等) - 比如
UI
中需要展示数据状态 - 比如在某些状态发生改变时,又需要改变
UI
- 比如
- 他们之间是密不可分,所以
React
没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件Component
- 当然,后面我们还是会继续学习更多组件相关的东西
- 在这里,我们只需要知道,
jsx
其实是嵌入到js
中的一种结构语法 jsx
的书写规范:jsx
的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div
元素(或者使用后面我们学习的Fragment
)- 为了方便阅读,我们通常在
jsx
的外层包裹一个小括号()
,这样可以方便阅读,并且jsx
可以进行换行书写 jsx
中的标签可以是单标签,也可以是双标签- 注意:如果是单标签,必须以
/>
结尾
- 注意:如果是单标签,必须以
二. jsx
的基本使用
1. jsx的使用
jsx
中的注释jsx
文件中,直接使用ctrl + /
,自动会生成对应的注释
jsx<div> {/* 这是一个jsx的注释 */} <h2>当前计数:{ count }</h2> <button onClick={ this.increment }>+1</button> <button onClick={ this.decrement }>-1</button> </div>
jsx
嵌入变量作为子元素- 情况一:当变量是
Number、String、Array
类型时,可以直接显示 - 情况二:当变量是
null、undefined、Boolean
类型时,内容为空- 如果希望显示
null、undefined、Boolean
,那么需要转成字符串才能正确显示 - 转换的方式有很多,比如
toString
方法、和空字符串拼接,String()
等方式
- 如果希望显示
- 情况三:
Object
对象类型不能作为子元素进行显示(Objects not valid as a React child
)
- 情况一:当变量是
jsx
嵌入表达式- 运算表达式
- 三元运算符
- 执行一个函数
jsxrender() { // 1.插入标识符 const { message, names, counter } = this.state const { aaa, bbb, ccc } = this.state const { friend } = this.state // 2.对内容进行运算后显示(插入表示) const { firstName, lastName } = this.state const fullName = firstName + " " + lastName const { age } = this.state const ageText = age >= 18 ? "成年人": "未成年人" const liEls = this.state.movies.map(movie => <li>{movie}</li>) // 3.返回jsx的内容 return ( <div> {/* 1.Number/String/Array直接显示出来 */} <h2>{counter}</h2> <h2>{message}</h2> <h2>{names}</h2> {/* 2.undefined/null/Boolean */} <h2>{String(aaa)}</h2> <h2>{bbb + ""}</h2> <h2>{ccc.toString()}</h2> {/* 3.Object类型不能作为子元素进行显示*/} <h2>{friend.name}</h2> <h2>{Object.keys(friend)[0]}</h2> {/* 4.可以插入对应的表达式*/} <h2>{10 + 20}</h2> <h2>{firstName + " " + lastName}</h2> <h2>{fullName}</h2> {/* 5.可以插入三元运算符*/} <h2>{ageText}</h2> <h2>{age >= 18 ? "成年人": "未成年人"}</h2> {/* 6.可以调用方法获取结果*/} <ul>{liEls}</ul> <ul>{this.state.movies.map(movie => <li>{movie}</li>)}</ul> <ul>{this.getMovieEls()}</ul> </div> ) } getMovieEls() { const liEls = this.state.movies.map(movie => <li>{movie}</li>) return liEls }
2.jsx的使用
jsx
绑定属性- 比如元素都会有
title
属性 - 比如
img
元素会有src
属性 - 比如
a
元素会有href
属性 - 比如元素可能需要绑定
class
- 比如原生使用内联样式
style
jsxclass APP extends React.Component { constructor() { super() this.state = { count: 0, title: '动态绑定的标题', imgURL: "https://ts1.cn.mm.bing.net/th/id/R-C.95bc299c3f1f0e69b9eb1d0772b14a98", isActive: true, classList: ['aaa', 'bbb', 'ccc'], c_red: { color: 'red' } } } render() { const { title, imgURL, isActive, classList, c_red } = this.state const className = `aaa bbb ${isActive ? 'active' : ''}` return ( <div> <h2 title="固定的标题">固定的标题</h2> <h2 title={ title }>动态绑定的标题</h2> {/* <img src={ imgURL } alt="" /> */} <h2 class="aaa">这种在jsx写class, babel转化之后, 虽然也可以正常显示, 但是react-dom会抛出一个j, 不介意使用</h2> <h2 className={ className }>推荐使用className</h2> <h2 className={ classList }>大括号中的标识符会被调用其toString方法</h2> <h2 className={ `${curIndex === index ? 'active' : ''} item` } ></h2> <h2 style={ c_red }>h2元素</h2> </div> ) } }
- 比如元素都会有
三. jsx
的事件绑定
1.React事件绑定
- 如果原生
DOM
原生有一个监听事件,我们可以如何操作呢?- 方式一:获取
DOM
原生,添加监听事件 - 方式二:在
HTML
原生中,直接绑定onclick
- 方式一:获取
- 在
React
中是如何操作呢?我们来实现一下React
中的事件监听,这里主要有两点不同React
事件的命名采用小驼峰式(camelCase
),而不是纯小写(原生事件纯小写)- 我们需要通过
{}
传入一个事件处理函数,这个函数会在事件发生时被执行
2.this的绑定问题
在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到
this
- 如果我们这里直接打印
this
,也会发现它是一个undefined
- 如果我们这里直接打印
为什么是
undefined
呢?- 原因是
btnClick
函数并不是我们主动调用的,而是当button
发生改变时,React
内部调用了btnClick
函数 - 而它内部调用时,并不知道要如何绑定正确的
this
- 原因是
如何解决
this
的问题呢?- 方案一:
bind
给btnClick
显示绑定this
- 方案二:使用
ES6 class fields
语法 - 方案三:事件监听时传入箭头函数
jsxclass App extends React.Component { constructor() { super() this.changeText = this.changeText.bind(this) // 写法二 } changeText() { console.log(this) } // 写法三: class 的 field,ES13中,新增了定义class类中成员字段field的其他方式 changeText = () => { console.log(this) } render() { return ( <div> {/* 写法一 */} <button onClick={ this.changeText.bind(this) }>改变文本</button> {/* 写法四 */} <button onClick={ () => this.changeText() }>改变文本</button> </div> ) } }
- 方案一:
3.事件参数传递
在执行事件函数时,有可能我们需要获取一些参数信息:比如
event
对象、其他参数情况一:获取
event
对象- 很多时候我们需要拿到
event
对象来做一些事情(比如阻止默认行为) - 那么默认情况下,
event
对象有被直接传入,函数就可以获取到event
对象
- 很多时候我们需要拿到
情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
jsxrender() { return ( <div> <button onClick={e => this.btnClick(e, 'later', 18)}></button> </div> ) }
四. jsx
的条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
- 在
vue
中,我们会通过指令来控制:比如v-if、v-show
- 在
React
中,所有的条件判断都和普通的js
代码一致
- 在
常见的条件渲染的方式有哪些呢?
方式一:条件判断语句
- 适合逻辑较多的情况
方式二:三元运算符
- 适合逻辑比较简单
方式三:与运算符
&&
- 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染
v-show
的效果- 主要是控制
display
属性是否为none
jsxclass App extends React.Component { constructor() { super() this.state = { message: "Hello World", isReady: false, friend: undefined, isShow: true } } changeShow() { this.setState({ isShow: !this.state.isShow }) } render() { const { isReady, friend, isShow } = this.state // 1.条件判断方式一: 使用if进行条件判断 let showElement = null if (isReady) { showElement = <h2>准备开始比赛吧</h2> } else { showElement = <h1>请提前做好准备!</h1> } return ( <div> {/* 1.方式一: 根据条件给变量赋值不同的内容 */} <div>{showElement}</div> {/* 2.方式二: 三元运算符 */} <div>{ isReady ? <button>开始战斗!</button>: <h3>赶紧准备</h3> }</div> {/* 3.方式三: &&逻辑与运算 */} {/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */} <div>{ friend && <div>{friend.name + " " + friend.desc}</div> }</div> {/* v-show的效果 */} <button onClick={() => this.changeShow()}>切换</button> { isShow && <h2>{message}</h2> } <h2 style={{display: isShow ? 'block': 'none'}}>哈哈哈哈</h2> </div> ) } }
- 主要是控制
五. jsx
的列表渲染
1.React列表渲染
真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储:
- 比如歌曲、歌手、排行榜列表的数据
- 比如商品、购物车、评论列表的数据
- 比如好友消息、动态、联系人列表的数据
在
React
中并没有像Vue
模块语法中的v-for
指令,而且需要我们通过js
代码的方式组织数据,转成jsx
:- 很多从
Vue
转型到React
的同学非常不习惯,认为Vue
的方式更加的简洁明了 - 但是
React
中的jsx
正是因为和js
无缝的衔接,让它可以更加的灵活 - 另外我经常会提到
React
是真正可以提高我们编写代码能力的一种方式
- 很多从
如何展示列表呢?
- 在
React
中,展示列表最多的方式就是使用数组的map
高阶函数
- 在
很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
- 比如过滤掉一些内容:
filter
函数 - 比如截取数组中的一部分内容:
slice
函数
jsxclass App extends React.Component { constructor() { super() this.state = { students: [ { id: 111, name: "why", score: 199 }, { id: 112, name: "kobe", score: 98 }, { id: 113, name: "james", score: 199 }, { id: 114, name: "curry", score: 188 }, ] } } render() { const { students } = this.state // 分数大于100的学生进行展示 const filterStudents = students.filter(item => { return item.score > 100 }) // 分数大于100, 只展示两个人的信息 // slice(start, end): [start, end) const sliceStudents = filterStudents.slice(0, 2) return ( <div> <h2>学生列表数据</h2> <div className="list"> { students.filter(item => item.score > 100).slice(0, 2).map(item => { return ( <div className="item" key={item.id}> <h2>学号: {item.id}</h2> <h3>姓名: {item.name}</h3> <h1>分数: {item.score}</h1> </div> ) }) } </div> </div> ) } }
- 比如过滤掉一些内容:
2.列表中的key
我们会发现在前面的代码中只要展示列表都会报一个警告:
这个警告是告诉我们需要在列表展示的
jsx
中添加一个key
key
主要的作用是为了提高diff
算法时的效率(diff算法本质也是在减少dom更新,减少回流重绘等带来的性能消耗)
六. jsx
的原理和本质
1.jsx的本质
- 实际上,
jsx
仅仅只是React.createElement(component, props, ...children)
函数的语法糖- 所有的
jsx
最终都会被转换成React.createElement
的函数调用 ,最终也就是创建ReactElement
- 所有的
createElement
需要传递三个参数:- 参数一:
type
- 当前
ReactElement
的类型 - 如果是标签元素,那么就使用字符串表示 “
div
” - 如果是组件元素,那么就直接使用组件的名称
- 当前
- 参数二:
config
- 所有jsx中的属性都在
config
中以对象的属性和值的形式存储 - 比如传入
className
作为元素的class
- 所有jsx中的属性都在
- 参数三:
children
- 存放在标签中的内容,以
children
数组的方式进行存储 - 当然,如果是多个元素呢?
React
内部有对它们进行处理,处理的源码在下方
- 存放在标签中的内容,以

2.Babel官网查看
我们知道默认
jsx
是通过babel
帮我们进行语法转换的,所以我们之前写的jsx
代码都需要依赖babel
可以在
babel
的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react
对于
render
方法返回的jsx
代码,通过babel
会转化为React.createElement函数调用的代码
,函数调用返回的js / React.creatElement
对象,其内部有形成一个嵌套结构,其实就是虚拟DOM
,最终React
会通过ReactDOM.render()调用
将其转化成真实dom
进行渲染jsReactNative => render => 原生控件 SSR => render => 字符
Vue
的template
是Vue
内部自己解析的,而不是依赖于React
,如果在Vue
中使用jsx
,也是依赖babel
进行解析的
可以将
babel
转化后的React
代码,直接放在render
函数中,直接被React
解析的,效果是一样的/*__PURE__*/
是代表纯函数的意思。当没有引用时,会被tree shaking
自动删除掉的

3.直接编写jsx代码
- 我们自己来编写
React.createElement
代码:我们就没有通过
jsx
来书写了,界面依然是可以正常的渲染另外,在这样的情况下,你还需要
babel
相关的内容吗?不需要了所以,
type="text/babel"
可以被我们删除掉了所以,
<script src="../react/babel.min.js"></script>
可以被我们删除掉了
4.虚拟DOM
的创建过程
我们通过
React.createElement
最终创建出来一个ReactElement
对象:这个
ReactElement
对象是什么作用呢?React
为什么要创建它呢?- 原因是
React
利用ReactElement
对象组成了一个js
的对象树 js
的对象树就是虚拟DOM(Virtual DOM)
- 原因是
如何查看
ReactElement
的树结构呢?我们可以将之前的
jsx
返回结果进行打印
而
ReactElement
最终形成的树结构就是Virtual DOM
5.jsx
– 虚拟DOM
– 真实DOM

6.声明式编程
- 虚拟
DOM
帮助我们从命令式编程转到了声明式编程的模式 React
官方的说法:Virtual DOM
是一种编程理念- 在这个理念中,
UI
以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的js
对象 - 我们可以通过
ReactDOM.render
让 虚拟DOM
和 真实DOM
同步起来,这个过程中叫做协调(Reconciliation
)- 就是
jsx
代码渲染到真实dom
的过程
- 就是
- 在这个理念中,
- 这种编程的方式赋予了
React
声明式的API
:- 你只需要告诉
React
希望让UI
是什么状态 React
来确保DOM
和这些状态是匹配的- 你不需要直接进行
DOM
操作,就可以从手动更改DOM
、属性操作、事件处理中解放出来
- 你只需要告诉
- 关于虚拟
DOM
的一些其他内容,在后续的学习中还会再次讲到
7. 阶段性案例练习
1.在界面上以表格的形式,显示一些书籍的数据
2.在底部显示书籍的总价格
3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-)
4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~)
代码如下:
jsx<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> :root { padding: 10px; } td, th { padding: 6px 10px; border: 1px solid #ccc } table { border-collapse: collapse; text-align: center; } thead { background-color: #eee; } tfoot th { border: none; font-size: 20px; font-weight: bold; } </style> </head> <body> <div id="root"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> class APP extends React.Component { constructor() { super() this.state = { titles: [ '序号', '书籍名称', '出版日期', '价格', '购买数量', '操作' ], books: [ { id: 1, name: '《算法导论》', publicDate: '2006-9', price: 85, count: 1 }, { id: 2, name: '《UNIX编程艺术》', publicDate: '2006-2', price: 59, count: 1 }, { id: 3, name: '《编程珠玑》', publicDate: '2008-10', price: 39, count: 1 }, { id: 4, name: '《代码大全》', publicDate: '2006-3', price: 128, count: 1 } ], totalPrice: 0 } } changeCount(index, num = 1) { const newBooks = [...this.state.books] newBooks[index].count += num this.setState({ books: newBooks }) } getTotalPrice() { return this.state.books.reduce((preValue, curValue) => { return preValue + curValue.price * curValue.count }, 0) } removeItem(index) { const newBooks = [...this.state.books] newBooks.splice(index, 1) this.setState({ books: newBooks }) } renderBooks() { const { titles, books } = this.state return ( <div> <table> <thead> <tr> { titles.map((item) => <th key={ item }>{ item }</th>) } </tr> </thead> <tbody> { books.map((book, index) => ( <tr key={ book.name }> { Object.keys(book).map(key => ( <th key={ key }> { key == 'count' ? <button disabled={ book['count'] == 0 } onClick={ () => this.changeCount(index, -1) }>-</button> : '' } { key == 'id' ? index + 1 : book[key] } { key == 'count' ? <button onClick={ () => this.changeCount(index) }>+</button> : '' } </th> )) } <th> <button onClick={ () => this.removeItem(index) }>删除</button> </th> </tr> )) } </tbody> <tfoot> <tr> <th>总价格:¥{ this.getTotalPrice() }</th> </tr> </tfoot> </table> </div> ) } renderBookEmpty() { return <div>购物车空空如也~</div> } render() { return ( this.state.books.length ? this.renderBooks() : this.renderBookEmpty() ) } } const rootEl = document.querySelector('#root') const root = ReactDOM.createRoot(rootEl) root.render(<APP/>) </script> </body> </html>
8. React必须在作用域内
由于
JSX
会编译为React.createElement
调用形式,所以React
库也必须包含在JSX
代码作用域内例如,在如下代码中,虽然
React
和CustomButton
并没有被直接使用,但还是需要导入:jsximport React from 'react' import CustomButton from './CustomButton' function WarningButton() { // return React.createElement(CustomButton, {color: 'red'}, null); return <CustomButton color="red" /> }
如果你不使用
JavaScript
打包工具而是直接通过<script>
标签加载React
,则必须将React
挂载到全局变量中