前言
对于展示也没来说,通常有好几种展示状态(以列表请求为例): 当数据为空、接口报错、加载中、数据正常等状态,在渲染的时候需要正确判断并渲染对应的视图,也就是我们今天要讲的条件渲染。不同于 Vue 的 v-if 、 v-show 等框架提供的 api,React 的条件渲染都是 js 原生的方法再加上一点点的 hack。在 React 中有好几种方法可以实现条件表达式,并且不同的方法适用于不同的场景,取决于你需要处理什么样的问题。
概述
本文列举了 React 的几种条件渲染的实现方式:
- If/Else
- return null 阻止渲染
- 变量
- 三元运算符
- 运算符(&&)
- 自执行函数(IIFE)
- 子组件
If/Else
创建两个组件, 一个是登录状态的提示组件,一个是未登录的提示组件
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
再创建一个 Greeting 组件,它会根据用户是否登录来决定显示上面的哪一个组件。
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
If/Else是最简便的实现条件渲染的方法,它的优势是,在简单场景下使用方便,并且每个程序员都理解这种使用方式。
return null 阻止渲染
如果想隐藏一个组件,你可以通过让该组件的 render 函数返回null,没必要使用一个空 div 或者其他什么元素去做占位符。
需要注意的是,即使返回了 null ,该组件“不可见”,但它的生命周期依然会运行。 举例如下,创建一个计时器组件
import { Component } from 'react'
class Number extends Component{
constructor (props) {
super(props)
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
render () {
if (this.props.number % 2 !== 0) {
return null
} else {
return (
<div className="count_wrap">
<h1>计时器:{this.props.number}</h1>
</div>
)
}
}
}
export default Number
并在 App 组件中调用 Number组件
import { Component } from 'react'
import Number from './Number.js'
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.onClick = this.onClick.bind(this)
}
onClick () {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<Number number={this.state.count} />
<button onClick={this.onClick}>Count</button>
</div>
)
}
}
ReactDOM.render( <App />, document.getElementById('root') );
Number组件只有在偶数时才会展示。因为奇数时,render 函数返回了 null。但是,当你查看console 时会发现,componentDidUpdate函数每次都会执行,无论render函数返回什么。
返回 null 而不是空 div 的另一个好处是,这可以略微提升整个 React 应用的性能,因为 React 不需要在更新的时候 unmount 这个空 div。
变量
可以使用变量来储存 JSX 元素。 并且只有当条件为true的时候才去初始化,而其他的渲染部分并不会因此而改变。
举例如下,定义一个登录和注销的无状态组件:
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
在下面的示例中,我们将创建一个名叫 LoginControl 的有状态的组件。
它将根据当前的状态来决定我们定义的常量 button的值,并根据 button来渲染 <LoginButton /> 或者 <LogoutButton />。
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
三元运算符
我们可以使用三元运算符替代if/else代码块:
condition ? expr_if_true : expr_if_false
举例如下,加入在刚才的例子中添加一段关于用户登录态的描述
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div>
);
}
整个运算符可以放在jsx的{}中,每一个表达式可以用()来包裹JSX来提升可读性。通过三元运算符,可以通过改变组件内的 isLoggedIn 状态来显示当前用户的登录态。
运算符(&&)
在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
例如,当你要么渲染一个组件,要么不做渲染,你可以使用&&运算符。举例如下我们在上面的计时器的例子可以修改如下:
import { Component } from 'react'
class Number extends Component{
constructor (props) {
super(props)
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
render () {
const isEven = this.props.number % 2 === 0
return isEven && (
<div className="count_wrap">
<h1>计时器:{this.props.number}</h1>
</div>
)
}
}
export default Number
如果isEven结果为true,Number组件才会展示;如果isEven结果为false,那么Number组件会被忽略。
最终渲染的结果与 return null 中的例子是一致的。
自执行函数(IIFE)
顾名思义,自执行函数就是在定义以后会被立刻执行,没有必要显式地调用他们。
也就是如下代码:
(function myFunction(/* arguments */) {
// ...
})(/* arguments */);
在 React 中,你可以用一个大括号包裹一整个自执行函数,把所有逻辑都放在里面(if/else、switch、三元运算符等等),然后返回你需要渲染的东西。 举例如下:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{
(() => {
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return button
})()
}
</div>
);
}
子组件
这是 IIFE 的变种,也就是把立即执行函数替换成一个普通函数,我们把上面 IIFE 的例子提取出 BtnControl子组件,通过 props 它接受足够的数据来供它展示。
import { Component } from 'react'
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
class BtnControl extends Component {
render() {
const { isLoggedIn, onLogin, onLogout } = this.props;
const subLogoutClick = () => {
onLogout()
}
const subLoginClick = () => {
onLogin()
}
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={subLogoutClick} />;
} else {
button = <LoginButton onClick={subLoginClick} />;
}
return (
<div>
{button}
</div>
);
}
}
export default BtnControl
然后 LoginControl 组件的 render 中引入 BtnControl组件
function render() {
return (
<div>
<BtnControl isLoggedIn={isLoggedIn} onLogout={this.handleLogoutClick} onLogin={this.handleLoginClick}/>
</div>
);
}
总结
在React中实现条件渲染有很多种实现方式,你可以自由选择任一方式,你可以基于这些理由来找到最适合当前场景的方案:
- 当项目很简单,或者条件渲染的逻辑确认无法复用时,推荐在代码中用
&&或者三元运算符、IIFE 等直接实现条件渲染。 - 当项目很复杂时,尽量都使用 子函数、子组件等方式做更有抽象度的条件渲染。
- 在做逻辑抽象时,考虑下项目的复杂度,避免因为抽象带来的成本增加,让本可以整体理解的项目变得支离破碎。