簡體   English   中英

在同構React應用程序中呈現HTML字符串

[英]Render HTML string in isomorphic React app

存在非SPA場景,其中使用已清理但隨機的HTML字符串作為輸入:

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

該字符串源自WYSIWYG編輯器,包含嵌套的常規HTML標記和應呈現給窗口小部件的有限數量的自定義元素(組件)。

目前像這樣的HTML片段應該分別在服務器端(Express)上呈現,但最終也將作為同構應用程序的一部分在客戶端呈現。

我打算使用React(或類似React的框架)來實現組件,因為它可能適合這種情況 - 它是同構的並且很好地呈現部分。

問題是子串就像

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

應該成為

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

JSX / TSX組件在某些時候,我不確定什么是正確的方法,但我希望它是一個常見的任務。

如何在React中解決這個問題?

通過解析html字符串並將結果節點轉換為React元素,可以將已清理的HTML轉換為可在服務器和客戶端上運行的React組件。

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輸出,否則不需要來回轉換它。

請原諒簡化的html解析。 它僅用於說明目的。 您可能希望使用更符合規范的庫來解析輸入html或適合您的用例的內容。

確保客戶端軟件包獲得完全相同的App組件,否則您可能React的客戶端腳本將重新創建DOM樹,您將失去服務器端呈現的所有好處。

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

解決道具問題

您可以從樹中獲取道具作為屬性,並且可以作為道具傳遞(仔細考慮您的使用案例)。

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。

您可以使用Babel的API將字符串轉換為可執行的JavaScript。

如果你<lovercase>自定義組件約定,你可以讓你的生活變得更輕松 ,因為在JSX中它們被視為DOM標簽,所以如果你可以讓你的用戶使用<Gallery>而不是<gallery>你將從很麻煩。

我為你創建了一個工作(但很難看)的CodeSandbox 我的想法是使用Babel將JSX編譯為代碼,然后評估該代碼。 但要小心,如果用戶可以編輯它,他們肯定會注入惡意代碼!

JS代碼:

import React from 'react'
import * as Babel from 'babel-standalone'
import { render } from 'react-dom'

console.clear()

const state = {
  code: `
  Hey!
  <Gallery hello="world" />
  Awesome!
`
}


const changeCode = (e) => {
  state.code = e.target.value
  compileCode()
  renderApp()
}

const compileCode = () => {
  const template = `
function _render (React, Gallery) {
  return (
    <div>
    ${state.code}
    </div>
  )
}
`
  state.error = ''
  try {
    const t = Babel.transform(template, {
      presets: ['react']
    })

    state.compiled = new Function(`return (${t.code}).apply(null, arguments);`)(React, Gallery)  
  } catch (err) {
    state.error = err.message
  }
}

const Gallery = ({ hello }) =>
  <div>Here be a gallery: {hello}</div>

const App = () => (
  <div>
    <textarea style={{ width: '100%', display: 'block' }} onChange={changeCode} rows={10} value={state.code}></textarea>
    <div style={{ backgroundColor: '#e0e9ef', padding: 10 }}>
    {state.error ? state.error : state.compiled}
    </div>
  </div>
)


const renderApp = () =>
  render(<App />, document.getElementById('root'));

compileCode()
renderApp()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM