React: Como substituir partes de uma corda por um componente?

Criado em 12 mar. 2015  ·  40Comentários  ·  Fonte: facebook/react

Eu tenho este código que obviamente não está funcionando:

    _.each(matches, (match) ->
      string = string.replace(match, ->
        <span className="match" key={i++}>{match}</span>
      )
    )

Porque isso resultaria em uma string misturada com objetos. Eu sei mal. Mas como posso adicionar componentes React dentro de uma string? Tudo que eu quero é destacar partes de uma string com um componente react. Um caso difícil de resolver, eu acho.

Comentários muito úteis

Você pode usar a função split com um regex e, em seguida, substituir as partes capturadas, assim:

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Avise-me se não estiver claro.

Todos 40 comentários

algo assim deve funcionar:

const escapeRE = new RegExp(/([.*+?^=!:$(){}|[\]\/\\])/g)
const safeRE = (string) => {
  return string.replace(escapeRE, "\\$1")
}

class Hightlight extends Component {
  static propTypes = {
    match : PropTypes.string,
    string : PropTypes.string,
  }

  render() {
    return (
      <span
        dangerouslySetInnerHTML={{
          __html : string.replace(safeRE(this.props.match), "<strong className\"match\">$1</strong>")
        }} />
    )
  }
}

Sim, tenho pensado em abusar de dangerouslySetInnerHTML mas não gosto dessa ideia. Este método é classificado como perigoso por boas razões.

Não há como ensinar o React a analisar jsx dentro de uma string? Ter o React parse iE

var example = "this one word <span className="match" key={i++}>hello</span> is highlighted"

e retornar isso em um método de renderização?

@binarykitchen Você pode construir elementos React dinamicamente e isso é o que você precisa fazer aqui, você simplesmente não pode fazer isso apenas com a substituição da string.

@syranide Sim, eu já sei disso. Meu ponto é: este não seria um bom recurso JSX / React? Analise uma string e transforme todas as tags em componentes filho do React.

Você pode usar a função split com um regex e, em seguida, substituir as partes capturadas, assim:

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Avise-me se não estiver claro.

A análise de JSX em tempo de execução é lenta e propensa a erros, e pode facilmente ter implicações de segurança - não seria inerentemente mais seguro do que hazouslySetInnerHTML é.

isso pode ser um pouco problemático com a caixa de tipo

- mlb

Em 12 de março de 2015, às 21h37, Ben Alpert [email protected] escreveu:

Você pode usar a função de divisão com um regex e, em seguida, substituir as partes capturadas, assim:

var parts = "Eu sou uma vaca; vacas dizem muuuuuuuuuu.". split (/ (\ bmoo + \ b) / gi);
para (var i = 1; i <partes.comprimento; i + = 2) {
partes [i] = ;}Retorna

{partes}
;
Avise-me se não estiver claro.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

Também encontrei esse problema ao tentar destacar partes de uma string de maneira programática (ou seja, substituir por tags <span> ). Acabei criando um módulo baseado na solução do @spicyj acima. Para qualquer pessoa interessada: iansinnott / react-string-replace

@iansinnott Obrigado por compartilhar isso. Comecei a usá-lo e percebi que vou precisar da API String.prototype.replace (acesso necessário aos grupos de correspondência individuais para um regexp dentro da função de substituição). Percebi que a mudança será mais do que um simples patch (e exigirá uma mudança de API), então criei um novo repo: https://github.com/oztune/string-replace-to-array

Se react-string-replace não atender aos seus requisitos:

https://github.com/EfogDev/react-process-string

O seguinte funcionou para mim como uma forma de destacar palavras-chave dentro de uma string de texto, sem
perigosamente configurando HTML:

/**
 * Find and highlight relevant keywords within a block of text
 * <strong i="7">@param</strong>  {string} label - The text to parse
 * <strong i="8">@param</strong>  {string} value - The search keyword to highlight
 * <strong i="9">@return</strong> {object} A JSX object containing an array of alternating strings and JSX
 */
const formatLabel = (label, value) => {
  if (!value) {
    return label;
  }
  return (<span>
    { label.split(value)
      .reduce((prev, current, i) => {
        if (!i) {
          return [current];
        }
        return prev.concat(<b key={value + current}>{ value }</b>, current);
      }, [])
    }
  </span>);
};

formatLabel('Lorem ipsum dolor sit amet', 'dolor');
// <span>Lorem ipsum <b>dolor</b> sit amet</span>

Eu precisava de algo assim para menções que correspondessem a algum padrão para que pudesse envolver vários valores e fiz algumas alterações na solução @richardwestenra . Talvez seja útil para alguém.

/**
 * Find and highlight mention given a matching pattern within a block of text
 * <strong i="7">@param</strong> {string} text - The text to parse
 * <strong i="8">@param</strong> {array} values - Values to highlight
 * <strong i="9">@param</strong> {RegExp} regex - The search pattern to highlight 
 * <strong i="10">@return</strong> {object} A JSX object containing an array of alternating strings and JSX
 */
const formatMentionText = (text, values, regex) => { 
    if (!values.length)
        return text;

    return (<div>
        {text.split(regex)
            .reduce((prev, current, i) => {
                if (!i)
                    return [current];

                return prev.concat(
                    values.includes(current)  ?
                        <mark key={i + current}>
                            {current}
                        </mark>
                        : current
                );
            }, [])}
    </div>);
};

const text = 'Lorem ipsum dolor sit amet [[Jonh Doe]] and [[Jane Doe]]';
const values = ['Jonh Doe', 'Jane Doe'];
const reg = new RegExp(/\[\[(.*?)\]\]/); // Match text inside two square brackets

formatMentionText(text, values, reg);
// Lorem ipsum dolor sit amet <mark>Jonh Doe</mark> and <mark>Jane Doe</mark>

Eu tive um problema semelhante e resolvi com portais de reação, para encontrar facilmente o nó, substituí a string por um div e renderizei meu componente de reação usando createPortal nesse div.

@rmtngh Estou curioso para saber por que você precisa fazer isso. Parece que pode ser um exagero. Você está entrelaçando código de não-reação?

Com relação às soluções para esse problema, gostaria de recomendar novamente string-replace-to-array (sou o autor). Foi batalha e testado ao longo do tempo em vários ambientes. Também é muito pequeno (a biblioteca inteira é este arquivo https://github.com/oztune/string-replace-to-array/blob/master/string-replace-to-array.js).

Aqui está um exemplo de uso com regex:

import replace from 'string-replace-to-array'

// The API is designed to match the native 'String.replace',
// except it can handle non-string replacements.
replace(
  'Hello Hermione Granger...',
  /(Hermione) (Granger)/g,
  function (fullName, firstName, lastName, offset, string) {
    return <Person firstName={ firstName } lastName={ lastName } key={ offset } />
  }
)

// output: ['Hello ', <Person firstName="Hermione" lastName="Granger" key={ 0 } />, ...]

@oztune Obrigado pela sua resposta, Neste caso, tenho uma string de marcação html proveniente de um editor wysiwyg de um CMS e gostaria de colocar componentes react em certos lugares dentro dessa marcação.
Você acha que seria melhor usar uma biblioteca em vez de usar portais? Eu acho que o portal faz é apenas renderizar o componente dentro de outro nó em vez do nó mais próximo, o que não seria mais caro do que renderizar um componente normal e a única etapa extra é substituir a string que pode ser feita com substituição js nativa.
Estou perdendo alguma coisa? Haverá alguma vantagem em usar a biblioteca?

@rmtngh Eu diria que depende do que você está tentando fazer. Se você está apenas tentando polvilhar alguns componentes do React em diferentes áreas da sua árvore DOM, e a árvore ainda não está sendo renderizada com o React, os portais são o caminho a percorrer. O método que mencionei é mais útil quando as partes que você está tentando substituir já foram renderizadas dentro de um componente React.

Este é o código que retorna uma matriz de nós com texto e padrão:

const highlightPattern = (text, pattern) => {
  const splitText = text.split(pattern);

  if (splitText.length <= 1) {
    return text;
  }

  const matches = text.match(pattern);

  return splitText.reduce((arr, element, index) => (matches[index] ? [
    ...arr,
    element,
    <mark>
      {matches[index]}
    </mark>,
  ] : [...arr, element]), []);
};

Obrigado @wojtekmaj
Minha versão para grupos no padrão:

const replacePatternToComponent = (text, pattern, Component) => {
  const splitText = text.split(pattern);
  const matches = text.match(pattern);

  if (splitText.length <= 1) {
    return text;
  }

  return splitText.reduce((arr, element) => {
      if (!element) return arr;

      if(matches.includes(element)) {
        return [...arr, Component];
      }

      return [...arr, element];
    },
    []
  );
};

const string = 'Foo [first] Bar [second]';
const pattern = /(\[first\])|(\[second\])/g;

replacePatternToComponent(string, pattern, <Component />);

Abordagem usando texto datilografado:

const formatString = (
  str: string,
  formatingFunction?: (value: number | string) => React.ReactNode,
  ...values: Array<number | string>
) => {
  const templateSplit = new RegExp(/{(\d)}/g);
  const isNumber = new RegExp(/^\d+$/);
  const splitedText = str.split(templateSplit);
  return splitedText.map(sentence => {
    if (isNumber.test(sentence)) {
      const value = values[Number(sentence)];
      return Boolean(formatingFunction) ? formatingFunction(value) : value;
    }
    return sentence;
  });
};

Exemplo de uso:

const str = '{0} test {1} test';
formatString(str, value => <b>{value}</b>, value1, value2);

Resultado:
<b>value1</b> test <b>value2</b> test

@rmtngh você poderia fornecer uma amostra de como você alcançou o enriquecimento de marcação por meio do createportal? Estou procurando fazer o mesmo.

@Dashue A ideia é criar alguns alvos para seus componentes de reação dentro de sua marcação, dependendo da string HTML e de quanto controle você tem sobre ela,
Se você pode alterá-lo no lado do servidor, você pode querer dar um ID para seus elementos. Algo como :
<div id="component_target_1"></div>
E dentro de reagir, procure \id="(component_target_\d)"\g com regexp, armazene os ids (state.targets) e renderize seus componentes:

let mappedComponents
if(this.state.targets && this.state.targets.length){
            mappedComponents = this.state.targets.map((id,index)=><MyComponent id={id} key={id} />)
        }

Aqui está um exemplo de "MyComponent":

import React from 'react'
import ReactDOM from 'react-dom'

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props)
  }
  getWrapper(){
    return document.getElementById(this.props.id)
  }
  render() {
    if(!this.getWrapper()) return null

    const content = <div>
        Hello there! I'm rendered with react.
      </div>

      return ReactDOM.createPortal(
        content,
        this.getWrapper(),
      );
  }
}

Se você não tem muito controle sobre a string HTML, ainda pode usar a mesma abordagem, talvez seja necessário encontrar alguns elementos e injetar um elemento de destino na string.

Grande discussão pessoal!

Eu criei uma caneta React Text Highlighter simples que implementa algumas das ideias neste tópico (com um pouco de magia extra ...), espero que os futuros visitantes possam considerá-la útil.

Aqui está uma solução usando map ,
bifurcando a solução @sophiebits aqui:

const reg = new RegExp(/(\bmoo+\b)/, 'gi');
const parts = text.split(reg);
return <div>{parts.map(part => (part.match(reg) ? <b>{part}</b> : part))}</div>;

Bela solução @ rehat101

Aqui está o que funcionou melhor no meu caso - dividir pelo caractere de espaço e procurar por palavras específicas.

É um pouco confuso no HTML, mas quem se importa 😂

💗 RegExp e o mapa tornam isso muuuito limpo. 💗💗💗

image

const Speech = ({ speech, speeches, setSpeeches, i, text }) => {

const highlightRegExp = new RegExp(
    /you|can|do|it|puedes|hacerlo|pingüinos|son|bellos/,
    "gi"
  );
  const delineator = " ";
  const parts = text.split(delineator);

  return (
        <div>
          {parts.map(part =>
            part.match(highlightRegExp) ? <b>{part + " "}</b> : part + " "
          )}
        </div>
  );
};

Linda @ rehat101

Se react-string-replace não atender aos seus requisitos:

https://github.com/EfogDev/react-process-string

Brilhante! Acho extremamente útil, pois na maioria das vezes você realmente precisa acessar a string original

Refatorei um pouco a solução de
js const highlightQueryText = (text: string, filterValue: string) => { const reg = new RegExp(`(${filterValue})`, 'gi'); const textParts = text.split(reg); return ( <span style={{ fontWeight: 600 }}> {textParts.map(part => (part.match(reg) ? <span style={{ fontWeight: 'normal' }}>{part}</span> : part))} </span> ); };

então eu encontrei uma boa combinação de implementação de DOMPurify para higienizar strings html com html-react-parser para encontrar marcação direcionada nele e convertê-lo em componentes JSX - eu uso Rebass com adereços passados ​​como um exemplo .. que saiu muito bem, o dois pacotes o ajudarão a evitar a sobrecarga de rolar seu próprio analisador, eu simplesmente não recomendaria este pacote de analisador sem higienização. Para aqueles que estão lutando com isso, talvez descubra o que vale a pena neste código: https://codesandbox.io/s/eager-wiles-5hpff. Comentários sobre quaisquer melhorias também são muito apreciadas.

Ei Jesse,
Essa parece uma combinação muito boa para pegar um texto e transformá-lo em
Componentes de reação! Seria definitivamente bom para substituir peças de um
string com um componente.

Obrigado por compartilhar.

Também pode ser útil em um site JAMstack para transformar Markdown em componentes.

Atenciosamente,
Derek

Derek R. Austin, PT, DPT, MS, BCTMB, LMT, CSCS

Junte-se a mim no LinkedIn: https://www.linkedin.com/in/derek-austin/

Na quarta-feira, 26 de fevereiro de 2020, 4:35 PM, Jesse Lewis [email protected] escreveu:

então eu encontrei uma boa combinação de implementação de DOMPurify para higienizar html
strings com html-react-parser para encontrar a marcação direcionada e convertê-la
para componentes JSX - eu uso Rebass com adereços passados ​​como exemplo .. que
saiu muito bem, os dois pacotes irão ajudá-lo a evitar a sobrecarga
de rolar seu próprio analisador, eu simplesmente não recomendaria este pacote de analisador
sem higienização. Para aqueles que lutam com isso, talvez ainda possamos encontrar
este código é o que vale a pena:
https://codesandbox.io/s/eager-wiles-5hpff. Feedback sobre quaisquer melhorias
muito apreciado também.

-
Você está recebendo isto porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/facebook/react/issues/3386?email_source=notifications&email_token=AMV42QTV4FDXZIFXCGB3NZDRE3OATA5CNFSM4A5VRBI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENB7H7Q#issuecomment-591655934 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AMV42QXARIOKOYSJ4C5IFQLRE3OATANCNFSM4A5VRBIQ
.

Você pode usar a função split com um regex e, em seguida, substituir as partes capturadas, assim:

var parts = "I am a cow; cows say moo. MOOOOO.".split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
  parts[i] = <span className="match" key={i}>{parts[i]}</span>;
}
return <div>{parts}</div>;

Avise-me se não estiver claro.

Isso funciona muito bem. Há alguma maneira de fazer funcionar no Angular? Eu postei uma pergunta sobre isso, usando seu código:
https://stackoverflow.com/questions/60889171/wrap-certain-words-with-a-component-in-a-contenteditable-div-in-angular

Olá amigos, preciso de sua ajuda por favor, estou usando React Native e o que preciso fazer é de um texto que extraí do DB para aplicar um formato (cor da fonte, link) para fazer @ menções , ao pesquisar se no texto encontra 1 única correspondência torna a substituição totalmente boa, mas! Se houver várias @ menções no texto, isso me gerará um erro.

///// Exemplo de texto: _hey o que aconteceu @ -id: 1- tudo ok ??
//// listusers sua matriz, exemplo: [idusuario: "1", usuario: "@luigbren", format: "@ -id: 1 -" .....]

const PatternToComponent = (text, usuarios,) => {
    let mentionsRegex = new RegExp(/@-(id:[0-9]+)-/, 'gim');
    let matches = text.match(mentionsRegex);
    if (matches && matches.length) {
        matches = matches.map(function (match, idx) {
            let usrTofind = matches[idx]
            //////////////////////////////////////////////////////////////////
            const mentFormat = listusers.filter(function (item) { 
                const itemData = item.format;
                return itemData.indexOf(usrTofind) > -1;
            });
            if (mentFormat.length > 0) {
                let idusuario = mentFormat[0].idusuario
                let replc = mentFormat[0].usuario

                console.log(usrTofind) //// here find @-id:1-
                console.log(replc)   //// here is <strong i="12">@luigbren</strong> for replace

                ////////// now here replace part of the string, @-id:1- with a <Text> component 
                ///////// with <strong i="13">@luigbren</strong> and the link, this is repeated for every <strong i="14">@mention</strong> found

                parts = text.split(usrTofind);
                for (var i = 1; i < parts.length; i += 2) {
                  parts[i] = <Text key={i} style={{ color: '#00F' }} onPress={() => { alert('but this is'); }}>{replc}</Text>;
                }
                return text = parts;
                /////////////////////////////////////////////////////////////////////
            } else {
                return text
            }
        });
    } else {
        return text
    }
    return text
};

desta forma o código funciona bem para mim apenas quando no texto há apenas uma menção, exemplo '_hey o que aconteceu @ -id: 1- tudo ok ?? _', mas ao colocar mais de uma menção me dá um erro , exemplo: '_hey o que aconteceu @ -id: 1- tudo ok ?? @ -id: 2- @ -id: 3-_ '... Erro: TypeError: text.split não é uma função e se, em vez de colocar _parts = text.split (usrTofind); _ eu coloco _parts = text.toString () .split (usrTofind); _ me dá um erro [Object Object]

Você pode usar html-react-parser para analisar a string e, em seguida, substituir pelo nome do nó por componentes jsx. Este tipo de substituição permitirá que você use elementos react dinamicamente substituindo strings.

      parse(body, {
        replace: domNode => {
          if (domNode.name === 'select') { // or
            return React.createElement(
              Select, // your react component
              { },
              domToReact(domNode.children)
            );
          }
        }

Alguém me diga como posso substituir react-router-dom na string?

Exemplo:

var text = "Are You okay <strong i="9">@sara</strong> ?";

var href= <Link to={{
  pathname: '/user/sara',
}}> <strong i="10">@sara</strong> </Link>;

var replace = text.replace("@sara", href);

//output : Are You okey [Object Object] ?

Eu realmente não sei quem é [Objeto Objeto]? eu digo "Você está bem @sara", mas este código chamou outra pessoa!

Tenho quase a mesma pergunta em react-native , agradeço se alguém puder me ajudar

minha pergunta

Nada funcionou para mim, exceto este pacote https://www.npmjs.com/package/regexify-string

Estou usando esta função:

export const replaceWithHtml = (
  text: string,
  textToReplace: string,
  replaceValue: any,
): any[] => {
  const delimiter = '|||||'
  return text && text.includes(textToReplace)
    ? text
        .replace(textToReplace, `${delimiter}${textToReplace}${delimiter}`)
        .split(delimiter)
        .map((i) => {
          if (i === textToReplace) {
            return replaceValue
          } else {
            return i || null
          }
        })
    : text
}

Curtiu isso:

const text = 'This is an [icon]'
replaceWithHtml(
      text,
      '[icon]',
      <i className="icon-cogs" />,
)

Eu só queria uma ferramenta de interpolação simples, para substituir certas palavras-chave por certos elementos.

Para reduzir o número de elementos em minha saída, estou usando um React.Fragment .

const interpolate = (text, values) => {
  const pattern = /([$0-9]+)/g
  const matches = text.match(pattern)
  const parts = text.split(pattern)

  if (!matches) {
    return text
  }

  return parts.map((part, index) => (
    <Fragment key={part + index}>{matches.includes(part) ? values[part] : part}</Fragment>
  ))
}

// ...

<p>
  {interpolate('$1 with $2 is fun!', {
    $1: <em>Playing</em>,
    $2: <strong>JavaScript</strong>,
  })}
</p>

// <p><em>Playing</em> with <strong>JavaScript</strong> is fun!</p>

Obrigado @sophiebits pela ideia em split() 🙏

Com base na solução de @pedrodurek , aqui está algo para minhas necessidades específicas (traduções com chaves legíveis em vez de apenas números):

export const insertComponentsIntoText = (
    str: string,
    replacements: {
        [key: string]: React.ReactNode
    }
) => {
    const splitRegex = new RegExp(/\[\[(\w*)\]\]/g);
    const parts = str.split(splitRegex);
    return parts.map(part => {
        if (replacements.hasOwnProperty(part)) {
            return replacements[part];
        }
        return part;
    });
};

Exemplo:

insertComponentsIntoText(`"Please accept our [[privacyPolicyLink]] and [[termsLink].", {
    "privacyPolicyLink": <a key="privacyPolicyLink" href="/privacy">Privacy Policy</a>,
    "termsLink": <a key="termsLink" href="/terms">Terms and Conditions</a>
})

...torna-se...

Please accept our <a key="privacyPolicyLink" href="/privacy">Privacy Policy</a> and <a key="termsLink" href="/terms">Terms and Conditions</a>.

(As chaves são necessárias porque na verdade se tornam uma matriz e não reagem como os elementos da matriz sem chaves.)

Nada funcionou para mim, exceto este pacote https://www.npmjs.com/package/regexify-string

Eu também. Tentei todos eles apenas este pacote funciona. Obrigada!

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

jimfb picture jimfb  ·  3Comentários

kocokolo picture kocokolo  ·  3Comentários

trusktr picture trusktr  ·  3Comentários

hnordt picture hnordt  ·  3Comentários

Prinzhorn picture Prinzhorn  ·  3Comentários