快来加入我们吧!
"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。
"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!
前言
这节我们将教你一个有趣的标签语法,他既不是字符串也不是 HTML
,他被称为 JSX
,是一个 JavaScript
的语法扩展,我们建议在 React
中配合使用 JSX
,JSX
可以很好地描述 UI
应该呈现出它应有交互的本质形式。
本文会向你介绍以下内容:
- 认识
JSX
的语法 - 在
JSX
中嵌入表达式 - 事件监听
- 条件渲染
- 列表渲染
JSX
原理解析- 案例练习
认识 JSX 的语法
JSX 是什么?
我们先来看一段代码:
const element = <h1>Hello, XHS-Rookies!</h1>
这段 element
变量的声明右侧赋值的标签语法是什么呢?
这个有趣的标签语法既不是字符串也不是 HTML
。
它被称为 JSX
,是一个 JavaScript
的语法扩展。我们建议在 React
中配合使用 JSX
,JSX
可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX
可能会使人联想到模版语言,但它具有 JavaScript
的全部功能。
注意: 对于JSX
来说,虽然是一种JavaScript
语法扩展,但是你能发现其无法直接使用在HTML
中,需要借助babel
的转换,转换后会自动帮我解析成想要的样式。
为什么使用 JSX?
React
认为渲染逻辑本质上与其他 UI
逻辑内在耦合,比如,在 UI
中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI
,以及需要在 UI
中展示准备好的数据。
React
并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。
React
不强制要求使用 JSX
,但是大多数人发现,在 JavaScript
代码中将 JSX
和 UI
放在一起时,会在视觉上有辅助作用。它还可以使 React
显示更多有用的错误和警告消息。
JSX 的书写规范:
JSX
的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div
原生;- 为了方便阅读,我们通常在
jsx
的外层包裹一个小括号(),这样可以方便阅读,并且jsx
可以进行换行书写; JSX
中的标签可以是单标签,也可以是双标签;
注意: 如果是单标签,必须以/>
结尾;
在 JSX 中嵌入表达式
如果我们 jsx
中的内容是动态的,我们可以通过表达式来获取:
书写规则:{表达式},大括号内可以是变量、字符串、数组、函数调用等任意 js
表达式;
JSX 中的注释
这是嵌入到 JavaScript
中的一种语法,所以在编写注释时,需要通过 JSX
的语法来编写:
<div>
{/* 我是一段注释 */}
<h2>Hello World</h2>
</div>
JSX 嵌入变量
情况一: 当变量是 Number
、String
、Array
类型时,可以直接显示
情况二: 当变量是 null
、undefined
、Boolean
类型时,内容为空;
- 如果想要显示
null
、undefined
、Boolean
,那么需要转成字符串;转换的方式有很多,比如toString
方法、和空字符串拼接,String
(变量)等方式;
情况三: 对象类型不能作为子元素(not valid as a React child
)
class App extends React.Component {
render() {
let data = {
name: 'xhs-rookies',
age: 18,
skills: ['JavaScript', 'React', 'Webpack'],
test1: null,
test2: undefined,
flag: false,
friend: {
name: 'xhs-zymxxxs',
age: 29,
},
}
return (
<div>
<div>
{/* 我是一段注释 */}
<h2>Hello React</h2>
</div>
<div>
{/* 1.可以直接显示 */}
<h2>{data.name}</h2>
<h2>{data.age}</h2>
<h2>{data.skills}</h2>
{/* 2.不显示 */}
<h2>{data.test1}</h2>
<h2>{data.test1 + ''}</h2>
<h2>{data.test2}</h2>
<h2>{data.test2 + ''}</h2>
<h2>{data.flag}</h2>
<h2>{data.flag + ''}</h2>
{/* 3.不显示 */}
<h2>123{data.friend}</h2>
</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
为什么 null、undefined、Boolean 在 JSX 中要显示为空内容呢? 原因是在开发中会进行很多的判断;
- 在判断结果为 false 时,不显示一个内容;
- 在判断结果为 true 时,显示一个内容;
JSX 嵌入表达式
在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式。
在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到<h1>
元素中。
function formatName(user) {
return user.firstName + ' ' + user.lastName
}
const user = {
firstName: 'xhs',
lastName: 'rookies',
}
const element = <h1>Hello, {formatName(user)}!</h1>
ReactDOM.render(element, document.getElementById('root'))
JSX 绑定属性
你可以通过使用引号,来将属性值指定为字符串字面量:
const element = <div className="active"></div>
也可以使用大括号,来在属性值中插入一个 JavaScript
表达式:
const element = <img src={user.avatarUrl}></img>
在属性中嵌入 JavaScript
表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。
事件监听
和原生绑定区别
如果原生 DOM
原生有一个监听事件,我们可以如何操作呢?
- 方式一:获取
DOM
原生,添加监听事件; - 方式二:在
HTML
原生中,直接绑定onclick
;
我们这里演练一下方式二:
btnClick()
这样写的原因是onclick
绑定的后面是跟上JavaScript
代码;
<button onclick="btnClick()">点我一下</button>
<script>
function btnClick() {
console.log('按钮发生了点击')
}
</script>
在 React
中是如何操作呢?
我们来实现一下 React
中的事件监听,这里主要有两点不同
React
事件的命名采用小驼峰式(camelCase
),而不是纯小写;- 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
class App extends React.Component {
render() {
return (
<div>
<button onClick={this.btnClick}>点我一下</button>
</div>
)
}
btnClick() {
console.log('React按钮点击了一下')
}
}
事件的 this 绑定
你必须谨慎对待 JSX
回调函数中的 this
,在 JavaScript
中,class
的方法默认不会绑定 this
。如果你忘记绑定 this.handleClick
并把它传入了 onClick
,当你调用这个函数的时候 this
的值为 undefined
。
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this)
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return <button onClick={() => this.handleClick()}>Click me</button>
}
}
如果你想了解 bind
和箭头函数对 this
使用的区别,可以选择查看bind 和箭头函数的区别
事件参数传递
在执行事件函数时,有可能我们需要获取一些参数信息:比如 event
对象、其他参数
情况一:获取 event
对象
- 很多时候我们需要拿到
event
对象来做一些事情(比如阻止默认行为) - 假如我们用不到
this
,那么直接传入函数就可以获取到event
对象;
class App extends React.Component {
btnClick(e) {
e.preventDefault()
console.log(e)
}
render() {
return (
<div>
<a href="https://xhs-rookies.com/" onClick={this.btnClick}>
点我一下
</a>
</div>
)
}
}
情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {
render() {
let data = {
names: ['衣服', '鞋子', '裤子'],
}
return (
<div>
<a href="https://xhs-rookies.com/" onClick={this.aClick}>
点我一下
</a>
{data.names.map((item, index) => {
return (
<a href="#" onClick={(e) => this.aClick(e, item, index)}>
这里是{item}
</a>
)
})}
</div>
)
}
aClick(e, item, index) {
e.preventDefault()
console.log(item, index)
console.log(e)
}
}
条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
在 React
中,所有的条件判断都和普通的 JavaScript
代码一致;
常见的条件渲染的方式有哪些呢?
条件判断语句
一种方式是当逻辑较多时,通过条件判断:
class App extends React.Component {
render() {
let data = {
isLogin: true,
}
let titleJsx = null
if (data.isLogin) {
titleJsx = <h2>欢迎回来~</h2>
} else {
titleJsx = <h2>请先登录~</h2>
}
return <div>{titleJsx}</div>
}
}
当然,我们也可以将其封装到一个独立的函数中:
class App extends React.Component {
this.data = {
isLogin: true
}
render() {
return (
<div>
{this.getTitleJsx()}
</div>
)
}
getTitleJsx() {
let titleJsx = null;
if (this.data.isLogin) {
titleJsx = <h2>欢迎回来~</h2>
} else {
titleJsx = <h2>请先登录~</h2>
}
return titleJsx;
}
}
三元运算符
另外一种实现条件渲染的方法就是三元运算符:condition ? true : false;
三元运算符适用于没有太多逻辑的代码:只是根据不同的条件直接返回不同的结果
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLogin: true,
}
}
render() {
return (
<div>
<h2>{this.state.isLogin ? '欢迎回来~' : '请先登录~'}</h2>
<button onClick={(e) => this.loginBtnClick()}>
{this.state.isLogin ? '退出' : '登录'}
</button>
</div>
)
}
loginBtnClick() {
this.setState({
isLogin: !this.state.isLogin,
})
}
}
与运算符&&
在某些情况下,我们会遇到这样的场景:
- 如果条件成立,渲染某一个组件;
- 如果条件不成立,什么内容也不渲染;
如果我们使用三元运算符,是如何做呢?
{
this.state.isLogin ? <h2>{this.state.username}</h2> : null
}
其实我们可以通过逻辑与&&
来简化操作:
{
this.state.isLogin && <h2>{this.state.username}</h2>
}
列表渲染
列表渲染
在开发中我们会从服务器请求到大量的数据,数据会以数组的形式存储。
我们需要通过 JavaScript
代码的方式组织数据,转成 JSX
。
我们来演练一个案例:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
skills: ['HTML', 'CSS', 'JavaScript', 'React', 'Node'],
}
}
render() {
return (
<div>
<h2>前端技能</h2>
<ul>
{this.state.skills.map((item) => {
return <li>{item}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
数组处理
很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
- 比如过滤掉一些内容:
filter
函数 - 比如截取数组中的一部分内容:
slice
函数
比如我当前有一个数组中存放了一系列的数字:[10, 30, 120, 453, 55, 78, 111, 222]
案例需求:从给定数组中获取所有大于等于 50 的数字,并且展示前 3 个数字
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
numbers: [10, 30, 120, 453, 55, 78, 111, 222],
}
}
render() {
return (
<div>
<h2>数字列表</h2>
<ul>
{this.state.numbers
.filter((item) => item >= 50)
.slice(0, 3)
.map((item) => {
return <li>{item}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
列表的 key
我们会发现在前面的代码中只要展示列表都会报一个警告:
列表展示警告
这个警告是告诉我们需要在列表展示的 jsx
中添加一个 key
。
至于为什么需要key
,这里涉及到react
组件渲染的规则,在特别长列表的时候,或是说DOM
节点的子元素发送改变,像是在子元素列表尾部新增元素的时候,只是需要将新增的内容放置于尾部即可,这没有什么问题。
但是如果需要在列表头部新增元素,那么开销会非常大。为了解决这类似的问题,React
引入了,使用 key 进行优化后,树的转换效率会提升。
详细关于 key 的内容请见:
列表 & Key – React (reactjs.org)
JSX 原理解析
JSX 转换本质
实际上,jsx
仅仅只是 React.createElement(component, props, ...children)
函数的语法糖。所有的 jsx 最终都会被转换成React.createElement
的函数调用。如果你想深入了解 JSX
的实现原理,请详见深入 JSX
案例练习
列表展示
真实开发中,我们的数据通常会从服务器获取,比较常见的是获取一个列表数据,保存到一个数组中进行展示
- 比如现在有一个代办事项列表,我们如何通过 React 进行展示呢?
我们还是通过一个组件来完成:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
things: ['写文章', '开会', '上课', '读书'],
}
}
render() {
return (
<div>
<h2>代办事项列表</h2>
<ul>
{this.state.things.map((item, index) => {
return <li>{index + 1 + '.' + item}</li>
})}
</ul>
</div>
)
}
}
点赞功能案例
代办事项列表的案例中并没有交互,我们再来实现一个点赞功能的案例:
class App extends React.PureComponent {
constructor() {
super()
this.state = {
isLike: false,
likeNum: 233,
}
}
likeOrUnlike() {
if (this.state.isLike) {
this.setState({
isLike: false,
likeNum: this.state.likeNum - 1,
})
} else {
this.setState({
isLike: true,
likeNum: this.state.likeNum + 1,
})
}
}
render() {
console.log('render')
return (
<div>
<p>点赞人数:{this.state.likeNum}</p>
<button onClick={(e) => this.likeOrUnlike()}>
{this.state.isLike ? '取消点赞' : '点赞'}
</button>
</div>
)
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。