从零开始的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。以上知识就是本文阐述的几个核心点了,时间也不早了,那么到这里本文结束,晚安。