引导语
在前一个小节中我们讲解了元素渲染,但在实际应用中,会出现比较复杂的渲染场景,例如根据不同条件渲染不同内容以及列表数据渲染等场景。我们都知道,在 JavaScript 中,条件渲染有 if / else、三目运算符等,列表渲染有 for、map、forEach、filter 等等,那么 React 是如何实现条件渲染和列表渲染的呢?带着这个问题,进入本小节条件渲染以及列表渲染的学习,同时从本小节开始我们的示例会围绕网易云信-IM即时通讯(该网站使用 Vue 开发)的某个或某几个模块展开,本册子结束后,会完成一个完整的网易云信-IM即时通讯的项目。
条件渲染
React 中的条件渲染和 JavaScript 中的一样,都是根据不同的条件,来渲染不同的元素,在 React 中是更新匹配的 UI。
React 中有多种方法可以实现条件表达式,进而渲染元素或者组件,这些方法适用于不同的应用场景,接下来会分别介绍不同的条件渲染方式。
在介绍这些条件渲染方式前,我们先创建登录和注册两个组件,分别放在 Login.js
和 Register.js
中,然后通过不同的条件渲染方式来实现一个具有登录和注册两个模块切换功能的 demo。
这两个组件的代码可以去本小节结尾处的 demo 里获取。
首先,我们创建一个基础组件,组件新增属性 isLogin
,它用来判断组件是登录还是注册(登录组件 login 和注册组件 register 在表单小节会详细讲解,此处不做阐述),进而来渲染相应的组件。
import React from 'react'
import Login from './login'
import Rejister from './register'
import './App.scss'
class App extends React.Component {
constructor() {
this.state = {
isLogin: true
}
}
}
接下来就到了 render
方法了,可以通过以下这些方式来进行条件渲染。
1. if / else
最常见的应该就是 if/else
了,可以说此方法是最简单的实现方式了,逻辑上很清晰,但是会存在一些问题,比如重复代码会重新渲染,显得 render
方法过于臃肿。
class App extends React.Component {
// ...
render() {
if (this.state.isLogin) {
return (
<div className="container-login-register">
<Login />
</div>
)
} else {
return (
<div className="container-login-register">
<Rejister />
</div>
)
}
}
}
2. 变量
使用变量来存储元素,这样可以有条件地渲染组件的部分,剩余部分保持不变。
class App extends React.Component {
// ...
render() {
let element
if (this.state.isLogin) {
element = <Login />
} else {
element = <Rejister />
}
return (
<div className="container-login-register">
{element}
</div>
)
}
}
此方法适用于有多个组件多种条件下的渲染。
3. 三元运算符
使用 JSX 表达式,我们可以实现内联条件渲染,其中一种就是使用三元运算符,如下:
class App extends React.Component {
// ...
render() {
return (
<div className="container-login-register">
{this.state.isLogin ? <Login /> : <Rejister />}
</div>
)
}
}
:::info
此方法适用于两个组件二选一的渲染,当然也可以多层嵌套,但不推荐使用。
:::
4. 行内条件渲染与运算符 &&
另一种内联条件渲染,就是与运算符 &&
class App extends React.Component {
// ...
render() {
return (
<div className="container-login-register">
{this.state.isLogin && <Login />}
{!this.state.isLogin && <Rejister />}
</div>
)
}
}
此方法适用于一个组件有无的渲染, true && component 此时会渲染 component。
5. 返回 null 阻止渲染
如果我们只想在登录时显示登录状态,登录了就不显示内容。那么我们可以按下面的写法,未登录返回 null。
class App extends React.Component {
// ...
render() {
return (
<div>
{this.state.isLogin ? <>已登录</> : null}
</div>
)
}
}
至此,我们就完成了一个简单的切换登录、注册界面的 demo 。
列表渲染
列表循环是非常常用的操作,那么在 React 中我们要如何使用呢?
React 中渲染列表就如同 JavasSript 中处理数组类似,比如我们可以使用 map 函数将原来的数组处理成我们需要的数组,这样开头,可能你大致就知道该如何在 React 中渲染列表了,即使没想法也没关系,我们接下来会详细讲解列表渲染。
先不要急着看本小节正文,大家先思考一个小小的问题: JavaScript 中循环遍历的函数有哪些?你可以先自己动手写一下,再看下面的内容。
- for ;
- while ;
- do…while ;
- for…in 你可能会说 for…in 用于遍历对象,其实数组也是对象;
- for…of 使用 for…of 能直接遍历出数组中的 value 值;
- map 返回一个新数组,并不会改变原数组;
- forEach 与 map 相似,但是它不返回值,只是操作数据;
- filter 过滤数组成员,返回新数组,并不会改变原数组;
- some 和 every,统计数组是否满足某个条件,some 是只要一个成员的返回值是 true,则整个 some 方法的返回值就是 true,否则返回 false,every 则是只要有一个是 false,则返回 false;
- reduce(),reduceRight() 方法可依次处理数组的每个成员。
这些你都掌握了吗?接下来我们一起来探究下 React 中的列表渲染。
map
class App extends React.Component {
render() {
const tabMenu = [{
title: '常用联系人',
tag: ''
}, {
title: '好友',
tag: ''
}, {
title: '群组',
tag: ''
}]
const tabMenuLi = tabMenu.map((item, index) =>
<li>
{item.title}
</li>
);
return (
<ul className="tab">
{ tabMenuLi }
</ul>
)
}
}
从上面这段代码可以看出,我们循环渲染了一个 tab 列表。不过这里有一个问题,控制台会提示下图中的错误,那么我们怎么解决呢?
很简单,我们只需在 <li>
标签中加入 key 值就行。
class App extends React.Component {
render() {
const tabMenus = [{
title: '常用联系人',
tag: ''
}, {
title: '好友',
tag: ''
}, {
title: '群组',
tag: ''
}]
// map箭头函数 不能加花括号 只能是单行
const tabMenuLi = tabMenus.map((item, index) =>
<li key={item.title}>
{item.title}
</li>
);
return (
<ul className="tab">
{ tabMenuLi }
</ul>
)
}
}
可以看到如下效果图,渲染出了数据。
读到此处,相信你一定会疑惑 key 的作用是什么呢?
其实 React 中的 key 属性是给 React 自己用的一个特殊属性,就是说即使为一个组件设置 key 之后,我们也无法获取这个组件的 key 值。它是一种身份标识,每个 key 对应一个组件。key 的值必须保证唯一且稳定,它类似于数据库中的主键 id 一样,有且唯一。
这里我们使用了 title 作为 key 值,通常情况下,我们也可以使用索引或者从服务端获取的每条数据的 id 作为 key值。
一定要注意:key值一定要放在就近的数组上下文中。 代码如下:
// 关于组件的部分我们可以在后面部分再深入学习,这里可以先做了解
function ListItem ({title}) {
return (
// 不能将key值放在这里
<li>{ title }</li>
)
}
class App extends React.Component {
render() {
const tabMenus = [{
title: '常用联系人',
tag: ''
}, {
title: '好友',
tag: ''
}, {
title: '群组',
tag: ''
}]
const tabMenuLi = tabMenus.map((item, index) =>
// key值要放在这里,这是最就近数组的上下文
<ListItem {...item} key={item.title} />
);
return (
<ul className="tab">
{ tabMenuLi }
</ul>
)
}
}
注意:React 中 key 值在兄弟元素之间必须是唯一的,不过,好在它们不用是全局唯一,否则 key 值的问题将成为一个头号难题。
React 中的唯一标识 key 在更新 DOM 时会用到,与虚拟 DOM diff 算法强关联,后续章节对此会有详细讲解,此处有个印象即可。
for / forEach
既然能用 map ,那么肯定也可以使用 for 和 forEach ,看看如下代码:
import React from 'react';
import './App.css';
class App extends React.Component {
render() {
const tabMenus = [{
title: '常用联系人',
tag: ''
}, {
title: '好友',
tag: ''
}, {
title: '群组',
tag: ''
}]
let tabMenuLi = []
for(let i = 0; i < tabMenus.length; i++) {
const item = tabMenu[i]
tabMenuLi.push(
<li key={item.title}>
{item.title}
</li>
)
}
return (
<ul className="tab">
{ tabMenuLi }
</ul>
)
}
}
export default App;
通过对比,我们可以发现 for / forEach 渲染列表时,都是通过循环来构建列表,但 map 方法可以直接返回一个新数组,推荐使用 map。
思考一下,如果我们这里需要根据用户的权限,进行相应的 tab 显示,应该如何编写代码呢?如果你有思考,那就继续看下面代码:
class App extends React.Component {
state = {
permission: 'user'
}
render() {
const strategy = {
user: ['Contact', 'Friend'],
admin: ['Contact', 'Friend', 'Group']
}
const tabMenus = [{
title: '常用联系人',
tag: 'Contact'
}, {
title: '好友',
tag: 'Friend'
}, {
title: '群组',
tag: 'Group'
}]
const filterTabMenus = tabMenus.filter(
item => strategy[this.state.permission].includes(item.tag)
)
const tabMenuLi = filterTabMenus.map(item =>
<li key={item.title}>
{item.title}
</li>
);
return (
<ul className="tab">
{ tabMenuLi }
</ul>
)
}
}
我们可以看到通过权限的判断,用 filter 过滤出我们需要使用的数据,然后再使用 map 来渲染出 <li>
,你需要跟着以上代码自己写一遍,然后试着把 permission
的值换成 admin
看看。
上面代码用到的策略模式,可以用在很多地方,也同样值得我们学习、思考,并转化成自己的知识。
小结
本小节我们学习了 React 中条件渲染和列表渲染的几种方式,以及各自的使用场景,其实和 JavaScript 中的渲染很相似,唯一的区别就是 React 中列表渲染这部分需要设置标识 key,本小节还深入探究了此处 key 值的作用,key 的值必须保证是唯一且稳定的,它是高效更新组件必须的一个标识,后续虚拟 DOM diff 算法章节会有深度解析,此处暂且有个印象即可。看完了本小节,大家还需根据文中的步骤,把本小节的代码自己演练一遍,这样才能真正掌握本小节讲解的关于条件渲染和列表渲染的内容。掌握了本小节的内容,我们在后面的学习中方能够游刃有余。
demo地址
条件渲染:https://github.com/XJFM/imooc-react/tree/master/01_05.conditional_render
列表渲染:https://github.com/XJFM/imooc-react/tree/master/01_05.list_render