从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key

壹 ❀ 引
在从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象。那么这篇文章中我们来熟悉react中常用的条件渲染语法。
贰 ❀ 条件渲染
在开发中,我们常有根据一个变量值的真或假来决定渲染A或者B内容的情况,这种需求不管用三元或者if语句都能轻松实现,比如实现一个简单的登录是否成功的文案提示功能:
class IsLogin extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: false
}
}
handleLogin = () => {
this.setState({ isLogin: true });
}
handleLogout = () => {
this.setState({ isLogin: false });
}
renderLogin = () => {
return <h1>欢迎回来!</h1>
}
renderLogout = () => {
return <h1>你好,请登录!</h1>
}
render() {
return (
<div className="isLogin">
<div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
<button className="login" onClick={this.handleLogin}>login</button>
<button className="logout" onClick={this.handleLogout}>logout</button>
</div>
);
}
};
ReactDOM.render(<IsLogin />, document.getElementById('root'));

在这个例子中,我们可以通过两个按钮改变state中关于isLogin的值,而这个值又决定了最终在render中应该渲染哪个文案。通过isLogin的变化,我们甚至都不需要同时显示两个按钮,同样通过变量的变化来决定渲染哪个按钮,修改render为如下,那么效果就是这样了:
render() {
const buttonType = this.state.isLogin
? <button className="logout" onClick={this.handleLogout}>logout</button>
: <button className="login" onClick={this.handleLogin}>login</button>
return (
<div className="isLogin">
<div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
{buttonType}
</div>
);
}

在react的花括号中,我们还能用JS逻辑运算符玩一些花样,比如前面的例子是根据情况显示A或者B,现在我们希望要么显示A,要么什么都不显示,这里就可以用逻辑运算符&&,其实效果与满足if条件完全一致,比如这个官网提供的例子:
function UnreadMessage(props) {
const unreadMessage = props.msg;
return (
<div className="unreadMessage">
<h1>你好!</h1>
{unreadMessage.length > 0 &&
<h2>
你有{unreadMessage.length}条未读信息。
</h2>
}
</div>
)
}
const emailMsg = [1, 2, 3];
ReactDOM.render(<UnreadMessage msg={emailMsg} />, document.getElementById('root'));

在这个例子中就凸显出了JSX语法的特点,我们将JS的逻辑判断与react元素糅合在了一起,并由{}去提供解析。站在JS的角度,这里所做的其实就是下面这段代码:
const emailMsg = [1, 2, 3];
let unreadMessageText = '';
if(unreadMessage.length){
unreadMessageText = `你有${emailMsg.length}条未读信息。`
}
逻辑运算符除了&&之外还有||,这两个的区别简单介绍下,首先是A&&B,它的执行为当执行到A为真时才会继续执行到后面的B,看个例子:
function A(bool) {
return bool;
};
function B() {
console.log(1);
};
A(true) && B();//B输出1
A(false) && B();//B不会执行
而A||B的意思是当A为真时就不会继续执行B了,因为只要有一个为真就可以了,所以当A为假时才会跑后面的B。
A(true) || B();//B不会执行
A(false) || B();//B输出1
对于||的场景,比较常见的是程序中需要传递某个值,假设前者为假,我们会提供一个默认值传递,保证后续的逻辑不会出错:
const arr = props.arr || [];
arr.filter(ele => ele);
在前面的例子中,我们都是根据条件决定组件内部渲染什么,还有种可能性,就是在特定情况下,我们希望组件内部什么都不要渲染;虽然这个组件有被调用,但不管是函数组件还是class组件,都需要通过return来返回需要渲染的react元素,所以在特定条件下,我们可以在return元素前直接return null来达到目的。
function IsShow(props) {
if(!props.show){
return null;
}
return (
<div >hello!</div>
)
}
ReactDOM.render(<IsShow show={false}/>, document.getElementById('root'));
这个例子中,虽然组件IsShow有被调用,但因为组件并未返回任何dom,所以在界面上我们看不到任何东西。
那么到这里我们介绍了react中一些常见的条件渲染场景,在{}中你可以根据需要任何组合这些条件并拿到自己想要的最终结果。
叁 ❀ 列表渲染
在实际开发中,我们常有将数组类数据渲染成列表的需求,在vue或者或者小程序中我们可以借用指令来达到目的,比如vue中的v-for,小程序中的wx:for,angularjs中的的ng-repeat等,以vue为例遍历一个数组可以这样做:
const app = new Vue({
el: '#list',
data: {
users: [
{ name: '听风' },
{ name: '是风' }
]
}
});
<ul id="list">
<!-- 利用v-for遍历 -->
<li v-for="user in users" :key="user.name">
{{ user.name }}
</li>
</ul>
但我们在react中的列表渲染会有所不同,我们不会借用类似的指令,而是通过数组API直接遍历数据并得到我们想要的react元素块,再加入render中进行解析渲染。
在JS中我们想要将一个数组中所有的元素都乘以2可以这么做:
const doubled = [1,2,3].map(ele => ele*2);// [2, 4, 6]
而react遍历列表也类似如此,比如我们需要在ul中通过li展示上面这些结果,我们则需要将要展示的所有li都提前遍历出来,再作为一个变量赋予给ul,像这样:
function List(props) {
const list = props.nums.map(ele => (
<li>{ele * 2}</li>
));
return <ul>{list}</ul>
}
const nums = [1, 2, 3];
ReactDOM.render(<List nums={nums} />, document.getElementById('root'));
在这个例子中,我们先通过map遍历,得到了包含多个li标签的合集,并保存在了变量list中,之后又将list赋予给ul标签内部,从而实现了我们想要的效果。看似完美的效果,当打开控制台就不那么完美了,这段代码报给出了红色警告:

list中的每个child都应该有一个独一无二的属性作为key。这个问题我想大家在vue或者小程序中都有类似的处理,我们来看看react如何解决。
肆 ❀ 独一无二的key
肆 ❀ 壹 为什么要用key
为什么要添加key?我想大家应该都有听说diff算法,对于react而言,每次的props或者state修改都会触发render重新渲染视图,如果是完整的重新渲染代价是昂贵的,而添加key的目的是便于react在数据修改后,能记录元素知道它对应的是先前的谁并进行对比,比如我们有个数组[0,1,2]被渲染,之后被修改为[0,2,2],对于react而言,它只要找到第二个li并修改它的渲染内容即可,而不是完整去渲染。
所以回到上面的列表渲染的例子,我们可以这样为li添加key属性:
const list = props.nums.map(ele => (
<li key={ele}>{ele * 2}</li>
));
我们直接将数组遍历的每个元素自身作为key赋予给了li,保存代码,你会发现控制台的警告已经没有了。
肆 ❀ 贰 不推荐使用index作为key
你也许在想,为什么不用index作为key呢?像这样:
const list = props.nums.map((ele, index) => (
<li key={index}>{ele * 2}</li>
));
但用index做为key其实是有风险的,我们来看个由官网改写的例子:
class Item extends React.Component {
render() {
return (
<div>
<label>{this.props.name}</label>
<div>
<input type='text' />
</div>
</div>
)
}
}
class Example extends React.Component {
constructor() {
super();
this.state = {
list: [
{ name: '听风是风', id: 1 },
{ name: '行星飞行', id: 2 }
]
};
}
addItem = () => {
const id = +new Date;
this.setState({
list: [{ name: '时间跳跃' + id + id, id }, ...this.state.list]
});
}
render() {
return (
<div className="example">
<button onClick={this.addItem}>clie me</button>
<div className="form">
<form>
<h3>不好的做法 <code>key=index</code></h3>
{this.state.list.map((todo, index) =>
<Item {...todo}
key={index} />
)}
</form>
<form>
<h3>更好的做法 <code>key=id</code></h3>
{this.state.list.map((todo) =>
<Item {...todo}
key={todo.id} />
)}
</form>
</div>
</div>
)
}
}
ReactDOM.render(<Example />, document.getElementById('root'))

当我们提前为input输入了值,并点击按钮新建输入框时效果就很明显了,我们的本意是在现有输入框头部插入新的输入框。但当使用inde作为key时react对比了新旧index为0的input,由于index前后都是0,所以react认为此时的item组件是可以复用的,它并没有完全替换掉它,而是单纯更新了item内部的label标签,所以你会发现input创建出来是有值的。
而当我们使用第一无二的标识作为key时点击创建,由于前后根本不是一个东西,react选择了重新创建一个全新的lable与input,并插入到了现有DOM节点之前。
通常来说,我们始终不推荐使用index作为key,因为使用key可能在如下场景引发问题:
- 若对数据进行逆序添加,逆序删除等破坏性操作,会产生没必要的真实
dom更新。 - 如果结构中含包含了输入类的
dom,可能会导致react认为这些输入dom没变化,从而引发界面出现信息对不上的问题。
但如果你说我的数据就是没id,这可怎么办,在react官网介绍的博客中,也推荐了用于随机生成id的小工具,例如shortid或者Nano ID,有兴趣大家可以自己看看用法。
肆 ❀ key与组件
在上一个介绍index作为key会造成问题的例子中,不知道大家有没有发现key是写在需要遍历的组件Item上,而非item内部的div上,其实不难理解,对于react而言,组件Item就是一个整体,我们希望这个整体带有唯一标识,在数据变化时,当前的Item是否应该更新或是新建,所以下面这样的写法就是错误的:
function ListItem(props) {
const value = props.value;
return (
// 错误!你不需要在这里指定 key:
<li key={value.toString()}>
{value}
</li>
);
}
function List(props) {
const listItems = [1, 2, 3].map((number) =>
// 错误!元素的 key 应该在这里指定:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
ReactDOM.render(<List />, document.getElementById('root'))
一个规则就是,key永远加在你所用的数组API内部的元素上,修改成如下这样就好了:
function ListItem(props) {
const value = props.value;
return (
<li>
{value}
</li>
);
}
function List(props) {
const listItems = [1, 2, 3].map((number) =>
<ListItem value={number} key={number.toString()}/>
);
return (
<ul>
{listItems}
</ul>
);
}
关于key最后一点说明就是,虽然我们说key应该独一无二,但并不是说它在全局是独一无二,而是只针对于兄弟元素之前,在我们前面展示index作为key的例子中,其实我们也将数组给了form中去遍历,由于不是兄弟关系,你会发现它们之间的key就算重名也没任何关系。
伍 ❀ 总
好了,那么到这里我们介绍了react中几种常见的条件渲染用法,其实总结来说,在react的{}中我们能做到很多JS中的条件判断骚操作。
除了条件渲染,我们还介绍了列表渲染,这才开发中将非常普遍,与常规框架不同,react并未提供对应的指令,而是借用数组API直接渲染react元素,而说到列表渲染总是离不开与之配对的key,我们了解了为什么要提供key,以及使用index作为key可能造成的问题,所以在开发中总是建议不要使用index作为key。以上知识就是本文阐述的几个核心点了,时间也不早了,那么到这里本文结束,晚安。
从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key的更多相关文章
- 无废话ExtJs 入门教程四[表单:FormPanel]
无废话ExtJs 入门教程四[表单:FormPanel] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在窗体里加了个表单.如下所示代码区的第28行位置,items:form. ...
- react 入门教程 阮一峰老师真的是榜样
- 转自阮一峰老师博客 React 入门实例教程 作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Nati ...
- React入门教程(二)
前言 距离上次我写 React 入门教程已经快2个月了,年头年尾总是比较忙哈,在React 入门教程(一)我大概介绍了 React 的使用和一些注意事项,这次让我们来继续学习 React 一. Rea ...
- PySide——Python图形化界面入门教程(四)
PySide——Python图形化界面入门教程(四) ——创建自己的信号槽 ——Creating Your Own Signals and Slots 翻译自:http://pythoncentral ...
- Elasticsearch入门教程(四):Elasticsearch文档CURD
原文:Elasticsearch入门教程(四):Elasticsearch文档CURD 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接: ...
- RabbitMQ入门教程(四):工作队列(Work Queues)
原文:RabbitMQ入门教程(四):工作队列(Work Queues) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https:/ ...
- React入门教程1---初见面
React入门教程1---初见面:https://blog.csdn.net/solar_lan/article/details/82799248 React 教程 React 是一个用于构建用户界面 ...
- JasperReports入门教程(四):多数据源
JasperReports入门教程(四):多数据源 背景 在报表使用中,一个页面需要打印多个表格,每个表格分别使用不同的数据源是很常见的一个需求.假如我们现在有一个需求如下:需要在一个报表同时打印所有 ...
- WebGL入门教程(四)-webgl颜色
前面文章: WebGL入门教程(一)-初识webgl WebGL入门教程(二)-webgl绘制三角形 WebGL入门教程(三)-webgl动画 颜色效果图: 操作步骤: 1.创建HTML5 canva ...
- Spring Cloud 入门教程(四): 分布式环境下自动发现配置服务
前一章, 我们的Hello world应用服务,通过配置服务器Config Server获取到了我们配置的hello信息“hello world”. 但自己的配置文件中必须配置config serve ...
随机推荐
- Java中内存四区
这里简要说明这四个区域通常用于存储的变量类型: 栈区(Stack): 存放局部变量.方法参数.返回地址等. 变量的生命周期与其所在的方法(函数)的调用周期一致. 堆区(Heap): 主要用于动态分配内 ...
- JMS微服务开发示例(六)安全退出进程
默认情况下,如果在linux,需要关闭微服务进程,请务必使用 kill -15 进程id 命令,其他命令可能会直接关闭进程,造成数据丢失. 例如,有个后台任务,执行了一半,这时候进程突然关闭了,会形成 ...
- Oracle数据类型的简单学习之一
Oracle数据类型的简单学习之一 背景 因为信创安可替代的发展 有很多项目提到了数据库切换到国产数据库的要求. 一般情况是要求从Oracle/SQLServer 迁移到国产的: 达梦/瀚高/人大金仓 ...
- [转帖]Windows下sc create命令行注册服务
https://www.cnblogs.com/li150dan/p/15603149.html 如何将exe注册为windows服务,让其直接从后台运行 方法一:使用windows自带的命令sc,首 ...
- [转帖]linux查看端口及端口详解
https://www.cnblogs.com/the-tops/p/6126941.html 今天现场查看了TCP端口的占用情况,如下图 红色部分是IP,现场那边问我是不是我的程序占用了tc ...
- [转帖]PG Exporter
http://v0.pigsty.cc/zh/docs/reference/kernel-optimize/ Exporter https://github.com/Vonng/pg_exporter ...
- [转帖]XCopy命令实现增量备份
https://www.cnblogs.com/pachongshangdexuebi/p/5051977.html xcopy XCOPY是COPY的扩展,可以把指定的目录连文件和目录结构一并拷贝, ...
- [转帖]比较不同CPU下的分支预测
https://plantegg.github.io/2023/04/16/%E6%AF%94%E8%BE%83%E4%B8%8D%E5%90%8CCPU%E4%B8%8B%E7%9A%84%E5%8 ...
- [转帖]一个故事看懂CPU的TLB
https://www.cnblogs.com/xuanyuan/p/15347054.html Hi,我是CPU一号车间的阿Q,还记得我吗,真是好久不见了- 我所在的CPU是一个八核CPU,就有八个 ...
- 谈JVM xmx, xms等内存相关参数合理性设置
作者:京东零售 刘乐 说到JVM垃圾回收算法的两个优化标的:吞吐量和停顿时长,并提到这两个优化目标是有冲突的.那么有没有可能提高吞吐量而不影响停顿时长,甚至缩短停顿时长呢?答案是有可能的,提高内存占用 ...