文章目录
- 认识JSX语法
- JSX是什么
- 为什么Rect选择了JSX
- JSX书写规范
- JSX注释编写
- JSX的基本使用
- JSX的事件绑定
- this绑定问题
- 参数传递问题
- JSX的条件渲染
- 常见的条件渲染方式
- JSX的列表渲染
- JSX的原理和本质
- JSX的本质
- 虚拟DOM的创建过程
- 案例练习
认识JSX语法
// 1. 定义根组件
const element = <div>Hello World</div>
// 2. 渲染根组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(element)
上述代码中element变量的声明右侧赋值的标签语法其实就是一段JSX的语法。
- 它不是一段字符串(因为没有使用引号包裹)
- 它看起来是一段HTML元素,但是我们能在js中直接给一个变量赋值HTML吗?其实是不可以的,如果我们将
type = "text/babel"
去除掉,那么就会出现语法错误。
JSX是什么
JSX是一种JavaScript的语法扩展,也在很多地方称之为JavaScript XML,因为看起来就是一段XML语法,它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用,它不同于Vue中的模块语法,不需要专门学习模块语法中的一些指令(比如v-if、v-for、v-bind等)
为什么Rect选择了JSX
React认为渲染逻辑本质上与其他UI逻辑存在内在耦合。比如UI需要绑定事件(button、a原生等等)、比如UI中需要展示数据的状态、比如在某些状态发生改变时,又需要改变UI,它们之间是密不可分的,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件。
JSX书写规范
- JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者Fragment)
- 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
- JSX中的标签可以是单标签,也可以是双标签(如果是单标签,必须以/>结尾)
return (
<div>
<div>
<h2>{message}</h2>
</div>
<div>哈哈哈</div>
</div>
)
JSX注释编写
return (
<div>
{/* JSX的注释写法*/}
<h2>{message}</h2>
</div>
)
JSX的基本使用
-
JSX嵌入变量作为子元素
- 情况一:当变量是Number、String、Array类型时,可以直接显示
- 情况二:当变量是null、undefined、Boolean类型时,内容为空
- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串
- 转换的方式有很多,比如toString方法、和空字符串拼接、String等方式
- 情况三:Object对象类型不能作为子元素
-
JSX嵌入表达式
- 运算表达式
- 三元运算符
- 执行一个函数
-
JSX绑定属性
- 比如元素都会有title属性
- 比如img元素会有src属性
- 比如a元素会有href属性
- 比如元素可能需要绑定class
- 比如元素使用内联样式style
JSX的事件绑定
如果原生DOM有一个监听事件,我们可以如何操作?
- 方式一:获取DOM元素,添加监听事件
- 方式二:在HTML原生中,直接绑定onclick
在React中是如何操作呢?
- React事件的命名采用小驼峰(cameClass),而不是纯小写
- 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
<body>
<div id="root"></div>
<script type="text/babel">
// 类组件
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
message: "Hello World"
}
}
// 组件方法
btnClick() {
// 内部完成了两件事:
// 1. 将state中的message值修改掉
// 2. 自动重新执行render函数
this.setState({
message: "Hello React"
})
}
// 渲染内容
render() {
// 简单数据
// return <h2>Hello World</h2>
// 复杂数据
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick.bind(this)}>修改文本</button>
</div>
)
}
// 为什么这里要bind一下? 因为类中的方法中的this默认指向undefined, 所以要将render函数中的this给方法
}
// 将组件渲染到界面上
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
这种数据的数据是定义在当前对象的state中的,我们可以通过构造函数中的this.state={定义的数据},然后当我们数据发生变化时,我们可以调用this.setState来更新数据,并且通知React进行update操作,在进行update操作时,会重新调用render函数,并且使用最新的数据来渲染界面。
this绑定问题
上述代码还存在this绑定问题
在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this。
在类中直接定义一个函数,并且将这个函数绑定到元素的onClick事件上,当前这个函数的this指向是谁呢?
默认情况下是undefined,因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说button对象),但是因为React并不是直接渲染成真实DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象,那么在这里发生监听的时候,react在执行函数时并没有绑定this,默认情况下就是一个undefined。
我们在绑定的函数中,可能想要使用当前对象,比如执行this.setState函数,就必须拿到当前对象的this,我们就需要在传入函数时,给这个函数直接绑定this:
- 方案一:bind给btnClick显式绑定this
// 写法一
<button onClick={this.btnClick.bind(this)}>修改文本</button>
// 写法二
constructor() {
super()
this.state = {
message: "Hello World"
}
this.btnClick = this.btnClick.bind(this)
}
- 方案二:使用ES6 class fields 语法
- 方案三:事件监听时传入箭头函数(推荐)
<body>
<div id="root"></div>
<script type="text/babel">
/*
this的四种绑定规则:
1. 默认绑定 独立执行
2. 隐式绑定 作为对象中的方法执行
3. 显式绑定 call apply bind
4. new绑定
*/
class App extends React.Component {
constructor() {
super()
this.state = {
counter: 100
}
}
btn1Click() {
console.log("btn1Click")
this.setState({ counter: this.state.counter + 1 })
}
// 所有实例都会有btn2Click这个字段,将一个箭头函数赋值给btn2Click,而箭头函数没有this,就会去上层作用域中查找,找到的是当前类的作用域的this,而this正好指向当前实例
btn2Click = () => {
console.log("btn2Click")
this.setState({ counter: this.state.counter + 1 })
}
btn3Click = () => {
console.log("btn3Click")
this.setState({ counter: this.state.counter + 1 })
}
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
{/*1. this绑定方式一:bind绑定*/}
<button onClick={this.btn1Click.bind(this)}>按钮1</button>
{/*2. this绑定方式二:ES6 class fields*/}
<button onClick={this.btn2Click}>按钮2</button>
{/*3. this绑定方式三:直接传入一个箭头函数(重要)*/}
<button onClick={() => this.btn3Click()}>按钮3</button>
{/*怎么做的?形成一个隐式绑定,this是当前实例,btn3Click作为当前实例的方法被调用*/}
</div>
)
}
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
参数传递问题
- 情况一:获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 那么默认情况下,event对象有被直接传入,函数就可以获取到event对象
- 情况二:获取更多参数
- 有更多参数时,最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
<body>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
counter: 100
}
}
btn1Click(event) {
console.log("btn1Click", event)
}
btn2Click(event, name, age) {
console.log("btn2Click", event, name, age)
}
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
{/*1. event参数的传递 */}
<button onClick={this.btn1Click.bind(this)}>按钮1</button>
<button onClick={(event) => this.btn1Click(event)}>按钮1</button>
{/*2. 额外的参数传递*/}
<button onClick={this.btn2Click.bind(this,)}>按钮2</button>
<button onClick={(event) => this.btn1Click(event, "zy", 18)}>按钮2</button>
</div>
)
}
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
JSX的条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
- 在vue中,我们会通过指令来控制:比如v-if、v-show
- 在React中,所有的条件判断都和普通的JavaScript代码一致
常见的条件渲染方式
- 方式一:条件判断语句,适合逻辑较多的情况
- 方式二:三元运算符,适合逻辑比较简单
- 方式三:与运算符&&,适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染
<body>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
isReady: true
}
}
render() {
const { isReady } = this.state
let showElement = null
if (isReady) {
showElement = <h2>准备开始比赛吧</h2>
} else {
showElement = <h2>请提前做好准备!</h2>
}
return (
<div>
{/*1. 方式一:根据条件给变量赋值不同的内容*/}
<div>{showElement}</div>
{/*2. 方式二:三元运算符*/}
<div>{isReady ? <button>开始战斗!</button> : <h3>准备</h3>}</div>
{/*3. 方式三:&&逻辑与运算*/}
{/*场景:当某一个值,有可能为undefined时,使用&&进行条件判断*/}
<div>{info && <div>{info.name + " " + info.desc}</div>}</div>
</div >
)
}
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
JSX的列表渲染
真实开发中我们会从服务器请求到大量数据,数据会以列表的形式存储:
- 比如歌手、歌曲、排行榜列表的数据
- 比如商品、购物车、评论列表的数据
- 比如好友消息、动态、联系人列表数据
在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX,那到底如何展示列表呢?在React中,展示列表最多的方式就是使用数组的map高阶函数。很多时候我们在展示一个数据之前,需要先对它进行一些处理:比如过滤掉一些内容:filter函数;比如截取数组中的一部分内容:slice函数。
<body>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
students: [
{ id: 1, name: "zy", score: 99 },
{ id: 2, name: "lgc", score: 88 },
{ id: 3, name: "mxy", score: 100 },
{ id: 4, name: "jack", score: 66 },
{ id: 5, name: "kobe", score: 92 },
]
}
}
render() {
const { students } = this.state
return (
<div>
<h2>学生列表数据</h2>
<div className="list">
{
// 选出成绩大于90分的人
students.filter(item => item.score > 90).map(item => {
return (
<div className="item" key={item.id}>
<h2>学号:{item.id}</h2>
<h2>姓名:{item.name}</h2>
<h2>分数:{item.score}</h2>
</div>
)
})
}
</div>
</div>
)
}
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
JSX的原理和本质
JSX的本质
实际上,jsx仅仅只是
React.createElement(component, props, ...children)
函数的语法糖,所有的jsx最终都会被转换成React.createElement
的函数调用
React.createElement
需要传递三个参数:
- 参数一:type
- 当前ReactElement的类型
- 如果是标签元素,那么就使用字符串表示“div”
- 如果是组件元素,那么就直接使用组件的名称
- 参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储
- 比如传入className作为元素的class
- 参数三:children
- 存放在标签中的内容,以children数组的方式进行存储
- 如果是多个元素,React内部有对它们进行处理
虚拟DOM的创建过程
我们通过React.createElement最终创建出来一个ReactElement对象,这个ReactElement对象是什么作用呢?React为什么要创建它呢?这是因为React利用ReactElement对象组成了一个JavaScript的对象树,JavaScript的对象树就是虚拟DOM。
如何查看ReactElement的树结构呢?我们可以将之前的就实现返回结果进行打印:
jsx-虚拟DOM-真实DOM
案例练习
要求:
- 在界面上以表格的形式,显示一些书籍的数据
- 在底部显示书籍的总价格
- 点击+或者-可以增加或减少书籍(如果为1,那么不能继续-)
- 点击移除按钮,可以将书籍移除(当所有书籍移除完毕时,显示:购物车为空~)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>购物车</title>
<!-- 添加依赖 -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- babel -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f2f2f2;
}
td,
th {
padding: 10px 16px;
border: 1px solid black;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="./data.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
books: books
}
}
format(price) {
return "¥" + Number(price).toFixed(2);
}
// 方法
increment(index) {
const newBooks = [...this.state.books]
newBooks[index].count += 1
this.setState({ books: newBooks })
}
decrement(index) {
const newBooks = [...this.state.books]
newBooks[index].count -= 1
this.setState({ books: newBooks })
}
removeItem(index) {
const newBooks = [...this.state.books]
newBooks.splice(index, 1)
this.setState({ books: newBooks })
}
render() {
const { books } = this.state
// 计算总价
let totalPrice = 0
for (let i = 0; i < books.length; i++) {
totalPrice += books[i].count * books[i].price
}
return (
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody >
{
books.map((item, index) => {
return (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.data}</td>
<td>{this.format(item.price)}</td>
<td>
<button disabled={item.count <= 1} onClick={() => this.decrement(index)}>-</button>
{item.count}
<button onClick={() => this.increment(index)}>+</button>
</td>
<td><button onClick={() => this.removeItem(index)}>移除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h1>总价格:{this.format(totalPrice)}</h1>
</div>
)
}
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
</html>
// data.js
const books = [
{
id: 1,
name: '《算法导论》',
data: '2006-9',
price: 85.0,
count: 1,
},
{
id: 2,
name: '《UNIX编程艺术》',
data: '2006-9',
price: 59.0,
count: 1,
},
{
id: 3,
name: '《编程珠玑》',
data: '2006-9',
price: 39.0,
count: 1,
},
{
id: 4,
name: '《代码大全》',
data: '2006-9',
price: 128.0,
count: 1,
},
];
container);
root.render();
// data.js
const books = [
{
id: 1,
name: '《算法导论》',
data: '2006-9',
price: 85.0,
count: 1,
},
{
id: 2,
name: '《UNIX编程艺术》',
data: '2006-9',
price: 59.0,
count: 1,
},
{
id: 3,
name: '《编程珠玑》',
data: '2006-9',
price: 39.0,
count: 1,
},
{
id: 4,
name: '《代码大全》',
data: '2006-9',
price: 128.0,
count: 1,
},
];