【问题标题】:How can I pass the original JSX markup to a component for display in the page as code?如何将原始 JSX 标记传递给组件以在页面中显示为代码?
【发布时间】:2019-04-25 15:02:12
【问题描述】:

对于我正在创建的自我记录风格指南,我有一个接受childrenCodeSample 组件。我需要将childrenexact 字符串呈现为字符串。

<CodeSample language="tsx">
  <div className="some-thing">
    <Icon name="some-icon" />
  </div>
</CodeSample>

当然,在我的组件中,我可以轻松地渲染孩子,但我还需要显示孩子的原始/原始 JSX。我尝试过ReactDOMServer.renderToString(...),但这包括许多不同的属性,并且还重新格式化了原始 JSX。我也尝试过jsx-to-string 包,但这也没有给我准确/原始/原始的 JSX。

或者我可以向CodeSample 发送一个字符串并将其用作原始示例文本,并将该字符串呈现为实际示例组件。我尝试使用 ReactDomServer.renderToStringrenderToStaticMarkup 来完成此操作,但在这两种情况下,输出都不是原始 JSX。

明确地说,首选方法是使用如上所示的 HTML,并能够将原始 children 捕获为字符串并将其用作代码示例。

我尝试了this solution(进行了一些调整),但它没有给我原始/原始 JSX,它包含额外的道具,如果我为导入设置别名,它使用别名的原始名称。

示例

原始片段 (JSX)

<div className="input-group">
  <div className="input-group-prepend">
    <span className="input-group-text">
      <FA icon={faSearch} />
    </span>
  </div>
  <input type="text" className="form-control"/>
</div>

预期结果(文本)

(和原版一模一样)

实际结果

<div className="input-group">
   <div className="input-group-prepend">
     <span className="input-group-text">
       <FontAwesomeIcon icon={{prefix:fas,iconName:search,icon:[512,512,[],f002,M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z]}} border="false" className="" mask="null" fixedWidth="false" inverse="false" flip="null" listItem="false" pull="null" pulse="false" rotation="null" size="null" spin="false" symbol="false" title="" transform="null" />
     </span>
   </div><input type="text" className="form-control" />
 </div>

【问题讨论】:

  • 有趣。我在这里更新了标题。在我阅读这篇文章之前,我并不清楚这是关于什么的。祝你好运。
  • 但这包括许多不同的属性,并且还重新格式化了原始 JSX - 你能详细说明一下吗?到底有什么不同?
  • 只需在构建期间创建任务以将组件保存为 .txt 并为其使用正确的加载器。
  • 所以澄清一下,你想渲染CodeSample 的孩子并且有原始的JSX sn-p 可用吗?有一些示例输出(在 HTML 中)可能会帮助人们更好地理解这一点
  • @estus -- 添加示例。

标签: javascript reactjs


【解决方案1】:

感谢您的所有意见。我意识到这是一个不同类型的问题,没有直接的解决方案。最后,这就是我所做的......

更新CodeSample 组件

组件在挂载期间获取原始样本。

app/CodeSample.tsx

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Prism } from 'react-syntax-highlighter';
import { atomDark as style } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { getSampleTextRequest } from './duck/actions';
import { sampleTextSelector } from './duck/selectors';
import { AppState } from './duck/state';

export class CodeSample extends Component<Props> {
  componentDidMount() {
    this.props.getSampleText(this.props.samplePath);
  }

  render() {
    const { sampleText } = this.props;

    if (!sampleText) {
      return <em>Loading sample...</em>;
    } else {
      const { children } = this.props;

      if (children) {
        return (
          <div className="row">
            <div className="col col-xs-12 col-md-6">{this.renderCodeSample()}</div>

            <div className="col col-xs-12 col-md-6 mt-2">
              <div className="card">
                <div className="card-body">{children}</div>
              </div>
            </div>
          </div>
        );
      } else {
        return this.renderCodeSample();
      }
    }
  }

  renderCodeSample() {
    const { sampleText } = this.props;
    const language = this.getLanguage();

    return (
      <Prism {...{ language, style }} showLineNumbers={false}>
        {sampleText}
      </Prism>
    );
  }

  getLanguage() {
    const { language, samplePath: path } = this.props;

    return language || /\.(.+?)$/.exec(path)[1];
  }
}

type InjectedProps = {
  sampleText: string;
};

type InjectedActions = {
  getSampleText: typeof getSampleTextRequest;
};

type MyProps = {
  samplePath: string;
  language?: string;
};

type Props = InjectedProps & InjectedActions & MyProps;

const mapStateToProps = (state: AppState, props: MyProps) => ({
  sampleText: sampleTextSelector(state, props.samplePath),
});

const mapDispatchToProps = {
  getSampleText: getSampleTextRequest,
};

export default connect<InjectedProps, InjectedActions, MyProps, AppState>(mapStateToProps, mapDispatchToProps)(CodeSample);

为每个代码示例创建一个实际组件

当它被提取时,我会预处理原始代码并忽略任何不以空格开头的行。这仅给了我导出函数中的示例。

插件/插件按钮.tsx

import React from 'react';
import { FontAwesomeIcon as FA } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';

export default () => (
  <div className="input-group">
    <input type="text" className="form-control" />
    <div className="input-group-append">
      <button className="btn btn-default">
        <FA icon={faSearch} />
      </button>
    </div>
  </div>
);

直接在容器中导入并渲染这个组件,在CodeSample

插件/index.ts

对于每个示例,以“正常方式”以及file-loader 导入组件,这为我提供了组件源的相对路径。我给CodeSample 实例提供原始样本的相对路径,然后将实际组件渲染为其子组件。

import React from 'react';
import CodeSample from '../../app/CodeSample';
import AddonButton from './addon-button';
import AddonButtonSample from '!!file-loader!./addon-button.tsx';

export default () => (
  <div>
    <a id="addon" />
    <h2>Addons</h2>
    <p>An addon is used to attach an icon, button, etc. to an input.</p>

    ...

    <h3>Buttons</h3>
    <CodeSample samplePath={AddonButtonSample}>
      <AddonButton />
    </CodeSample>
  </div>
);

这是最终结果

【讨论】:

    【解决方案2】:

    可能有图书馆可以帮助解决这类问题。例如。 https://github.com/scniro/react-codemirror2

    【讨论】:

    • 那是一个代码编辑器,我只需要渲染一个组件以及原始/原始组件 JSX。
    【解决方案3】:

    JSX 不存储在您的 Javascript 中,而是存储为一组 React.createElement 命令。

    所以一个想法是拦截 React.createElement,并获取值。

    然后逆向生成一种 HTML。

    下面是一个例子,它不完整,但你也许可以增强它等等。

    当然它并不完美,因为某些信息会丢失,例如。 {2+xyz},被转换为 9,因为它已经在编译时进行了评估。

    const ret = [];
    const React = {createElement: (...args) => { return args}};
    
    const xyz = 7;
    
    const jsx = <div>
      <div>hello <b>there {2+xyz}</b></div>
      <span>span content {() => "abc"}</span>
    </div>;
      
    
    function render(jsx, indent = '') {
      let ret = '';
      ret += `${indent}<${jsx[0]}>`;
      for (let l = 2; l < jsx.length; l += 1) {
        const v = jsx[l];
        if (Array.isArray(v)) ret += '\r\n' + render(v, indent + '  ')
        else if (typeof v === 'function') ret += `{${v}}`;
        else { ret += v;  }
      }
      ret += `${indent}</${jsx[0]}>\r\n`;
      return ret;
    }
    
    console.log(render(jsx));

    【讨论】:

      【解决方案4】:

      由于您的 JSX 被编译成 JavaScript 函数,您需要在构建时执行您的步骤,或者使用运行时 JSX 转换器。

      对于构建时间步骤,您可以编写自己的 babel 转换并将其与自定义组件相结合:

      import React, {Component} from "react"
      
      class FA extends Component {
        render () {
          return <div>Hi there {this.props.icon}!</div>
        }
      }
      
      // this could be something exported by a preval macro
      class CodeSnippet extends Component {
        render () {
          return <>
            {this.props.snippet}
            {this.props.children}
          </>
        }
      }
      
      <
        CodeSnippet
      attr="1"
      >
        <div className="input-group">
        <div className="input-group-prepend">
          <span className="input-group-text">
            <FA icon={faSearch} />
          </span>
        </div>
        <input type="text" className="form-control"/>
      </div>
      </CodeSnippet>
      

      还有变换:

      export default function (babel) {
        const { types: t } = babel;
      
        return {
          visitor: {
            JSXIdentifier(path, file) {
              if (path.node.name === "CodeSnippet" && path.container.type === "JSXOpeningElement") {
                const { start, end } = path.parentPath.container
      
                // we "slice out" the contents of our element, storing them as a string
                const contents = file.file.code.slice(path.parentPath.container.openingElement.end, path.parentPath.container.closingElement.start)
                const contentsEle = t.JSXText(contents)
                // and passing that string to our element as a prop for use later
                path.parentPath.container.openingElement.attributes.push(
                  t.JSXAttribute(t.JSXIdentifier("snippet"), t.stringLiteral(contents))
                )
              }
            }
          }
        };
      }
      

      导致:

      <
      CodeSnippet
      attr="1" snippet="\n  <div className=\"input-group\">\n  <div className=\"input-group-prepend\">\n    <span className=\"input-group-text\">\n      <FA icon={faSearch} />\n    </span>\n  </div>\n  <input type=\"text\" className=\"form-control\"/>\n</div>\n">
      
        <div className="input-group">
        <div className="input-group-prepend">
          <span className="input-group-text">
            <FA icon={faSearch} />
          </span>
        </div>
        <input type="text" className="form-control" />
        </div>
      </CodeSnippet>;
      

      React 像往常一样渲染出来:

      这是astexplorer.net上的一个可播放示例

      【讨论】:

        猜你喜欢
        • 2011-10-01
        • 2012-12-29
        • 1970-01-01
        • 2021-10-26
        • 1970-01-01
        • 2015-07-27
        • 1970-01-01
        • 2018-11-20
        相关资源
        最近更新 更多