React-如何渲染嵌套组件而不是[object Object]?

时间:2018-09-20 02:34:05

标签: javascript reactjs

我有一个使用AccordionAccordionItemLink的问答题。 除了Link以外,其他所有东西都运行良好,当在小提琴中的手风琴组件行50之外时,它可以完美呈现,但是当它嵌套在小提琴中的sampleQuestions > question1 > answer,第59行中时,则无法正常工作。

它将答案呈现为: Ottawa baby!! Check [object Object] for more details.

代替所需的链接:Ottawa baby!! Check wikipedia link b for more details.

这里是参考代码,但是我建议直接跳到代码下面的小提琴,单击第一个问题,然后第一手看到问题。

class Link extends React.Component {
  render() {
    return (
        <span onClick={this.props.onClick} className="link">{this.props.linkTitle}</span>
    );
  }
}

class AccordionItem extends React.Component {
  constructor() {
    super();
    this.state = {
      active: false
    };
    this.toggle = this.toggle.bind(this);
  }
  toggle() {
    this.setState({
      active: !this.state.active,
      className: "active"
    });
  }
  render() {
    const activeClass = this.state.active ? "active" : "inactive";
    const question = this.props.details;
    return (
            <div className={activeClass} onClick={this.toggle}>
              <span className="summary">{question.summary}</span>
              <span className="folding-pannel answer">{question.answer}</span>
            </div>
    );
  }
}

class Accordion extends React.Component {
  constructor() {
    super();
    this.state = {
      questions: sampleQuestions,
    };
    this.renderQuestion = this.renderQuestion.bind(this);
  }
  renderQuestion(key) {
    return <AccordionItem key={key} index={key} details={this.state.questions[key]} />
  }
  render() {
    return(
      <div className="mainbody">
        <h1>What is...</h1>
        <Link onClick={() => alert('outside link works')} linkTitle={'wikipedia link a'} />
        <div className="accordion-container">
          {Object.keys(this.state.questions).map(this.renderQuestion)}
        </div>
      </div>    
    )
  }
}
const sampleQuestions = {
  question1: {summary:'the capital of Canada?', answer:'Ottawa baby!! Check ' + <Link onClick={() => alert('trying to get this nested link to show')} linkTitle={'wikipedia link b'} /> + ' for more details.'},
  question2: {summary:'the life span of a bowhead whale?', answer:'Over 200 years!!'},
  question3: {summary:'the most visited city in the world?', answer:'London, groovy baby!!'},
  question4: {summary:'the warmest ocean?', answer:'Indian Ocean, it\'s a hottie!'},
  question5: {summary:'the one thing ron swanson hates more than lying?', answer:'Skim milk, which is water that\'s lying about being milk'}
};
ReactDOM.render(
  <Accordion />,
  document.getElementById('accordion')
);

Here is the fiddle

是否知道如何获取[object Object]来呈现第一个问题的答案所需的Link分量?

3 个答案:

答案 0 :(得分:1)

这是一种实现方法:利用React Fragment。

工作示例:https://codesandbox.io/s/92r12m7zp

public / index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <pattern id="pattern" x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
            <rect fill="rgba(159, 188, 191, 0.15)" x="0" width="20" height="20" y="0" />
            <rect fill="rgba(159, 188, 191, 0.15)" x="20" width="20" height="20" y="20" />
        </pattern>
        <rect fill="url(#pattern)" x="0" y="0" width="100%" height="100%" />
    </svg>
    <div id="accordion"></div>
</body>

</html>

index.js

import React from "react";
import { render } from "react-dom";
import Accordian from "./Accordian";
import "./styles.css";

render(<Accordian />, document.getElementById("accordion"));

Accordian.js

import map from "lodash/map";
import React, { Component } from "react";
import AccordionItem from "./AccordianItem";
import SampleQuestions from "./sampleQuestions";

export default class Accordion extends Component {
  state = { questions: SampleQuestions };

  render = () => (
    <div className="mainbody">
      <h1>What is...</h1>
      <div className="accordion-container">
        {map(this.state.questions, ({ key, ...rest }) => (
          <AccordionItem key={key} {...rest} />
        ))}
      </div>
    </div>
  );
}

AccordianItem.js

import React, { Component } from "react";

export default class AccordionItem extends Component {
  state = { isActive: false };

  toggle = () => this.setState(prevState => ({ isActive: !this.state.isActive }));

  render = () => (
    <div
      className={`${this.state.isActive ? "active" : "inactive"}`}
      onClick={this.toggle}
    >
      <span className="summary">&#62; {this.props.summary}</span>
      <span className="folding-pannel answer">
        {this.props.answer}
      </span>
    </div>
  );
}

sampleQuestions.js

import React, { Fragment } from "react";

const Link = url => (
  <a href={url} target="_blank">
    here
  </a>
);

export default [
  {
    key: "capital-of-canada",
    summary: "the capital of Canada?",
    answer: (
      <Fragment>
        Ottawa baby!! Click {Link("https://en.wikipedia.org/wiki/Ottawa")} for
        more details
      </Fragment>
    )
  },
  {
    key: "whale-lifespan",
    summary: "the life span of a bowhead whale?",
    answer: "Over 200 years!!"
  },
  {
    key: "most-popular-city",
    summary: "the most visited city in the world?",
    answer: "London, groovy baby!!"
  },
  {
    key: "warmest-ocean",
    summary: "the warmest ocean?",
    answer: "Indian Ocean, it's a hottie!"
  },
  {
    key: "swanson",
    summary: "the one thing ron swanson hates more than lying?",
    answer: "Skim milk, which is water that's lying about being milk"
  }
];

这是另一种实现方式:利用混合内容的数组。

工作示例:https://codesandbox.io/s/1v1xmq1kmq

public / index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <pattern id="pattern" x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
            <rect fill="rgba(159, 188, 191, 0.15)" x="0" width="20" height="20" y="0" />
            <rect fill="rgba(159, 188, 191, 0.15)" x="20" width="20" height="20" y="20" />
        </pattern>
        <rect fill="url(#pattern)" x="0" y="0" width="100%" height="100%" />
    </svg>
    <div id="accordion"></div>
</body>

</html>

index.js

import React from "react";
import { render } from "react-dom";
import Accordian from "./Accordian";
import "./styles.css";

render(<Accordian />, document.getElementById("accordion"));

Accordian.js

import map from "lodash/map";
import React, { Component } from "react";
import AccordionItem from "./AccordianItem";
import SampleQuestions from "./sampleQuestions";

export default class Accordion extends Component {
  state = { questions: SampleQuestions };

  render = () => (
    <div className="mainbody">
      <h1>What is...</h1>
      <div className="accordion-container">
        {map(this.state.questions, ({ key, ...rest }) => (
          <AccordionItem key={key} {...rest} />
        ))}
      </div>
    </div>
  );
}

AccordianItem.js

import each from "lodash/each";
import React, { Component, Fragment } from "react";
import uuid from "uuid/v5";

export default class AccordionItem extends Component {
  state = { isActive: false };

  toggle = () => this.setState(prevState => ({ isActive: !this.state.isActive }));

  render = () => (
    <div
      className={`${this.state.isActive ? "active" : "inactive"}`}
      onClick={this.toggle}
    >
      <span className="summary">&#62; {this.props.summary}</span>
      <span className="folding-pannel answer">
        {each(this.props.answer, prop => <Fragment key={uuid}>{prop}</Fragment>)}
      </span>
    </div>
  );
}

sampleQuestions.js

import React from "react";

const Link = url => (
  <a href={url} target="_blank">
    here
  </a>
);

export default [
  {
    key: "capital-of-canada",
    summary: "the capital of Canada?",
    answer: [
      "Ottawa baby!! Click ",
      Link("https://en.wikipedia.org/wiki/Ottawa"),
      " for more details"
    ]
  },
  {
    key: "whale-lifespan",
    summary: "the life span of a bowhead whale?",
    answer: ["Over 200 years!!"]
  },
  {
    key: "most-popular-city",
    summary: "the most visited city in the world?",
    answer: ["London, groovy baby!!"]
  },
  {
    key: "warmest-ocean",
    summary: "the warmest ocean?",
    answer: ["Indian Ocean, it's a hottie!"]
  },
  {
    key: "swanson",
    summary: "the one thing ron swanson hates more than lying?",
    answer: ["Skim milk, which is water that's lying about being milk"]
  }
];

这是另一种实现方式:将dangerouslySetInnerHTMLsanitize-html结合使用。

工作示例:https://codesandbox.io/s/0q1mv0omkw

public / index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <pattern id="pattern" x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
            <rect fill="rgba(159, 188, 191, 0.15)" x="0" width="20" height="20" y="0" />
            <rect fill="rgba(159, 188, 191, 0.15)" x="20" width="20" height="20" y="20" />
        </pattern>
        <rect fill="url(#pattern)" x="0" y="0" width="100%" height="100%" />
    </svg>
    <div id="accordion"></div>
</body>

</html>

index.js

import React from "react";
import { render } from "react-dom";
import Accordian from "./Accordian";
import "./styles.css";

render(<Accordian />, document.getElementById("accordion"));

Accordian.js

import map from "lodash/map";
import React, { Component } from "react";
import AccordionItem from "./AccordianItem";
import SampleQuestions from "./sampleQuestions";

export default class Accordion extends Component {
  state = { questions: SampleQuestions };

  render = () => (
    <div className="mainbody">
      <h1>What is...</h1>
      <div className="accordion-container">
        {map(this.state.questions, ({ key, ...rest }) => (
          <AccordionItem key={key} {...rest} />
        ))}
      </div>
    </div>
  );
}

AccordianItem.js

import React, { Component } from "react";
import sanitizeHtml from "sanitize-html";

export default class AccordionItem extends Component {
  state = { isActive: false };

  toggle = () => this.setState(prevState => ({ isActive: !this.state.isActive }));

  sanitize = ans =>
    sanitizeHtml(ans, {
      allowedTags: ["a"],
      allowedAttributes: {
        a: ["href", "target"]
      }
    });

  render = () => (
    <div
      className={`${this.state.isActive ? "active" : "inactive"}`}
      onClick={this.toggle}
    >
      <span className="summary">&#62; {this.props.summary}</span>
      <span
        className="folding-pannel answer"
        dangerouslySetInnerHTML={{
          __html: this.sanitize(this.props.answer)
        }}
      />
    </div>
  );
}

sampleQuestions.js

const Link = url => `<a href=${url} target="_blank">here</a>`;

export default [
  {
    key: "capital-of-canada",
    summary: "the capital of Canada?",
    answer: `Ottawa baby!! Click ${Link("https://en.wikipedia.org/wiki/Ottawa")} for more details`
  },
  {
    key: "whale-lifespan",
    summary: "the life span of a bowhead whale?",
    answer: "Over 200 years!!"
  },
  {
    key: "most-popular-city",
    summary: "the most visited city in the world?",
    answer: "London, groovy baby!!"
  },
  {
    key: "warmest-ocean",
    summary: "the warmest ocean?",
    answer: "Indian Ocean, it's a hottie!"
  },
  {
    key: "swanson",
    summary: "the one thing ron swanson hates more than lying?",
    answer: "Skim milk, which is water that's lying about being milk"
  }
];

答案 1 :(得分:1)

感谢所有回答的人,但是我发现这样做的方式不太麻烦。正如patrick在评论中所建议的那样,只需将答案包装在div中即可将答案转换为jsx而不是字符串。

这是现在的样子:

answer: <div>Ottawa baby!! Check <Link onClick={() => alert('trying to get this nested link to show')} linkTitle={'wikipedia link b'} /> for more details.</div>

这是最简单的解决方案,并且可以按预期获取要呈现的链接。

答案 2 :(得分:0)

通常,您不能真正将react组件放在字符串中。有多种方法可以做到这一点,即使用react-jsx-parser,但我们将不再赘述。

一种可能的解决方案是执行以下操作:设置一个哑子组件,该子组件呈现子级数组。

const AnswerWithLink = (children) => {
  return (
    <span>{[...children]}</span>
  )
}

然后在答案1中,将其作为函数调用,并将字符串的各个部分作为数组的元素传递:

question1: {
  summary:'the capital of Canada?', 
  answer: AnswerWithLink(['Ottawa baby!! Check ', Link({onClick: () => alert('trying to get this nested link to show'), linkTitle: 'wikipedia link b'}), ' for more details'])
}

我敢肯定,这样做有更有效的方法。

编辑:我还编辑了代码笔以使其正常工作。最终也将<Link />更改为愚蠢的组件,因为它是无状态的。