ReactJs - 创建一个“如果”组件...是个好主意吗?

62
我在React文档中读到,"if"类型的语句不能在JSX代码中使用,因为JSX渲染成JavaScript的方式不符合预期。但是是否有任何理由认为实现一个"if"组件是一个坏主意?从我的初步测试来看,它似乎可以正常工作,并让我想知道为什么这样做不常见?我的部分意图是允许React开发尽可能地基于标记,尽可能少地使用JavaScript。对我来说,这种方法更像是一种“数据驱动”的方法。你可以在JS Fiddle上查看它

<script type='text/javascript' src="https://unpkg.com/react@0.11.0/dist/JSXTransformer.js"></script>
<script type='text/javascript' src="https://unpkg.com/react@0.11.0/dist/react-with-addons.js"></script>

<script type="text/jsx">
/** @jsx React.DOM */
    
var If = React.createClass({
  displayName: 'If',

  render: function()
  {
    if (this.props.condition)
      return <span>{this.props.children}</span>
    return null;
  }
});

var Main = React.createClass({
    render: function() {
        return (
           <div>
             <If condition={false}>
                <div>Never showing false item</div>
             </If>
             <If condition={true}>
                <div>Showing true item</div>
             </If>
          </div>
        );
    }
});

React.renderComponent(<Main/>, document.body);
</script>

运行以上代码的结果为:

显示真实项目


4
在刚开始学习React时,人们经常会问这个问题,但是在使用了几周后,他们就不再需要它了。只需像其他问题中提到的那样使用三元运算符或&&来有条件地渲染内容即可。 - Brigand
嘿!感谢反馈……我一直在使用有条件地渲染到变量的方法,然后将该变量放入我的“render”响应中,这一直很有效。这种方法的问题在于它会打破JSX并使得在响应中看到完整HTML结构变得困难。这就是为什么我认为在这些情况下,If组件可能会有所帮助,而且确实如此——在这种情况下,响应的完整标记更容易看到——但我从未意识到三元运算符/内联条件语句,以后可能会使用其中之一。 - Brad Parks
就我个人而言,我认为这个问题应该保持开放状态,因为我认为这将在搜索中更容易被找到,并且已经有一些很棒、详细的答案添加到了这个讨论中,我在其他地方没有看到过! - Brad Parks
1
是的,我理解这个道理。一开始我也是这么想的。顺便说一下,封闭的问题通常会在谷歌上获得很多流量,但是如果有什么问题,另一个问题应该被关闭并指向这个问题。 - Brigand
1
不要返回 <noscript/>,而是使用 React 0.11 及更高版本支持的 null - Sophie Alpert
13个回答

68

查看react文档中的JSX中的If-Else部分。

在JSX中,你不能在花括号内放置语句--只能使用表达式。如果你不知道JavaScript中表达式与语句的区别,请阅读这篇文章。这是因为JSX会被转换成函数调用,而你无法将if语句作为JavaScript函数的参数。但是,你可以使用布尔运算符(&&||? :)来完成类似的工作。它们是表达式,因此可以适合JSX生成的构造函数调用中,并且它们的短路求值与if语句中使用的相同。

<div>
    {(true
        ? <div>Showing true item</div>     
        : <div>Never showing false item</div>
    )}
</div>
<p>My name is {this.name || "default name"}</p>
此外,React 将 nullfalse 视为“空组件”,不会在真正的 DOM 中呈现(目前在幕后使用相同的 noscript 技巧)。当你不想要一个“else”分支时,这非常有用。有关详细信息,请参见JSX 中的 False
<div>
    {shouldIncludeChild ? <ChildComponent/> : false}
</div>
关于您提到的 If 组件,它目前存在一个问题,即在其当前形式下,即使条件为假,它仍将评估其子组件。这可能会导致错误,特别是当 If 组件的内容仅在条件为真时才有意义的情况下。
<If condition={person !== null}>
    //This code throws an exception if this.person is null
    <div>{person.name}</div>
</If>

你可以通过将if组件作为函数接收body,而不是作为子组件列表来解决这个问题,但这会更冗长:

<If condition={person !== null} body={function(){
    return <div>{person.name}</div>
}/>

最后,由于"If"组件是无状态的,您应该考虑使用普通函数而不是新的组件类,这样可以使"If"对React的协调算法透明。如果您使用"If"组件,那么<div><If><div>将被认为是不兼容的,React将进行完全重绘而不是尝试将新组件与旧组件合并。

// This custom if function is for purely illustrative purposes
// However, this idea of using callbacks to represent block of code
// is useful for defining your own control flow operators in other circumstances.
function myCustomIf(condition, onTrue, onFalse){
    onTrue  = onTrue  || function(){ return null }        
    onFalse = onFalse || function(){ return null }
    if(condition){
        return onTrue();
    }else{
        return onFalse();
    }
}

<div>    
    {myCustomIf(person !== null, function(){
         return <div>{person.name}</div>
     })}
</div>

你可以使用ES2015来完成这个任务:https://gist.github.com/Ginhing/58305312a75f7f6d6165bb583ecc94ba - Ginhing
"Fat arrow"函数语法使myCustomIf更易于使用,但我更愿意使用内置的 ?: - hugomg
这几乎没有回答问题。 - Petr Peller
但是,如果您正在使用redux,则可以通过创建一些容器来使父组件不知道您在连接时要检查的条件。因此,您可以通过将状态映射到测试“condition”属性来创建基于If容器的AppWasLoaded容器。在这种情况下,使用If来创建更具体的容器是否是一个好主意? - panurg

36

你只需要普通的JS,不需要其他任何东西。

安全且易读(但冗长)

maybeRenderPerson: function() {
    var personName = ...;
    if ( personName ) {
        return <div className="person">{personName}</div>;
    }
}

render: function() {
    return (
       <div className="component">
          {this.maybeRenderPerson()}
       </div>
    );
}

虽然有点啰嗦,但它可以让你轻松地将你的逻辑分成更小、更专注的块。当组件开始变得复杂时,这是最易读的。


简洁易读(但危险)

render: function() {
    var personName = ...; // present or absent name, but never ""
    return (
       <div className="component">
          {personName && (
            <div className="person">{personName}</div>
          )}
       </div>
    );
}

如果被测试的变量可能是假值,比如0"" 或者 false,那么这种语法可能会非常危险。特别是对于数字,如果你想要确保它在 0 情况下被渲染,你应该稍微修改一下测试方式:

render: function() {
    var counter= ...; // number, can be 0
    return (
       <div className="component">
          {(typeof counter !== 'undefined') && (
            <div className="counter">{counter}</div>
          )}
       </div>
    );
}

当组件变得复杂时,将其拆分为多个较小的组件,并使用代码风格约定可以帮助保持可读性:

render: function() {
    var person= ...; 
    var counter= ...; 
    return (
       <div className="component">
          {person && (
            <Person person={person}/>
          )}
          {(typeof counter !== 'undefined') && (
            <Counter value={counter}/>
          )}
       </div>
    );
}

现代语法(但为时过早)

使用具有函数式无状态组件的do符号表示法可以提高表达能力,同时保持易读性。您可以轻松地将一个大组件拆分成非常小而专注的组件,这些组件使用do符号表示法:

const Users = ({users}) => (
  <div>
    {users.map(user =>
      <User key={user.id} user={user}/>
    )}
  </div>
)  

const UserList = ({users}) => do {
  if (!users) <div>Loading</div>
  else if (!users.length) <div>Empty</div>
  else <Users users={users}/>
}

它有点像在JSX中使用模块模式,所以非常灵活,但却少了很多样板代码。

为了启用此功能,您需要ES7阶段0编译工具,并且您喜欢的IDE可能尚未支持。


5
迄今为止我读过的最准确的答案,因为它非常习惯用语。好主意。 - sospedra
我同意这绝对是漂亮而干净的...并且添加更多的功能可以提高可读性,并有助于调用堆栈跟踪和转储! - Brad Parks
我发现这种方法对于除了最简单的条件检查以外的任何事情都非常理想,而且在选择许多不同的React组件(即实现switch语句)时非常好用。 - gbmhunter
1
这个ES7的例子真的很酷!你可以通过删除do并添加三个return语句来使用相同的模式与ES6。 - styfle

16
您可以像这样使用内联条件:

{true && (
<div>render item</div>
)}

{false && (
<div>don't render item</div>
)}

或者您可以直接使用一个变量

var something;

if (true) {
something = <div>render item</div>
}

{something}

9

您可以使用自执行函数,而且可以正确设置上下文(虽然需要Babel的帮助)!我更喜欢这种方式,因为不需要分配变量,而且您可以尽可能复杂(但出于可维护性的考虑,您可能不应该这么做):

render() {
  return (
    <div>
      <div>Hello!</div>
      {() => {
        if (this.props.isLoggedIn) {
          return <Dashboard />
        } else {
          return <SignIn />
        }
      }()}
      <div>Another Tag</div>
    </div>
  );
}

6

实验性的 ES7 do 语法对于这种情况非常方便。如果您正在使用 Babel,请启用 es7.doExpressions 功能,然后:

render() {
  var condition = false;

  return (
    <div>
      {do {
        if (condition) {
          <span>Truthy!</span>;
        } else {
          <span>Not truthy.</span>;
        }
      }}
    </div>
  );
}

请查看http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions,该页面涉及到IT技术相关内容。

3
我认为你的主要问题不是“我能实现一个< strong >If< /strong >组件”(因为显然你可以),而是“是否有任何理由实现一个< strong >If< /strong >组件是个坏主意? ”
主要原因是,添加组件会让现有组件中的代码量增加,从而增加了React的渲染周期。额外的组件包装器意味着React需要在其子组件上运行整个额外的渲染周期。如果你的应用程序到处都充斥着< strong >If< /strong >组件,那么你的UI速度就会慢下来。
类似于如何:< code ><div> Hi </ div>比< code ><div><div> Hi </ div></ div>更有效率。

完美的答案。其他人都在炫耀他们的React技能。 - hannad rehman

2

ES7的闭包在这方面非常好用:

例如,如果您正在使用webpack - 您可以添加ES7功能:

首先使用以下命令安装stage-0功能:

npm install babel-preset-stage-0 -save-dev;

然后在webpack的加载器设置中,添加stage-0 babel预设:

    loaders : [
        //...
        {
            test : /\.js$/,
            loader : "babel-loader",
            exclude : /node_modules/,
            query : {
                presets : ["react", "stage-0", "es2015"]
            }
        }
    ]

那么你的组件渲染函数可以如下所示:
import React , { Component } from "react";

class ComponentWithIfs extends Component {

    render() {
        return (
            <div className="my-element">
                {
                    do {
                        if ( this.state.something ) {
                            <div>First Case</div>
                        } else {
                            <div>2nd Case</div>
                        }
                    }
                }
            </div>
        );
    }
}

2
我认为这取决于具体情况。我刚遇到了同样的问题,不知道自己为什么在这里。我认为,如果要渲染的组件很大,使用IF组件而不是标准的条件渲染方法可能会更有用。"最初的回答"
...
<IF condition={screenIs("xs")}>
       <div> Then statement</div>
       <div>other Statement</div>
       <div>and so on</div>
</IF>
...

is definitively more readable than

...
{
  screenIs("xs") &&  (
     <Fragment>
         <div> Then statement</div>
         <div>other Statement</div>
         <div>and so on</div>
      </Fragment>
    )
 }
 ...

最初的回答:还适用于三元语句,
...
<IF.Ternary condition={screenIs("xs")}>
   <Fragment>
       <div> Then statement</div>
       <div>other then Statement</div>
       <div>and so on</div>
   </Fragment>
   <Fragment>
       <div> Else statement</div>
       <div>other Else Statement</div>
       <div>and so on</div>
   </Fragment>
 </IF.Ternary>
 ...

"更易读"比"最初的回答"更通俗易懂。
...
{
  screenIs("xs")
   ? (
     <Fragment>
         <div> Then statement</div>
         <div>other Then Statement</div>
         <div>and so on</div>
      </Fragment>
    )
    : (
     <Fragment>
         <div> Else statement</div>
         <div>other Else Statement</div>
         <div>and so on</div>
      </Fragment>
    )
 }
 ...

无论如何,为了使其更易读,您可以在某个方法中定义您的条件渲染部分,并在主要的渲染函数中调用它。最初的回答。
{
  ...
  renderParts = () => {
    return screenIs('xs') ? <div>XS</div> : <div>Not XS</div>
  }
  ...
  render() {
    return (
      ...
      {this.renderParts()}
      ...
    )
  }
 }

1

如果有人感兴趣,我刚发布了一个React模块来实现这些目的:

var Node = require('react-if-comp');
...
React.createClass({
    render: function() {
        return (
            <div>
                <Node if={false} else={<div>Never showing false item</div>} />
                <Node if={true} then={<div>Showing true item</div>} />
            </div>
        );
    }
});

1

我会尽量避免使用“模板”来进行条件渲染。在React和JSX中,您可以使用完整的JavaScript功能来实现条件渲染

  • if-else
  • 三目运算符
  • 逻辑运算符&&
  • switch case
  • 枚举

这就是使JSX渲染变得如此强大的原因。否则,人们可能需要再次发明自己的模板语言,这些语言是特定于库/框架的,例如Angular 1/2中所使用的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接