一. 认识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中添加一个keykey主要的作用是为了提高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挂载到全局变量中
