2017-08-12 37 views
5

Jest scenariusz non-SPA z oczyszczone jeszcze losowy ciąg HTML jako wejście:renderowania HTML ciąg w izomorficzna React aplikację

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

Łańcuch pochodzi z edytora WYSIWYG i zawiera zagnieżdżone znaczniki regularne HTML i ograniczoną liczbę niestandardowe elementy (komponenty), które powinny być renderowane do widżetów.

Obecnie fragmenty kodu HTML, takie jak ten, mają być renderowane osobno po stronie serwera (Express), ale ostatecznie będą również renderowane po stronie klienta również jako część aplikacji izomorficznej.

Zamierzam użyć React (lub React-like framework) do implementacji komponentów, ponieważ przypuszczalnie pasuje to przypadku - jest izomorficzne i dobrze oddaje cząstki.

Problemem jest to, że jak podciągi

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

powinna stać

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

JSX/TSX w pewnym momencie, a nie jestem pewien, co jest właściwym sposobem, aby to zrobić, ale Spodziewałbym się, że to będzie typowe zadanie.

W jaki sposób można rozwiązać tę sprawę w React?

+0

Wydaje mi się, że pytasz, jak zamienić ciągi takie jak '' na komponent reagujący - nie brzmi to zbyt rozsądnie. Czy mogę wiedzieć, dlaczego? Czy możesz w razie potrzeby zmienić zdanie? –

+0

Pytanie już zawiera informacje o kontekście problemu. Łańcuch pochodzi z edytora WYSIWYG. – estus

+0

A chcesz to zmienić w komponent reagujący? Konwencjonalnie było odwrotnie - Components => (react + react-dom) => htmlString. Pomijając sprawę, o której wspomniałeś, jak sobie z tym radzisz? Czego nie rozumiem, to czy szukasz automatycznego sposobu na przekształcenie html wygenerowanego z WYSIWYG w React Components? Czy istnieje lista niestandardowych elementów, które chcesz przenieść do biblioteki React/React-like. –

Odpowiedz

5

Sanitized HTML można przekształcić React składniki, które mogą być uruchamiane zarówno na serwerze i kliencie przez parsowania ciąg HTML i przekształcenie uzyskanego węzły do ​​React elementy.

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 
) 
); 

Uwaga jednak, że nie jest JSX/TSX że naprawdę trzeba, jak wspomniano, ale drzewo React węzłów do renderowania React (ReactDOM w tym przypadku). JSX jest po prostu syntaktycznym cukrem, a przekształcanie go tam i z powrotem jest niepotrzebne, chyba że chcesz zachować wyjście React w twojej bazie kodów.

Ułagodzenie uproszczonego parsowania html. Jest to tylko w celach ilustracyjnych. Możesz użyć bardziej spec-zgodnej biblioteki, aby przeanalizować wejściowy html lub coś, co pasuje do twojego przypadku użycia.

Upewnij się, że pakiet po stronie klienta ma dokładnie ten sam komponent App, bo inaczej skrypt klienta po stronie React mógłby ponownie utworzyć drzewo DOM, a stracisz wszystkie zalety renderowania po stronie serwera.

Możesz skorzystać z React 16's streamingu również z powyższym podejściem.

Rozwiązanie problemu rekwizyty

Rekwizyty będzie dostępna do ciebie z drzewa jako atrybuty i mogą być przekazywane jako rekwizyty (na starannym rozważeniu przypadku użycia oczywiście).

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 
) 
); 

Ostrożnie z niestandardowymi atrybutami - możesz chcieć postępować zgodnie z this rfc. Trzymaj z camelCase, jeśli to możliwe.

+0

Podoba mi się ta odpowiedź lepiej niż moja własna.:) – Jeff

+0

Dziękuję :) Aktualizuję moją odpowiedź z rekwizytami –

+0

Dzięki, właśnie tego szukałem. To świetnie, że ograniczenie zestawu dozwolonych komponentów powoduje łatwiejsze przechodzenie. – estus

2

Możesz użyć API Babel, aby przekształcić ciąg w wykonywalny JavaScript.

Można uczynić swoje życie drogę łatwiejszą jeśli rowu konwencję komponentu <lovercase> niestandardowy, ponieważ w JSX są one traktowane jak znaczniki dom, więc jeśli można zrobić użytkownicy korzystają <Gallery> zamiast <gallery> można zapisać się od A dużo kłopotów.

Stworzyłem dla ciebie działający (ale brzydki) CodeSandbox. Chodzi o to, aby Babel skompilował JSX do kodowania, a następnie ocenił ten kod. Bądź jednak ostrożny, jeśli użytkownicy mogą to edytować, z pewnością mogą wstrzyknąć złośliwy kod!

kod 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() 
+0

Tak, wymaga to użycia eval, w związku z tym nie jest praktyczne wprowadzanie danych przez użytkownika w jego obecnej formie. Czy możesz wyjaśnić, co dokładnie masz na myśli mówiąc "łatwiejsze"? '' będzie ciągiem * w dowolny sposób, prawda? – estus

+0

Musiałbyś sprawdzić AST w przypadku wywołań funkcji lub coś w celu złagodzenia, tak naprawdę nie ma innego sposobu. O '', nie, JSX jest przekształcany w 'React.createElement (Gallery, ...)' podczas gdy '' jest przekształcany w 'React.createElement ('gallery', ...)'. – Jeff

+0

Nie widzę, jak mogę ułatwić sobie życie. Skompilowany HTML to ciąg, który został pobrany od strony klienta i zapisany w DB. To nie może być nic innego jak ciąg. Stąd pytanie. – estus