解析错误:相邻的JSX元素必须包含在一个封闭标签中。

582

我正在尝试设置我的React.js应用程序,以便仅在我设置的变量为true时才进行渲染。

我的渲染函数的设置方式如下:

render: function() {
    var text = this.state.submitted ? 'Thank you!  Expect a follow up at '+email+' soon!' : 'Enter your email to request early access:';
    var style = this.state.submitted ? {"backgroundColor": "rgba(26, 188, 156, 0.4)"} : {};
    return (
    <div>

if(this.state.submitted==false) 
{

      <input type="email" className="input_field" onChange={this._updateInputValue} ref="email" value={this.state.email} />

      <ReactCSSTransitionGroup transitionName="example" transitionAppear={true}>
      <div className="button-row">
         <a href="#" className="button" onClick={this.saveAndContinue}>Request Invite</a>
     </div>
     </ReactCSSTransitionGroup>
}
   </div>
    )
  },

基本上,这里重要的部分是if(this.state.submitted==false)(当提交变量设置为false时,我希望这些div元素显示出来)。

但是在运行此代码时,我遇到了问题:

Uncaught Error: Parse Error: Line 38: Adjacent JSX elements must be wrapped in an enclosing tag

问题出在哪里?我该如何改进呢?


3
这里的其他人只是告诉你使用一个父元素,但这可能是不必要的。你之前的问题版本有一个使用数组的有趣答案。 - Meow
16个回答

782
你应该将你的组件放在一个包含标签之间,这意味着:
// WRONG!

return (  
    <Comp1 />
    <Comp2 />
)

相反:

// Correct

return (
    <div>
       <Comp1 />
       <Comp2 />
    </div>
)

编辑:根据Joe Clay在片段API的评论。

// More Correct

return (
    <React.Fragment>
       <Comp1 />
       <Comp2 />
    </React.Fragment>
)

// Short syntax

return (
    <>
       <Comp1 />
       <Comp2 />
    </>
)

7
如果我想在表格中同时打印两行,该如何换行<tr>标签? - Jose
1
@Jose,你可以像答案中所示使用Fragments API,它们不会产生任何额外的DOM元素。 - jtsalva

296

虽然回答这个问题有点晚,但我认为这会增加解释的内容。

出现这种情况是因为你的代码中有一个地方同时返回了两个元素。

例如:

return(
    <div id="div1"></div>
    <div id="div1"></div>
  )

应该将其包装在一个父元素中。例如:

 return(
      <div id="parent">
        <div id="div1"></div>
        <div id="div1"></div>
      </div>
      )

更详细的说明

下面的jsx代码将被转换


class App extends React.Component {
  render(){
    return (
      <div>
        <h1>Welcome to React</h1>
      </div>
    );
  }
}

转换为这样

_createClass(App, [{
    key: 'render',
    value: function render() {
      return React.createElement(
        'div',
        null,
        React.createElement(
          'h1',
          null,
          'Welcome to React'
        )
      );
    }
  }]);

但如果您这样做

class App extends React.Component {
  render(){
    return (
        <h1>Welcome to React</h1>
        <div>Hi</div>
    );
  }
}

这将被转换成这样(仅为说明目的,实际上你将获得错误:相邻的JSX元素必须包含在封闭标记中

_createClass(App, [{
    key: 'render',
    value: function render() {
      return React.createElement(
        'div',
        null,
       'Hi'
      ); 
    return React.createElement(
          'h1',
          null,
          'Welcome to React'
        )
    }
  }]);

在上面的代码中,你可以看到试图从一个方法调用中两次返回,这显然是错误的。

编辑- React 16及以上的最新更改:

如果你不想添加额外的div来包装并且想要返回多个子组件,则可以使用React.Fragments

React.Fragments<React.Fragments>)速度略快,并且内存使用较少(无需创建额外的DOM节点,DOM树更简洁)。

例如:(在React 16.2.0中)

render() {
  return (
    <>
       React fragments.
      <h2>A heading</h2>
      More React fragments.
      <h2>Another heading</h2>
      Even more React fragments.
    </>
  );
}

或者

render() {
  return (
    <React.Fragments>
       React fragments.
      <h2>A heading</h2>
      More React fragments.
      <h2>Another heading</h2>
      Even more React fragments.
    </React.Fragments>
  );
}

或者

render() {
 return [
  "Some text.",
  <h2 key="heading-1">A heading</h2>,
  "More text.",
  <h2 key="heading-2">Another heading</h2>,
  "Even more text."
 ];
}

122

React元素必须仅返回一个元素。您需要将两个标签都用另一个元素标签包装起来。

我还可以看到您的渲染函数没有返回任何内容。您的组件应该像这样:

var app = React.createClass({
    render () {
        /*React element can only return one element*/
        return (
             <div></div>
        )
    }
})

请注意,您无法在返回的元素内部使用if语句:

render: function() {
var text = this.state.submitted ? 'Thank you!  Expect a follow up at '+email+' soon!' : 'Enter your email to request early access:';
var style = this.state.submitted ? {"backgroundColor": "rgba(26, 188, 156, 0.4)"} : {};
    if(this.state.submitted==false) {
        return <YourJSX />
    } else {
        return <YourOtherJSX />
    }
},

这并没有解决“if”的问题;如果我在渲染函数中删除“if”,它就可以正常工作。 - user1072337
1
请注意,您不能在返回的元素内使用if语句。请查看我的更新答案。 - Matan Gubkin

102

如果您不想像其他答案建议的那样将其包装在另一个 div 中,您也可以将其包装在数组中,它也可以正常工作。

// Wrong!
return (  
   <Comp1 />
   <Comp2 />
)

它可以写成:

// Correct!
return (  
    [<Comp1 />,
    <Comp2 />]
)
请注意,上述代码会生成一个警告:Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of 'YourComponent'. 如果手动添加组件,则可以通过添加key属性来解决此问题,添加方式如下:
return (  
    [<Comp1 key="0" />,
    <Comp2 key="1" />]
)

这里是关于键的更多信息:组合 vs 继承


7
我试过这个,但它给了我一个错误。必须返回一个有效的React元素(或null)。你可能已经返回了undefined、数组或一些其他无效的对象。 - prasadmsvs
3
@prasadmsvs +1 invariant.js:39 抛出不变式错误:CommitFilter.render(): 必须返回有效的React组件。你可能已经返回了undefined、数组或其他无效的对象。 - Svetlana Ivanova
2
@aaaaaa,由于当前React协调器的工作方式,这是不可能的。它是一个堆栈,协调是递归完成的。在React 16中,这个问题已经得到解决,现在可以返回一个数组。 - natnai
1
https://github.com/iamdustan/tiny-react-renderer 是一个优秀的仓库,每个 React 开发者都应该阅读。一旦你阅读了它,你就会立即明白为什么当前的 React 实现不允许返回多个子元素。 - natnai
1
相邻的JSX标签被解析为一个数组 - 这是非常正常的。 OP之所以会出错,是因为他的if()块打断了JSX解析器,导致它期望一个单一的外部元素。实际上,将if()块封装在返回数组的帮助函数中是更优雅的解决问题的方式。这适用于React <=15。 React 16只需消除样板文件方括号和逗号的需要即可。 - pscl
显示剩余11条评论

51

问题

解析错误:相邻的JSX元素必须包装在一个封闭的标签中

这意味着您正在以不正确的方式返回多个兄弟JSX元素。请记住,您不是编写HTML,而是JSX!您的代码将从JSX转译为JavaScript。例如:

render() {
  return (<p>foo bar</p>);
}

将被转译为:

render() {
  return React.createElement("p", null, "foo bar");
}

除非你是新手,否则你已经知道函数/方法(任何语言)可以接受任意数量的参数,但总是只返回一个值。鉴于此,你可能会发现,在尝试基于createElement()的工作原理返回多个兄弟组件时会遇到问题;它只接受一个元素的参数并返回该元素。因此,我们无法从一个函数调用中返回多个元素。


因此,如果你曾经想过为什么这样可以运行...

render() {
  return (
    <div>
      <p>foo</p>
      <p>bar</p>
      <p>baz</p>
    </div>
  );
}

但不是这个...

render() {
  return (
    <p>foo</p>
    <p>bar</p>
    <p>baz</p>
  );
}

这是因为在第一个片段中,两个<p>元素都是<div>元素的children。当它们是children时,我们可以表示无数个兄弟元素。看看这样会转换成什么:

render() {
  return React.createElement(
    "div",
    null,
    React.createElement("p", null, "foo"),
    React.createElement("p", null, "bar"),
    React.createElement("p", null, "baz"),
  );
}

解决方案

根据你运行的React版本,有几种解决方法:

  • 使用片段(仅适用于React v16.2+)

    从React v16.2开始,React支持片段,它是一个没有节点的组件,直接返回其子项。

    在数组中返回子项(参见下文)存在一些缺点:

    • 数组中的子项必须用逗号分隔。
    • 数组中的子项必须具有键来防止React的键警告。
    • 字符串必须用引号括起来。

    这些问题都可以通过使用片段来解决。以下是包含在片段中的子项示例:

render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

转换后为:

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

请注意,第一个代码片段需要Babel v7.0或更高版本。


  • 返回一个数组(仅适用于React v16.0+!)

    从React v16开始,React组件可以返回数组。这与React的早期版本不同,早期版本中你被迫将所有兄弟组件包装在父组件中。

    换句话说,现在你可以这样做:

    render() {
      return [<p key={0}>foo</p>, <p key={1}>bar</p>];
    }
    

    这将被转译为:

    return [React.createElement("p", {key: 0}, "foo"), React.createElement("p", {key: 1}, "bar")];
    

    请注意,上述代码返回的是一个数组。从React 16版本开始,数组是有效的React元素。但在早期版本的React中,数组不是有效的返回对象!

    还要注意,以下代码是无效的(必须返回一个数组):

    render() {
      return (<p>foo</p> <p>bar</p>);
    }
    

  • 将元素放入父元素中

    另一种解决方案是创建一个父组件,用它的children包裹兄弟组件。这是目前解决此问题最常见的方法,在所有 React 版本中都适用。

    render() {
      return (
        <div>
          <h1>foo</h1>
          <h2>bar</h2>
        </div>
      );
    }
    

    注意:请再次查看此答案顶部以获取更多细节和此代码的转译方式


  • @Grievoushead,不是针对组件的,只适用于儿童。 - Chris
    在 React v16.4 中,第一个示例不能使用 <>,只能使用 <React.Fragment> :( - vsync
    @vsync,对于该语法的支持取决于您的构建环境。不确定 Babel 是否已经支持它,如果支持,是哪个版本。 - Chris
    @Chris - 谢谢。我在 Codepen 上尝试了一下,使用了原生的 Babel 复选框。 - vsync

    26

    React 16.0.0可以将多个组件作为数组从渲染函数中返回。

    return ([
        <Comp1 />,
        <Comp2 />
    ]);
    

    React 16.2.0及以上版本中,我们可以在一个Fragment标签中从render函数返回多个组件。Fragment

    return (
    <React.Fragment>
        <Comp1 />
        <Comp2 />
    </React.Fragment>);
    

    React 16.2.0及以上版本,您可以使用这种简写语法。(一些旧的工具版本不支持它,因此您可能需要显式编写<Fragment>,直到工具跟上步伐。)

    return (
    <>
        <Comp1 />
        <Comp2 />
    </>)
    

    1
    你在组件之间漏掉了一个逗号。这是一个数组,所以你需要分隔其中的每个元素。 - Chris
    你的链接中说,应该使用 <React.Fragment> 而不是 <Fragment> - vsync
    2
    如果你在解构 import React { Fragment } from 'react';,那么你可以像这样使用它:<Fragment> - Morris S

    7
    如果您不包装组件,则可以按照下面的方法编写它。
    而不是:
    return(
      <Comp1 />
      <Comp2 />
         );
    

    你可以写这样的代码:
    return[(
     <Comp1 />
    ),
    (
    <Comp2 />
    ) ];
    

    7

    非常简单,我们可以使用父元素 div 来包装所有的元素, 或者我们可以使用高阶组件(HOC)的概念,这对于 react js 应用程序非常有用。

    render() {
      return (
        <div>
          <div>foo</div>
          <div>bar</div>
        </div>
      );
    }
    

    另一种最好的方法是使用HOC,它非常简单而不是很复杂, 只需在您的项目中添加一个hoc.js文件,并简单地添加这些代码即可

    const aux = (props) => props.children;
    export default aux;
    

    现在导入 hoc.js 文件到你想要使用的地方,现在我们可以用 hoc 包装而不是 div 元素。

    import React, { Component } from 'react';
    import Hoc from '../../../hoc';
    
        render() {
          return (
        <Hoc>
            <div>foo</div>
            <div>bar</div>
        </Hoc>
          );
        }
    

    虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文会提高答案的长期价值。阅读此内容 - Shanteshwar Inde
    你所谈论的功能,在ReactJS中被称为HOC,意味着props.children。 - Fazal

    4
    在React中有一个规则,即JSX表达式必须具有恰好一个最外层元素。
    错误的。
    const para = (
        <p></p>
        <p></p>
    );
    

    正确

    const para = (
        <div>
            <p></p>
            <p></p>
        </div>
    );
    

    1

    React 16返回一个数组,因此应该将其包装在一个元素(如div)中。

    错误的方法

    render(){
        return(
        <input type="text" value="" onChange={this.handleChange} />
    
         <button className="btn btn-primary" onClick=   {()=>this.addTodo(this.state.value)}>Submit</button>
    
        );
    }
    

    正确的做法(所有元素放在一个div或其他你正在使用的元素中)

    render(){
        return(
            <div>
                <input type="text" value="" onChange={this.handleChange} />
    
                <button className="btn btn-primary" onClick={()=>this.addTodo(this.state.value)}>Submit</button>
            </div>
        );
    }
    

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