React中的条件渲染
在Web中时常需要根据不同的状态来渲染对应状态下的部分内容。在React中也同样有这样的运用场景,只不过在React中可以创建不同的组件来封装各种所需要的行为,然后根据不同状态来渲染对应的内容。如果你熟悉Vue的话,知道他有一个v-if
指令可以轻易帮助你根据不同的状态(条件)来渲染所需的组件(或UI),但在React没有这样的指令。虽然在React中没有v-if
这样的指令,但它可以使用你熟悉的JavaScript条件语句(比如if...else
或switch
)。接下来,我们就一起来看看如何在React中实现条件渲染。
JavaScript中的条件语句
在React中我们一般使用JSX来构建我们所需要的组件模板。通过《深入了解JSX》和《如何编写出优雅的JSX》的学习我们知道JSX简单地说是HTML和JavaScript的结合物,以及我们如何构建出更好的JSX模板。
在JSX中我们可以像使用JavaScript的if
、switch
或条件运算符一样。也就是说,React中的条件渲染和JavaScript中的一样,使用JavaScript运算符if
、switch
或条件运算符去创建元素来表现当前的状态,然后根据不同的状态来更新UI。
在学习React中条件渲染相关的知识之前,咱们先一起来回顾一下JavaScript中有关于条件运算相关的知识。
回顾JavaScript中的条件语句
在JavaScript中我们可以使用if
条件语句来根据条件做判断,从而获得相应的结果;另外也可以使用switch
来达到相应的效果。我们可以拿生活中的一个示例来描述JavaScript中的条件语句。
例如,天气预报说明天可能会降温,有点冷,那么你可能会决定明天要不要加衣服。针对这样的场景,我们可以创建一个类似下图这样的决策模型:
在每做一个决定都会问自己一个问题,问题的答案总是在true
和false
之间做选择。这个问题的答案决定了你的下一个选择。比如上图的穿衣决策模型。而这种模型用于我们的编码中也非常的适用。
if ... else
语句
在JavaScript中使用最常见的条件语句是if
、if ... else
或者if ... else if ... else
。如果把穿衣服的选择放到我们的编码中来,可能会像下面这样:
let isCooling = true
let isThick = true
if (isCooling) {
if (isThick) {
console.log('明天要穿羽绒服,戴鸭绒帽')
} else {
console.log('明天穿件普通外套就可以')
}
} else {
console.log('明天穿件衬衣')
}
在if
条件语句中需要一个表达式,该表达式返回的值是 true
(真) 或 false
(假)。在JavaScript中我们可能通过 比较运算符、逻辑运算符 和 关系操作符来获得true
或false
的值。也就是说,if
语句中除了像上例那样简单直接给表达式传true
或false
的值之外,还可以通过运算符得到。
比较运算符
常见的JavaScript比较运算符主要有:
比较运算符 | 描述 |
---|---|
== |
如果两边操作数相等时返回true ,否则返回false |
!= |
如果两边操作数不相等时返回true ,否则返回false |
=== |
如果两边操作数相等且类型相同时返回true ,否则返回false |
!== |
如果两边操作数不相等或类型不相同时返回true ,否则返回false |
> |
如果左边的操作数大于右边的操作数时返回true ,否则返回false |
>= |
如果左边的操作数大于或等于右边的操作数时返回true ,否则返回false |
< |
如果左边的操作数小于右边的操作数时返回true ,否则返回false |
<= |
如果左边的操作数小于或等于右边的操作数时返回true ,否则返回false |
在JavaScript中使用==
和===
得到的结果会有所不同,其中==
会发生一些奇怪的现象,如下图所示:
逻辑运算符
逻辑运算符常用于布尔(逻辑运算)值之间;当操作数都是布尔值时,返回的也是布尔值,不过实际上&&
和||
返回的是一个特定的操作数的值,所以当它用于非布尔值的时候,返回值就可能是非布尔值。
逻辑运算符 | 描述 |
---|---|
&& |
通常称为逻辑与,当操作数都为true 时返回true ,否则返回false |
` | |
! |
通常称为逻辑非,如果操作数为true 则返回false ,否则返回true |
关系操作符
关系操作符对操作数进行比较,根据比较结果true
或false
,返回相应的布尔值。
关系操作符 | 描述 |
---|---|
in |
如果所指定的属性确实存在于所指定的对象中,则返回true ,否则返回false |
instanceof |
如果所判别的对象确实是所指定的类型,则返回true ,否则返回false |
在JavaScript中除了if
之外还有if ... else
以及if ... else if ... else
等。我们用流程图来描述这几种类型的if
语句大致如下图这样的。
if
语句
单个if
语句是最简单的if
语句,比如:
if (temperature < 2) { // 气温是否低于2度
console.log('穿羽绒服')
}
如果我们用图来描述的话,大致像下图这样:
平时写代码的时候,有可能会碰到多个if
并排,类似于if ... if
这样的。我们在上面的示例上做做改造:
if (temperature > 30) {
console.log('今天穿短袖')
}
if (temperature > 20 && temperature < 30) {
console.log('今天穿衬衣')
}
if (temperature > 10 && temperature < 20) {
console.log('今天穿夹克')
}
if (temperature < 10) {
console.log('今天穿羽绒服')
}
if ... else
语句
我们在编码的时候,还会碰到条件满足做什么,条件不满足做什么。用图的话可以这样来描述:
来看一个简单的实例:
if (isWeekend) {
// 是周末
console.log('不用上班')
} else {
// 不是周末
console.log('需要上班')
}
同样的,在if ... else
中还会嵌套if ... else
(可以在任何{}
内嵌套),如果我们用图来表示的话,大致如下图这样:
在实际编码中,我们可以按照上图的模型来使用:
if (number == 0) {
console.log('number是个蛋')
} else {
if (number > 0) {
console.log('number是个正数')
} else {
console.log('number是个负数')
}
}
if ... else if ... else
语句
接下来我们来看多个条件判断,JavaScript可以使用if ... else if ... else
,甚至还可以使用if ... else if ... else if ... else
(还可以更多个)。如果用图来描述的话,大致像下面这样:
我们用这个模式来修改上面数值的判断:
if (number == 0) {
console.log('number是个蛋')
} else if (number > 0) {
console.log('number是个正数')
} else {
console.log('number是个负数')
}
switch语句
虽然我们可以使用if ... else
这样的条件语句执行一个多路的分支。但有的时候这并不是最好的解决方案,特别是当所有分支都依赖于单个变量的值时。面对这样的场景,我们可以使用switch
语句来处理这种情况,它比重复使用if
条件语句更有效。
JavaScript的switch
语句常用来决策目的。在某些情况下,使用switch
语句会比if
语句更方便。同样的,我们可以用流程图来描述一个switch
语句,如下图所示:
从流程图上来看,它和if ... else if ... else
非常的相似。同样的用一个示例来向大家阐述switch
的使用:
switch (new Date().getDay()) {
case 0:
console.log('今天是星期日')
break;
case 1:
console.log('今天是星期一')
break;
case 2:
console.log('今天是星期二')
break;
case 3:
console.log('今天是星期三')
break;
case 4:
console.log('今天是星期四')
break;
case 5:
console.log('今天是星期五')
break;
case 6:
console.log('今天是星期六')
break
}
三元运算符
在if
语句中,我们在一种if ...else
的语句。简单地说,条件语句有一个条件,它返回的值要么是true
,要么是false
,并且会根据对应的结果做出相应的选择。对于这样的场景,我们除了可以使用if ... else
之外这样的条件语句之外还可以使用另一种做法,即三元运算符。他的使用看起来像这样:
( condition ) ? run this code : run this code instead
如果我们用实例来描述的话,大致像下面这样:
isBirthday ? '生日快乐' : '早上好!'
如果isBirthday
的值是true
的话会返回**生日快乐**
,如果值是false
的话,就会返回**早上好!**
。
条件语句的优化
条件语句是JavaScript的基础知识,虽然该部分知识看上去非常的简单。但如果写得不好的话,还是会令人难以理解。事实上呢?在写条件语句(不管是if
语句还是switch
语句)我们还是有一些手段可以对其进行优化的。接下来我们来看一些简单的优化手段。
使用&&
取代单纯的if
语句
如果在编码的时候,只使用单纯的if
语句的话,我们可以使用&&
来替代。比如:
if (user && user.canDeletePost) {
deletePost()
}
可以用下面代码来替代:
user && user.canDeletePost && deletePost()
使用||
取代if ... else
语句
拿一个判断用户密码长度的例子来说。
let strength = null
if (password.length > 7) {
strength = 'Strong'
} else {
strength = 'Weak'
}
针对上面的代码,我们可以使用||
来替代:
const strength = (password.length > 7) && 'Strong' || 'Weak'
三元运算符
三元运算符适合if ... else
这种情况,如果你的代码中会使用if ... else
这样场景,可以采用三元运算符来替代:
if (isWeekend) {
console.log('周末不用上班')
} else {
console.log('工作日哟,得上班')
}
用三元运算符可以这样来写:
(isWeekend) ? console.log('周末不用上班') : console.log('工作日哟,得上班')
除此之外,还有一些其他的优化手段,比如@Jecelyn Yeen在她的博文《5 Tips to Write Better Conditionals in JavaScript》中所介绍的,不同的方式方法来优化JavaScript的条件语句,感兴趣的可以看看。另外,有关于JavaScript中更多的条件语句相关的介绍还可以阅读:
- JAVASCRIPT. BUT LESS IFFY.
- Replacing switch statements with Object literals
- Conditionals in JavaScript: If, Else If and Else
- JavaScript Expressions: Shortening Logic with Expressions!
- Tips and Tricks for Better JavaScript Conditionals and Match Criteria
React中的条件渲染
文章开头提到过,React并不像Vue一样能有类似v-if
这样的指令根据条件来渲染不同的内容。不过在React中,JSX是一个强大的JavaScript的扩展,允许我们定义UI组件。虽然它不能直接支持条件表达式来渲染,但是我们可以使用JavaScript的条件语句来实现条件渲染。
在React中使用条件表达式的方式不止一种。而且,和JavaScript中一样,我们可以根据需要解决的问题选择最为合适的方式。
React中的if ... else
React和JavaScript有点类似,也可以使用if ... else
这样的方式来做条件渲染,该方式也算得上是React中条件渲染的简单方式之一。比如你的项目中希望根据所传数据做出不同样式的渲染,就拿一个列表来说吧,当有列表的时候渲染出列表,当没有列表时就显示一个提示信息。对于这样的场景,我们就可以使用if ... else
来做处理。就像下面这样:
实现该UI渲染效果其实并不复杂:
const Lists = ({list}) => {
if (!list) {
return null
}
if (!list.length) {
return <div className="no__list">任务都处理完了</div>
} else {
return (
<ul className="list">
{list.map(item => <li className="list__item">{item}</li>)}
</ul>
)
}
}
在调用Lists
组件时,如果传给list
的值是个空值,那么显示的效果即上图的上半部分(只有一句提示文案),如果将一个数组传给list
比如像下面这样:
const listsData = ['CSS', 'React', 'Vue', 'HTML5', 'JavaScript']
const rootElement = document.getElementById("app");
ReactDOM.render(<Lists list={listsData}/>, rootElement);
那么渲染的效果将是一个列表,如上图的下半部分。具体的实例如下所示:
如果list
不存在,那么List
组件什么内容都不会显示。
React中的三元运算符
三元运算符可以很好的替代if ... else
这样的条件语句。在React的条件渲染中同样也可以使用三元运算符。比如上面的示例,如果我们更换成三元运算符,大致像下面这样:
const Lists = ({list}) => {
return
(!list.length) ?
(<div className="no__list">任务都处理完了</div>) :
(
<ul className="list">
{list.map(item => <li className="list__item">{item}</li>)}
</ul>
)
}
const listsData = ['CSS', 'React', 'Vue', 'HTML5', 'JavaScript']
const rootElement = document.getElementById("app");
ReactDOM.render(<Lists list={listsData}/>, rootElement);
最终得到的效果是一样的:
三元操作符使得React中的条件渲染要比if ... else
语句更简洁。在return
语句中内联它很简单。
React中的逻辑运算符
在JavaScript中有&&
(逻辑与,两个都是true
才是真),||
(逻辑或,只要有一个true
就是真)和!
(逻辑非,真就是假,假就是真)。
在平时开发的时候,我们经常会碰到这样的场景,比如页面在加载内容时,会有一个加载动效。针对这样的场景,我们可以写个加载组件,比如LoadingIndicator
,该组件就两个选择,要么是有加载动效,要么什么内容都没有(返回null
)。针对这样的场景,我们可以使用前面介绍的if ... else
或三元操作符。其实还有一种更为简洁的方式可以让我们省略返回null
的必要性。该方法就是使用JavaScript的逻辑运算符中的&&
。
在JavaScript中,它的工作类似下面这样:
const result = true && 'Hello, React!'
console.log(result) // ~> 'Hello, React!'
const result = false && 'Hello, React!'
console.log(result) // ~> false
在React中,我们可以利用这种特性。如果条件为true
,则会输出逻辑符&&
后面的表达式。如果条件为false
,React则会忽略并跳过表达式。
接下来,我们使用该特性,来构建我们所需要的LoadingIndicator
组件,其代码大致如下:
const LoadingIndicator = ({isLoading}) => {
const loadingEle = (
<div className="loader">
<span></span>
</div>
)
return (<>{isLoading && loadingEle}</>)
}
const rootElement = document.getElementById("app");
ReactDOM.render(<LoadingIndicator isLoading={true}/>, rootElement);
最终的效果如下:
当LoadingIndicator
组件的isLoading
值为true
和false
时,会决定loadingEle
是否渲染,如下图所示:
React中的switch
语句
在写组件的时候,可能会有多个条件渲染的情况。比如我们要构建一个Alert
组件,它可以根据不同的状态渲染出不同的效果,比如info
、error
、warning
等。针对于这样的场景,我们可以使用switch
来构建。
代码如下:
const Alert = ({text, state}) => {
switch(state) {
case 'primary':
return <div className={`alert alert-${state}`}>{text}</div>
case 'secondary':
return <div className={`alert alert-${state}`}>{text}</div>
case 'success':
return <div className={`alert alert-${state}`}>{text}</div>
case 'danger':
return <div className={`alert alert-${state}`}>{text}</div>
case 'warning':
return <div className={`alert alert-${state}`}>{text}</div>
case 'info':
return <div className={`alert alert-${state}`}>{text}</div>
case 'light':
return <div className={`alert alert-${state}`}>{text}</div>
case 'dark':
return <div className={`alert alert-${state}`}>{text}</div>
default:
return null
}
}
const rootElement = document.getElementById("app");
ReactDOM.render(<Alert text="A simple primary alert—check it out!" state="primary" />, rootElement);
这个时候渲染出来的是primary
状态下的Alert
:
我们可以结合前面渲染列表的示例,渲染出Alert
不同状态下的效果:
在这个示例中我们用到了React中的列表渲染,在后面我们将会一起来学习如何在React中实现列表渲染。
React中的枚举
在JavaScript中,当对象用键值(Key: Value
)对映射时,该对象可以用作枚举。从MDN的属性的可枚举性和所有权中我们可以获知:
可枚举属性是指内部可枚举标志(
enumerable
)设置为true
的属性,自然不可枚举属性即是enumerable
为false
。
在JavaScript中我们可以使用propertyIsEnumerable()
来判断可枚举与不可枚举属性方法:
当
propertyIsEnumerable()
方法返回一个布尔值,表示指定的自身属性是否可枚举。(不包括原型链继承的属性)
有关于JavaScript中对象属性的枚举可以阅读下面文章:
比如下面这个示例:
const STATE_ENUM = {
info: 'Alert Info',
warning: 'Alert Warning',
error: 'Alert Error'
}
在React中枚举也是实现多个条件渲染的好方法。比如上面的Alert
组件,我们就可以通过枚举的方式来替代switch
。
const Alert = ({state, text}) => {
return (
<>
{{
primary: <div className={`alert alert-${state}`}>{text}</div>,
secondary: <div className={`alert alert-${state}`}>{text}</div>,
success: <div className={`alert alert-${state}`}>{text}</div>,
danger: <div className={`alert alert-${state}`}>{text}</div>,
warning: <div className={`alert alert-${state}`}>{text}</div>,
info: <div className={`alert alert-${state}`}>{text}</div>,
light: <div className={`alert alert-${state}`}>{text}</div>,
dark: <div className={`alert alert-${state}`}>{text}</div>
}[state]}
</>
)
}
const rootElement = document.getElementById("app");
ReactDOM.render(<Alert state="danger" text="A simple danger alert—check it out!" />, rootElement);
得到的效果和使用switch
是一样的:
上面的示例中,我们通过state
属性的key
帮助我们从对象中检索值value
。是不是同样的简单,而且与switch
相比,它更具可读性。
React中的多级条件渲染
在JavaScript中有多级嵌套渲染,同样的,在React中也有嵌套的条件渲染。比如前面的列表渲染示例,它可以显示出一个列表,也可以是一段提示文本,还可以什么都不显示。
除了使用if ... else
这样的条件渲染之外,还可以使用多级渲染的方式。我们使用多级嵌套来改造前面的列表组件:
const Lists = ({list}) => {
const isNull = !list
const isEmpty = !list && !list.length
return (
<>
{
isNull ? null : (
isEmpty ? (<div className="no__list">任务都处理完了</div>) :
(
<ul className="list">
{list.map(item => <li className="list__item">{item}</li>)}
</ul>
)
)
}
</>
)
}
const listsData = ['CSS', 'React', 'Vue', 'HTML5', 'JavaScript']
const rootElement = document.getElementById("app");
ReactDOM.render(<Lists list={listsData}/>, rootElement);
最终渲染出来的结果是一样的:
多级嵌套会让代码变得更难于阅读。在如何编写出优雅的JSX中也着重提到这方面,感兴趣的可以阅读该文。
比如上面的列表组件,我们可以像下面这样来改造,将会提高代码的阅读性:
const Lists = ({list}) => {
const isList = list && list.length
return (
<>
{ isList ? (
<ul className="list">
{list.map(item => <li className="list__item">{item}</li>)}
</ul>
) : <NoList isNull={!list} isEmpty={list && !list.length} />}
</>
)
}
const NoList = ({isNull, isEmpty}) => {
return (!isNull && isEmpty) && <div className="no__list">任务都处理完了</div>
}
const listsData = ['CSS', 'React', 'Vue', 'HTML5', 'JavaScript']
const rootElement = document.getElementById("app");
ReactDOM.render(<Lists list={listsData}/>, rootElement);
最终得到的效果是一致的:
React中的高阶组件
高阶组件(HOCs)是React中条件渲染的佳方式。什么是高阶组件,这里不做过多的阐述,如果你感兴趣的话,可以阅读下面这些文章:
- 高阶组件
- Using Recompose to write clean Higher-Order Components
- Introduction to Higher-Order Components in React by example
- Reusing React Component logic with Higher Order Component
- The Common Patterns of React
- CROSS-CUTTING FUNCTIONALITY IN REACT USING HIGHER-ORDER COMPONENTS, RENDER PROPS AND HOOKS
- How to Use React Higher-Order Components
- Understanding React Render Props and HOC
- React's Render Props Pattern
- A gentle Introduction to React's Higher Order Components
- React Higher-Order Components
- Getting started with Higher Order Components and Recompose
- 高阶组件(Higher-Order Components)
- React — Composing Higher-Order Components (HOCs)
- React Higher Order Components in 3 minutes
- What are Higher-Order Components in React?
- Higher Order Components in React
- ReactJS Higher-Order Components in Plain English
- 高阶组件(Higher-Order Components)
- 探索Vue高阶组件
高阶组件(HOCs)是一个函数,它接受一个现有的组件,然后返回一个带有一些附加功能的新组件:
const EnhancedComponent = higherOrderComponent(component);
应用于条件渲染,高阶组件可以返回一个不同于基于某些条件传递的组件:
function higherOrderComponent(Component) {
return function EnhancedComponent(props) {
if (condition) {
return <AnotherComponent { ...props } />;
}
return <Component { ...props } />;
};
}
因此,我们首先定义一个函数,该函数有两个参数,另一个函数将返回一个布尔值,如果该值为true
,则返回组件:
function withEither(conditionalRenderingFn, EitherComponent) {
// ...
}
按照惯例,高阶组件的名称以with
开头。这个函数将返回另一个函数,该函数将采取原来的组件返回一个新的:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
// ...
}
}
这个内部函数返回的组件(函数)将是你在应用程序中使用的组件,因此它将获取一个对象,该对象具有其所需的所有属性:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
// ...
}
}
}
内部函数可以访问外部函数的参数,所以现在,根据函数conditionalRenderingFn
返回的值,你可以返回EitherComponent
或原始Component
:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
return conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
}
}
}
我们可以使用箭头函数,让它变得更简洁一些:
const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) =>
conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
这样,使用之前定义的SaveComponent
和EditComponent
组件,可以创建一个withEditConditionalRendering
高阶组件。用这个,创建一个EditSaveWithConditionalRendering
组件:
const isViewConditionFn = (props) => props.mode === 'view';
const withEditContionalRendering = withEither(isViewConditionFn, EditComponent);
const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent);
你可以使用render
方法传递所有需要的属性:
render () {
return (
<div>
<p>Text: {this.state.text}</p>
<EditSaveWithConditionalRendering
mode={this.state.mode}
handleEdit={this.handleEdit}
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>
</div>
);
}
最终效果如下:
使用IIFE实现条件渲染
IIFE代表立即调用函数表达式,该模式允许你将函数声明为表达式并在其声明之后立即执行它。表达式可以与JSX代码自由组合,IIFE也不例外。表达式值将是函数返回的值。你可以在其中包含任何类型的逻辑,包括if/else
语句。也就是说,我们可以使用IIFE实现条件渲染。比如下面这个示例:
const Drawer = () => {
const [expanded, setExpanded] = React.useState(false)
const HandlerClick = () => {
setExpanded(!expanded)
}
const renderDrawer = () =>{
const menuData = ['CSS', 'HTML5', 'JavaScript', 'React', 'Vue']
return (
<ul id="menu">
{menuData.map((menu) => <li><a href="#">{menu}</a></li>)}
</ul>
);
}
return (
<div>
<button onClick={HandlerClick} className={expanded ? 'on' : ''}><span></span></button>
{
(() => {
if (expanded) {
return renderDrawer();
}
})()
}
</div>
)
}
ReactDOM.render(<Drawer />, document.querySelector('#app'))
效果如下:
小结
文章中介绍了React中条件渲染的几种方式。不管是哪种方式,都有各自的优劣,在实际使用过程中,应该根据相应的场景来选择对应的方式。我想你在实际编码的过程中一定也碰到要根据相应的条件(状态)来渲染不同的UI或状态。如果你在这方面有经验,欢迎在下面的评论中与我们一起分享。
在React中除了需要根据条件渲染相应的UI或组件之外,在很多时候还会面要渲染列表组件。其实在上面的示例中,我们已经碰到了怎么渲染列表,事实上,渲染列表也会涉及到很多知识点。在后续中将和大家一起来探讨React中列表是怎么渲染的。如果你感兴趣的话,欢迎持续关注后续的相关更新。