Construindo um editor de código da Web

Publicados: 2022-03-10
Resumo rápido ↬ Se você é um desenvolvedor que está pensando em construir uma plataforma que requer um editor de código de uma forma ou de outra, então este artigo é para você. Este artigo explica como criar um editor de código web que exibe o resultado em tempo real com a ajuda de alguns HTML, CSS e JavaScript.

Um editor de código da Web online é mais útil quando você não tem a oportunidade de usar um aplicativo de editor de código ou quando deseja experimentar rapidamente algo na Web com seu computador ou até mesmo com seu telefone celular. Este também é um projeto interessante para se trabalhar, pois ter o conhecimento de como construir um editor de código lhe dará ideias sobre como abordar outros projetos que exigem que você integre um editor de código para mostrar alguma funcionalidade.

Aqui estão alguns conceitos do React que você precisa saber para acompanhar este artigo:

  • Ganchos,
  • Estrutura do componente,
  • Componentes funcionais,
  • Adereços.

Usando CodeMirror

Usaremos uma biblioteca chamada CodeMirror para construir nosso editor. CodeMirror é um editor de texto versátil implementado em JavaScript para o navegador. É especialmente para edição de código e vem com vários modos de idioma e complementos para funcionalidades de edição mais avançadas.

Uma rica API de programação e um sistema de temas CSS estão disponíveis para personalizar o CodeMirror para se adequar ao seu aplicativo e estendê-lo com novas funcionalidades. Ele nos dá a funcionalidade de criar um editor de código rico que roda na web e nos mostra o resultado do nosso código em tempo real.

Na próxima seção, vamos configurar nosso novo projeto React e instalar as bibliotecas que precisamos para construir nosso aplicativo web.

Criando um novo projeto React

Vamos começar criando um novo projeto React. Em sua interface de linha de comando, navegue até o diretório no qual você deseja criar seu projeto e vamos criar um aplicativo React e nomeá-lo code_editor :

 npx create-react-app code_editor

Tendo criado nosso novo aplicativo React, vamos navegar até o diretório desse projeto na interface de linha de comando:

 cd code_editor

Existem duas bibliotecas que precisamos instalar aqui: codemirror e react-codemirror2 .

 npm install codemirror react-codemirror2

Tendo instalado as bibliotecas que precisamos para este projeto, vamos criar nossas guias e habilitar a alternância de guias entre as três guias que aparecerão em nosso editor (para HTML, CSS e JavaScript).

Mais depois do salto! Continue lendo abaixo ↓

Componente do botão

Em vez de criar botões individuais, vamos fazer do botão um componente reutilizável. Em nosso projeto, o botão teria três instâncias, de acordo com as três abas que precisamos.

Crie uma pasta chamada components na pasta src . Nesta nova pasta de components , crie um arquivo JSX chamado Button.jsx .

Aqui está todo o código necessário no componente Button :

 import React from 'react' const Button = ({title, onClick}) => { return ( <div> <button style={{ maxWidth: "140px", minWidth: "80px", height: "30px", marginRight: "5px" }} onClick={onClick} > {title} </button> </div> ) } export default Button

Aqui está uma explicação completa do que fizemos acima:

  • Criamos um componente funcional chamado Button , que depois exportamos.
  • Desestruturamos o title e o onClick dos adereços que entram no componente. Aqui, title seria uma string de texto e onClick seria uma função que é chamada quando um botão é clicado.
  • Em seguida, usamos o elemento button para declarar nosso botão e usamos os atributos de style para estilizar nosso botão para parecer apresentável.
  • Adicionamos o atributo onClick e passamos nossos adereços de função onClick desestruturados para ele.
  • A última coisa que você notará que fizemos neste componente é passar em {title} como o conteúdo da tag do button . Isso nos permite exibir o título dinamicamente, com base em qual prop está sendo passada para a instância do componente do botão quando ele é chamado.

Agora que criamos um componente de botão reutilizável, vamos seguir em frente e trazer nosso componente para o App.js. Acesse App.js e importe o componente de botão recém-criado:

 import Button from './components/Button';

Para rastrear qual guia ou editor está aberto, precisamos de um estado de declaração para manter o valor do editor que está aberto. Usando o gancho useState React, configuraremos o estado que armazenará o nome da guia do editor que está aberta no momento quando o botão dessa guia é clicado.

Aqui está como fazemos isso:

 import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { const [openedEditor, setOpenedEditor] = useState('html'); return ( <div className="App"> </div> ); } export default App;

Aqui, declaramos nosso estado. Leva o nome do editor que está aberto no momento. Como o valor html é passado como o valor padrão do estado, o editor HTML seria a guia aberta por padrão.

Vamos seguir em frente e escrever a função que usará setOpenedEditor para alterar o valor do estado quando um botão de guia é clicado.

Nota: Duas abas podem não estar abertas ao mesmo tempo, então teremos que considerar isso ao escrever nossa função.

Aqui está como nossa função, chamada onTabClick , se parece:

 import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { ... const onTabClick = (editorName) => { setOpenedEditor(editorName); }; return ( <div className="App"> </div> ); } export default App;

Aqui, passamos um único argumento de função, que é o nome da guia selecionada no momento. Esse argumento seria fornecido em qualquer lugar em que a função fosse chamada e o nome relevante dessa guia seria passado.

Vamos criar três instâncias do nosso Button para as três abas que precisamos:

 <div className="App"> <p>Welcome to the editor!</p> <div className="tab-button-container"> <Button title="HTML" onClick={() => { onTabClick('html') }} /> <Button title="CSS" onClick={() => { onTabClick('css') }} /> <Button title="JavaScript" onClick={() => { onTabClick('js') }} /> </div> </div>

Aqui está o que nós fizemos:

  • Começamos adicionando uma tag p , basicamente apenas para dar algum contexto sobre o que é nosso aplicativo.
  • Usamos uma tag div para envolver nossos botões de guia. A tag div carrega um className que usaremos para estilizar os botões em uma exibição de grade no arquivo CSS posteriormente neste tutorial.
  • Em seguida, declaramos três instâncias do componente Button . Se você se lembra, o componente Button leva dois props, title e onClick . Em todas as instâncias do componente Button , essas duas props são fornecidas.
  • A prop title pega o título da aba.
  • A prop onClick recebe uma função, onTabClick , que acabamos de criar e que recebe um único argumento: o nome da guia selecionada.

Com base na guia atualmente selecionada, usaríamos o operador ternário JavaScript para exibir a guia condicionalmente. Isso significa que se o valor do estado openedEditor for definido como html (ou seja setOpenedEditor('html') ), então a guia da seção HTML se tornará a guia visível no momento. Você entenderá isso melhor quando fizermos isso abaixo:

 ... return ( <div className="App"> ... <div className="editor-container"> { openedEditor === 'html' ? ( <p>The html editor is open</p> ) : openedEditor === 'css' ? ( <p>The CSS editor is open!!!!!!</p> ) : ( <p>the JavaScript editor is open</p> ) } </div> </div> ); ...

Vamos revisar o código acima em inglês simples. Se o valor de openedEditor for html , exiba a seção HTML. Caso contrário, se o valor de openedEditor for css , exiba a seção CSS. Caso contrário, se o valor não for html nem css , isso significa que o valor deve ser js , porque temos apenas três valores possíveis para o estado openedEditor ; então, exibiríamos a guia para JavaScript.

Usamos tags de parágrafo ( p ) para as diferentes seções nas condições do operador ternário. À medida que prosseguirmos, criaremos os componentes do editor e substituiremos as tags p pelos próprios componentes do editor.

Já chegamos tão longe! Quando um botão é clicado, ele aciona a ação que define a guia que representa como true , tornando essa guia visível. Veja como está nosso aplicativo atualmente:

Um GIF mostrando a alternância de guias que temos atualmente.
Um GIF mostrando a alternância de guias que temos atualmente. (Visualização grande)

Vamos adicionar um pouco de CSS ao contêiner div que contém os botões. Queremos que os botões sejam exibidos em uma grade, em vez de empilhados verticalmente como na imagem acima. Acesse seu arquivo App.css e adicione o seguinte código:

 .tab-button-container{ display: flex; }

Lembre-se de que adicionamos className="tab-button-container" como um atributo na tag div que contém os botões de três guias. Aqui, estilizamos esse contêiner, usando CSS para definir sua exibição como flex . Este é o resultado:

Usamos CSS para definir sua exibição para flex
(Visualização grande)

Tenha orgulho do quanto você fez para chegar a este ponto. Na próxima seção, criaremos nossos editores, substituindo as tags p por eles.

Criando os Editores

Como já instalamos as bibliotecas nas quais trabalharemos em nosso editor CodeMirror, vamos em frente e criar nosso arquivo Editor.jsx na pasta de components .

componentes > Editor.jsx

Tendo criado nosso novo arquivo, vamos escrever algum código inicial nele:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> </div> ) } export default Editor

Aqui está o que fizemos:

  • Importamos o React junto com o hook useState porque vamos precisar dele.
  • Importamos o arquivo CSS CodeMirror (que vem da biblioteca CodeMirror que instalamos, então você não precisa instalá-lo de nenhuma maneira especial).
  • Importamos Controlled de react-codemirror2 , renomeando-o para ControlledEditorComponent para torná-lo mais claro. Estaremos usando isso em breve.
  • Em seguida, declaramos nosso componente funcional Editor e temos uma instrução return com um div vazio, com um className na instrução return por enquanto.

Em nosso componente funcional, desestruturamos alguns valores das props, incluindo language , value e setEditorState . Esses três adereços seriam fornecidos em qualquer instância do editor quando ele fosse chamado em App.js .

Vamos usar o ControlledEditorComponent para escrever o código do nosso editor. Aqui está o que faremos:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, }} /> </div> ) } export default Editor

Vamos ver o que fizemos aqui, explicando alguns termos do CodeMirror.

Os modos CodeMirror especificam para qual idioma um editor se destina. Importamos três modos porque temos três editores para este projeto:

  1. XML: Este modo é para HTML. Ele usa o termo XML.
  2. JavaScript: Isso ( codemirror/mode/javascript/javascript ) traz o modo JavaScript.
  3. CSS: Isso ( codemirror/mode/css/css ) traz o modo CSS.

Nota: Como o editor é construído como um componente reutilizável, não podemos colocar um modo direto no editor. Então, nós fornecemos o modo através do suporte de language que desestruturamos. Mas isso não muda o fato de que os modos precisam ser importados para funcionar.

Em seguida, vamos discutir as coisas em ControlledEditorComponent :

  • onBeforeChange
    Isso é chamado sempre que você escreve ou remove do editor. Pense nisso como o manipulador onChange que você normalmente teria em um campo de entrada para rastrear alterações. Usando isso, poderemos obter o valor do nosso editor sempre que houver uma nova alteração e salvá-lo no estado do nosso editor. Vamos escrever a função {handleChange} medida que prosseguimos.
  • value = {value}
    Este é apenas o conteúdo do editor a qualquer momento. Passamos um prop desestruturado chamado value para este atributo. O value props é o estado que contém o valor desse editor. Isso seria fornecido a partir da instância do editor.
  • className ="code-mirror-wrapper"
    Este nome de classe não é um estilo que criamos. Ele é fornecido a partir do arquivo CSS do CodeMirror, que importamos acima.
  • options
    Este é um objeto que leva as diferentes funcionalidades que queremos que nosso editor tenha. Existem muitas opções incríveis no CodeMirror. Vejamos os que usamos aqui:
    • lineWrapping: true
      Isso significa que o código deve passar para a próxima linha quando a linha estiver cheia.
    • lint: true
      Isso permite a formação de fiapos.
    • mode: language
      Este modo, como discutido acima, usa o idioma para o qual o editor será usado. O idioma já foi importado acima, mas o editor aplicará um idioma com base no valor do language fornecido ao editor por meio do prop.
    • lineNumbers: true
      Isso especifica que o editor deve ter números de linha para cada linha.

Em seguida, podemos escrever a função handleChange para o manipulador onBeforeChange :

 const handleChange = (editor, data, value) => { setEditorState(value); }

O manipulador onBeforeChange nos dá acesso a três coisas: editor, data, value .

Nós só precisamos do value porque é o que queremos passar em nossa prop setEditorState . A prop setEditorState representa o valor definido para cada estado que declaramos em App.js , mantendo o valor para cada editor. À medida que avançamos, veremos como passar isso como um suporte para o componente Editor .

Em seguida, adicionaremos um menu suspenso que permite selecionar diferentes temas para o editor. Então, vamos ver os temas no CodeMirror.

Temas CodeMirror

CodeMirror tem vários temas que podemos selecionar. Visite o site oficial para ver demos dos diferentes temas disponíveis. Vamos fazer um dropdown com diferentes temas que o usuário pode escolher em nosso editor. Para este tutorial, adicionaremos cinco temas, mas você pode adicionar quantos quiser.

Primeiro, vamos importar nossos temas no componente Editor.js :

 import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css';

Em seguida, crie uma matriz de todos os temas que importamos:

 const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night']

Vamos declarar um gancho useState para manter o valor do tema selecionado e definir o tema padrão como dracula :

 const [theme, setTheme] = useState("dracula")

Vamos criar a lista suspensa:

 ... return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="cars">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> // the rest of the code comes below... </div> ) ...

No código acima, usamos a tag HTML do label para adicionar um rótulo ao nosso menu suspenso e, em seguida, adicionamos a tag HTML de select para criar nosso menu suspenso. A tag de option no elemento select define as opções disponíveis no menu suspenso.

Como precisávamos preencher o menu suspenso com os nomes dos temas no themeArray que criamos, usamos o método .map array para mapear o themeArray e exibir os nomes individualmente usando a tag option .

Espere — ainda não terminamos de explicar o código acima. Na tag select de abertura, passamos o atributo onChange para rastrear e atualizar o estado do theme sempre que um novo valor for selecionado no menu suspenso. Sempre que uma nova opção é selecionada na lista suspensa, o valor é obtido do objeto retornado para nós. Em seguida, usamos o setTheme do nosso gancho de estado para definir o novo valor como o valor que o estado contém.

Neste ponto, criamos nosso menu suspenso, configuramos o estado do nosso tema e escrevemos nossa função para definir o estado com o novo valor. A última coisa que precisamos fazer para que o CodeMirror use nosso tema é passar o tema para o objeto options em ControlledEditorComponent . No objeto de options , vamos adicionar um valor chamado theme e definir seu valor como o valor do estado para o tema selecionado, também chamado theme .

Veja como o ControlledEditorComponent ficaria agora:

 <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} />

Agora, criamos uma lista suspensa de diferentes temas que podem ser selecionados no editor.

Veja como está o código completo no Editor.js no momento:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { const [theme, setTheme] = useState("dracula") const handleChange = (editor, data, value) => { setEditorState(value); } const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night'] return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="themes">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} /> </div> ) } export default Editor

Há apenas um className que precisamos estilizar. Vá para App.css e adicione o seguinte estilo:

 .editor-container{ padding-top: 0.4%; }

Agora que nossos editores estão prontos, vamos voltar ao App.js e usá-los lá.

src > App.js

A primeira coisa que precisamos fazer é importar o componente Editor.js aqui:

 import Editor from './components/Editor';

Em App.js , vamos declarar os estados que conterão o conteúdo dos editores HTML, CSS e JavaScript, respectivamente.

 const [html, setHtml] = useState(''); const [css, setCss] = useState(''); const [js, setJs] = useState('');

Se você se lembra, precisaremos usar esses estados para manter e fornecer o conteúdo de nossos editores.

Em seguida, vamos substituir as tags de parágrafo ( p ) que usamos para HTML, CSS e JavaScript nas renderizações condicionais pelos componentes do editor que acabamos de criar e também passaremos o prop apropriado para cada instância do editor componente:

 function App() { ... return ( <div className="App"> <p>Welcome to the edior</p> // This is where the tab buttons container is... <div className="editor-container"> { htmlEditorIsOpen ? ( <Editor language="xml" value={html} setEditorState={setHtml} /> ) : cssEditorIsOpen ? ( <Editor language="css" value={css} setEditorState={setCss} /> ) : ( <Editor language="javascript" value={js} setEditorState={setJs} /> ) } </div> </div> ); } export default App;

Se você está acompanhando até agora, vai entender o que fizemos no bloco de código acima.

Aqui está em inglês simples: Substituímos as tags p (que estavam lá como espaços reservados) por instâncias dos componentes do editor. Em seguida, fornecemos suas props language , value e setEditorState , respectivamente, para corresponder aos estados correspondentes.

Chegamos tão longe! Veja como está nosso aplicativo agora:

A aparência do nosso aplicativo agora
(Visualização grande)

Introdução aos iframes

Faremos uso de frames inline (iframes) para exibir o resultado do código inserido no editor.

De acordo com o MDN:

O elemento HTML Inline Frame ( <iframe> ) representa um contexto de navegação aninhado, incorporando outra página HTML na atual.

Como os iframes funcionam no React

Os iframes são normalmente usados ​​com HTML simples. Usar Iframes com React não requer muitas mudanças, sendo a principal delas converter nomes de atributos para camelcase. Um exemplo disso é que srcdoc se tornaria srcDoc .

O futuro dos iframes na web

Os iframes continuam sendo muito úteis no desenvolvimento web. Algo que você pode querer verificar é portais. Como Daniel Brain explica:

“Os portais introduzem um novo e poderoso conjunto de recursos nesse mix. Agora é possível construir algo que se pareça com um iframe, que pode animar e transformar perfeitamente e assumir a janela completa do navegador.”

Uma das coisas que o Portals tenta resolver é o problema da barra de URL. Ao usar o iframe, os componentes renderizados no iframe não carregam um URL exclusivo na barra de endereço; como tal, isso pode não ser ótimo para a experiência do usuário, dependendo do caso de uso. Vale a pena conferir os portais, e eu sugiro que você faça isso, mas como não é o foco do nosso artigo, é tudo o que vou dizer sobre isso aqui.

Criando o Iframe para abrigar nosso resultado

Vamos avançar com nosso tutorial criando um iframe para abrigar o resultado de nossos editores.

 return ( <div className="App"> // ... <div> <iframe srcDoc={srcDoc} title="output" sandbox="allow-scripts" frameBorder="1" width="100%" height="100%" /> </div> </div> );

Aqui, criamos o iframe e o alojamos em uma tag de contêiner div . No iframe, passamos alguns atributos que precisamos:

  • srcDoc
    O atributo srcDoc é escrito em camelcase porque é assim que se escreve atributos iframe no React. Ao usar um iframe, podemos incorporar uma página da Web externa na página ou renderizar o conteúdo HTML especificado. Para carregar e incorporar uma página externa, usaríamos a propriedade src . No nosso caso, não estamos carregando uma página externa; em vez disso, queremos criar um novo documento HTML interno que abrigue nosso resultado; para isso, precisamos do atributo srcDoc . Este atributo pega o documento HTML que queremos embutir (ainda não criamos, mas o faremos em breve).
  • title
    O atributo title é usado para descrever o conteúdo do quadro embutido.
  • sandbox
    Esta propriedade tem muitos propósitos. No nosso caso, estamos usando para permitir que scripts sejam executados em nosso iframe com o valor allow-scripts . Como estamos trabalhando com um editor JavaScript, isso seria útil rapidamente.
  • frameBorder
    Isso apenas define a espessura da borda do iframe.
  • width e height
    Isso define a largura e a altura do iframe.

Esses termos agora devem fazer mais sentido para você. Vamos seguir em frente e declarar o estado que conterá o documento de modelo HTML para srcDoc . Se você observar atentamente o bloco de código acima, verá que passamos um valor para o atributo srcDoc : srcDoc ={srcDoc} . Vamos usar nosso useState() React para declarar o estado srcDoc . Para fazer isso, no arquivo App.js , vá até onde definimos os outros estados e adicione este:

 const [srcDoc, setSrcDoc] = useState(` `);

Agora que criamos o estado, a próxima coisa a fazer é exibir o resultado no estado sempre que digitarmos no editor de código. Mas o que não queremos é renderizar novamente o componente a cada pressionamento de tecla. Com isso em mente, vamos prosseguir.

Configurando o Iframe para exibir o resultado

Sempre que houver uma mudança em qualquer um dos editores para HTML, CSS e JavaScript, respectivamente, queremos que useEffect() seja acionado, e isso renderizará o resultado atualizado no iframe. Vamos escrever useEffect() para fazer isso no arquivo App.js :

Primeiro, importe o gancho useEffect() :

 import React, { useState, useEffect } from 'react';

Vamos escrever useEffect() assim:

 useEffect(() => { const timeOut = setTimeout(() => { setSrcDoc( ` <html> <body>${html}</body> <style>${css}</style> <script>${js}</script> </html> ` ) }, 250); return () => clearTimeout(timeOut) }, [html, css, js])

Aqui, escrevemos um gancho useEffect() que sempre será executado sempre que o valor declarado para os editores de HTML, CSS e JavaScript for alterado ou atualizado.

Por que precisamos usar setTimeout() ? Bem, se escrevêssemos isso sem ele, toda vez que um único pressionamento de tecla fosse feito em um editor, nosso iframe seria atualizado, e isso não é ótimo para o desempenho em geral. Então usamos setTimeout() para atrasar a atualização por 250 milissegundos, dando-nos tempo suficiente para saber se o usuário ainda está digitando. Ou seja, toda vez que o usuário pressiona uma tecla, ele reinicia a contagem, de modo que o iframe só seria atualizado quando o usuário estivesse ocioso (não digitando) por 250 milissegundos. Essa é uma maneira legal de evitar ter que atualizar o iframe toda vez que uma tecla é pressionada.

A próxima coisa que fizemos acima foi atualizar o srcDoc com as novas alterações. O componente srcDoc , como explicamos acima, renderiza o conteúdo HTML especificado no iframe. Em nosso código, passamos um template HTML, pegando o estado html que contém o código que o usuário digitou no editor HTML e colocando-o entre as tags body do nosso template. Também pegamos o estado css que contém os estilos que o usuário digitou no editor de CSS e passamos isso entre as tags de style . Por fim, pegamos o estado js que contém o código JavaScript que o usuário digitou no editor JavaScript e o passamos entre as tags de script .

Observe que ao definir setSrcDoc , usamos acentos graves ( ` ` ) em vez de aspas normais ( ' ' ). Isso ocorre porque os acentos graves nos permitem passar valores de estado correspondentes, como fizemos no código acima.

A instrução return no gancho useEffect() é uma função de limpeza que limpa setTimeout() quando ele é concluído, para evitar vazamento de memória. A documentação tem mais sobre useEffect .

Veja como está nosso projeto no momento:

Como está nosso projeto no momento
(Visualização grande)

Complementos CodeMirror

Com os complementos do CodeMirror, podemos aprimorar nosso editor com mais do tipo de funcionalidade que encontraríamos em outros editores de código. Vamos ver um exemplo de tags de fechamento sendo adicionadas automaticamente quando uma tag de abertura é digitada e outro exemplo de um colchete fechando automaticamente quando o colchete de abertura é inserido:

A primeira coisa a fazer é importar o addon para isso em nosso arquivo App.js :

 import 'codemirror/addon/edit/closetag'; import 'codemirror/addon/edit/closebrackets';

Vamos passá-lo nas opções do ControlledEditorComponent :

 <ControlledEditorComponent ... options={{ ... autoCloseTags: true, autoCloseBrackets: true, }} />

Agora vamos ao que temos:

A aparência do nosso projeto
(Visualização grande)

Você pode adicionar uma tonelada desses complementos ao seu editor para fornecer recursos mais ricos. Nós não poderíamos passar por todos eles aqui.

Agora que terminamos com isso, vamos discutir brevemente o que podemos fazer para melhorar a acessibilidade e o desempenho do nosso aplicativo.

Desempenho e Acessibilidade da Solução

Olhando para o nosso editor de código da web, algumas coisas definitivamente poderiam ser melhoradas.

Como prestamos atenção principalmente à funcionalidade, podemos ter negligenciado um pouco o design. Para uma melhor acessibilidade, aqui estão algumas coisas que você pode fazer para melhorar esta solução:

  1. Você pode definir uma classe active no botão para o editor atualmente aberto. Destacar o botão melhoraria a acessibilidade, dando aos usuários uma indicação clara de qual editor eles estão trabalhando no momento.
  2. Você pode querer que o editor ocupe mais espaço na tela do que temos aqui. Outra coisa que você pode tentar é fazer o iframe aparecer com o clique de um botão que está encaixado em algum lugar ao lado. Isso daria ao editor mais espaço na tela.
  3. Esse tipo de editor seria útil para pessoas que desejam executar um exercício rápido em seu dispositivo móvel, portanto, seria necessário adaptá-lo totalmente ao celular (sem mencionar os dois pontos sobre celular acima).
  4. Atualmente, podemos alternar o tema do componente do editor entre os vários temas que carregamos, mas o tema geral da página permanece o mesmo. Você pode permitir que o usuário alterne entre um tema escuro e claro para todo o layout. Isso seria bom para a acessibilidade, aliviando a tensão nos olhos das pessoas de olhar para uma tela brilhante por muito tempo.
  5. Não analisamos problemas de segurança com nosso iframe, principalmente porque estávamos carregando um documento HTML interno no iframe, em vez de um documento externo. Portanto, não precisamos considerar isso com muito cuidado porque os iframes são uma boa opção para nosso caso de uso.
  6. Com iframes, outra consideração seria o tempo de carregamento da página, porque o conteúdo carregado no iframe normalmente estaria fora de seu controle. Em nosso aplicativo, isso não é um problema porque nosso conteúdo de iframe não é externo.

O desempenho e a acessibilidade merecem muita consideração ao criar qualquer aplicativo, pois eles determinarão o quão útil e utilizável seu aplicativo é para seus usuários.

Shedrack fez um bom trabalho explicando métodos para melhorar e otimizar o desempenho em aplicativos React. Vale a pena conferir!

Conclusão

Trabalhar em diferentes projetos nos ajuda a aprender sobre uma ampla gama de assuntos. Agora que você leu este artigo, sinta-se à vontade para expandir sua experiência experimentando mais complementos para tornar o editor de código mais rico, renovando a interface do usuário e corrigindo os problemas de acessibilidade e desempenho descritos acima.

  • Toda a base de código para este projeto está disponível no GitHub.

Aqui está a demonstração no Codesandbox:

Links e materiais

  • “Portais do Google Chrome: como iframes, mas melhores e piores”, Daniel Brain
  • “Otimizando o desempenho”, documentação do React
  • “Manual do usuário e guia de referência”, documentação do CodeMirror