在同构React应用程序中呈现HTML字符串 [英] Render HTML string in isomorphic React app

查看:77
本文介绍了在同构React应用程序中呈现HTML字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

非SPA场景使用已清理但随机的HTML字符串作为输入:

There is non-SPA scenario with sanitized yet random HTML string as an input:

<p>...</p>
<p>...</p>
<gallery image-ids=""/>
<player video-id="..."/>
<p>...</p>

该字符串源自WYSIWYG编辑器,包含嵌套的常规HTML标记和有限数量的自定义元素(组件应该呈现给小部件。

The string originates from WYSIWYG editor and contains nested regular HTML tags and a limited number of custom elements (components) that should be rendered to widgets.

目前像这样的HTML片段应该分别在服务器端(Express)呈现,但最终也会在客户端呈现作为同构应用程序的一部分。

Currently HTML snippets like this one are supposed to be rendered on server side (Express) separately but will eventually be rendered on client side too as a part of isomorphic application.

我打算使用React(或类似React的框架)实现组件,因为它可能适合这种情况 - 它是同构的并且很好地渲染部分。

I intend use React (or React-like framework) to implement components because it presumably suits the case - it is isomorphic and renders partials well.

问题在于子串如

<gallery image-ids="[1, 3]"/>

应该成为

<Gallery imageIds={[1, 3]}/>

JSX / TSX组件在某些时候,我不确定什么是正确的做法这个,但我希望这是一个常见的任务。

JSX/TSX component at some point, and I'm not sure what is the right way to do this, but I would expect it to be a common task.

如何在React中解决这个问题?

How can this case be solved in React?

推荐答案

通过解析html字符串并将生成的节点转换为React元素,可以将已清理的HTML转换为可在服务器和客户端上运行的React组件。

Sanitized HTML can be turned into React Components that can be run both on server and client by parsing the html string and transforming the resulting nodes into React elements.

const React = require('react');
const ReactDOMServer = require('react-dom/server');

const str = `<div>divContent<p> para 1</p><p> para 2</p><gallery image-ids="" /><player video-id="" /><p> para 3</p><gallery image-ids="[1, 3]"/></div>`;


var parse = require('xml-parser');

const Gallery = () => React.createElement('div', null, 'Gallery comp');
const Player = () => React.createElement('div', null, 'Player comp');

const componentMap = {
  gallery: Gallery,
  player: Player
};


const traverse = (cur, props) => {
  return React.createElement(
    componentMap[cur.name] || cur.name,
    props,
    cur.children.length === 0 ? cur.content: Array.prototype.map.call(cur.children, (c, i) => traverse(c, { key: i }))
  );
};

const domTree = parse(str).root;
const App = traverse(
   domTree
);

console.log(
  ReactDOMServer.renderToString(
    App
  )
);

但是,请注意,正如您所提到的那样,您真正需要的不是JSX / TSX,而是一棵树React渲染器的React Nodes(本例中为ReactDOM)。 JSX只是语法糖,除非你想在代码库中维护React输出,否则无需来回转换它。

Note however, it is not JSX/TSX that you really need, as you mentioned, but a tree of React Nodes for the React renderer (ReactDOM in this case). JSX is just syntactic sugar, and transforming it back and forth is unnecessary unless you want to maintain the React output in your codebase.

请原谅简化的html解析。它仅用于说明目的。您可能希望使用更符合规范的库来解析输入html或适合您的用例的内容。

Pardon the over simplified html parsing. Its only for illustrative purposes. You might want to use a more spec-compliant library to parse the input html or something that fits your use case.

确保客户端包得到完全相同的 App 组件,否则你可能React的客户端脚本会重新创建DOM树,你将失去服务器端渲染的所有好处。

Make sure, the client side bundle get the exact same App component, or else you might React's client side script would re-create the DOM tree and you'll lose all the benefits of server side rendering.

使用上述方法,您也可以利用React 16的流量。

You can take advantage of the React 16's streaming out too with the above approach.

解决道具问题

道具将作为属性从树中获得,并且可以作为道具传递(仔细考虑您的使用案例)。

Props will be available to you from the tree as attributes and can be passed as props (on careful consideration of your use case ofcourse).

const React = require('react');
const ReactDOMServer = require('react-dom/server');

const str = `<div>divContent<p> para 1</p><p> para 2</p><gallery image-ids="" /><player video-id="" /><p> para 3</p><gallery image-ids="[1, 3]"/></div>`;


var parse = require('xml-parser');

const Gallery = props => React.createElement('div', null, `Gallery comp: Props ${JSON.stringify(props)}`);
const Player = () => React.createElement('div', null, 'Player comp');

const componentMap = {
  gallery: Gallery,
  player: Player
};

const attrsToProps = attributes => {
  return Object.keys(attributes).reduce((acc, k) => {

    let val;
    try {
      val = JSON.parse(attributes[k])
    } catch(e) {
      val = null;
    }

    return Object.assign(
      {},
      acc,
      { [ k.replace(/\-/g, '') ]: val }
    );
  }, {});
};


const traverse = (cur, props) => {

  const propsFromAttrs = attrsToProps(cur.attributes);
  const childrenNodes = Array.prototype.map.call(cur.children, (c, i) => {

    return traverse(
      c,
      Object.assign(
        {},
        {
          key: i
        }
      )
    );
  });

  return React.createElement(
    componentMap[cur.name] || cur.name,
      Object.assign(
        {},
        props,
        propsFromAttrs
      ),
    cur.children.length === 0 ? cur.content: childrenNodes
  );
};

const domTree = parse(str).root;
const App = traverse(
  domTree
);

console.log(
  ReactDOMServer.renderToString(
    App
  )
);

小心使用自定义属性 - 您可能想要关注此rfc 。如果可能,坚持使用camelCase。

Careful with custom attributes though - you might want to follow this rfc. Stick with camelCase if possible.

这篇关于在同构React应用程序中呈现HTML字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆