Creazione di un editor di codice Web
Pubblicato: 2022-03-10Un editor di codice Web online è particolarmente utile quando non si ha l'opportunità di utilizzare un'applicazione di editor di codice o quando si desidera provare rapidamente qualcosa sul Web con il computer o anche con il telefono cellulare. Questo è anche un progetto interessante su cui lavorare perché avere la conoscenza di come costruire un editor di codice ti darà idee su come affrontare altri progetti che richiedono l'integrazione di un editor di codice per mostrare alcune funzionalità.
Ecco alcuni concetti di React che devi conoscere per seguire in questo articolo:
- Ganci,
- struttura dei componenti,
- componenti funzionali,
- Puntelli.
Utilizzo di CodeMirror
Utilizzeremo una libreria denominata CodeMirror per creare il nostro editor. CodeMirror è un versatile editor di testo implementato in JavaScript per il browser. È specialmente per la modifica del codice e viene fornito con una serie di modalità linguistiche e componenti aggiuntivi per funzionalità di modifica più avanzate.
Sono disponibili una ricca API di programmazione e un sistema di temi CSS per personalizzare CodeMirror per adattarlo alla tua applicazione ed estenderlo con nuove funzionalità. Ci dà la funzionalità per creare un ricco editor di codice che gira sul web e ci mostra il risultato del nostro codice in tempo reale.
Nella prossima sezione, imposteremo il nostro nuovo progetto React e installeremo le librerie di cui abbiamo bisogno per costruire la nostra web app.
Creazione di un nuovo progetto React
Iniziamo creando un nuovo progetto React. Nella tua interfaccia a riga di comando, vai alla directory in cui vuoi creare il tuo progetto e creiamo un'applicazione React e code_editor
:
npx create-react-app code_editor
Dopo aver creato la nostra nuova applicazione React, andiamo alla directory di quel progetto nell'interfaccia della riga di comando:
cd code_editor
Ci sono due librerie che dobbiamo installare qui: codemirror
e react-codemirror2
.
npm install codemirror react-codemirror2
Dopo aver installato le librerie di cui abbiamo bisogno per questo progetto, creiamo le nostre schede e abilitiamo il passaggio tra le tre schede che appariranno nel nostro editor (per HTML, CSS e JavaScript).
Componente pulsante
Invece di creare singoli pulsanti, rendiamo il pulsante un componente riutilizzabile. Nel nostro progetto, il pulsante avrebbe tre istanze, in base alle tre schede di cui abbiamo bisogno.
Crea una cartella denominata components
nella cartella src
. In questa nuova cartella components
, crea un file JSX denominato Button.jsx
.
Ecco tutto il codice necessario nel 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
Ecco una spiegazione completa di ciò che abbiamo fatto sopra:
- Abbiamo creato un componente funzionale chiamato
Button
, che abbiamo poi esportato. - Abbiamo destrutturato
title
eonClick
dagli oggetti di scena che entrano nel componente. Qui, iltitle
sarebbe una stringa di testo eonClick
sarebbe una funzione che viene chiamata quando si fa clic su un pulsante. - Successivamente, abbiamo utilizzato l'elemento
button
per dichiarare il nostro pulsante e abbiamo utilizzato gli attributi distyle
per dare uno stile al nostro pulsante in modo che appaia presentabile. - Abbiamo aggiunto l'attributo
onClick
e gli abbiamo passato la nostra funzione destrutturataonClick
props. - L'ultima cosa che noterai che abbiamo fatto in questo componente è passare in
{title}
come contenuto del tag delbutton
. Questo ci consente di visualizzare il titolo in modo dinamico, in base a quale prop viene passato all'istanza del componente button quando viene chiamato.
Ora che abbiamo creato un componente pulsante riutilizzabile, andiamo avanti e portiamo il nostro componente in App.js.
Vai su App.js
e importa il componente pulsante appena creato:
import Button from './components/Button';
Per tenere traccia di quale scheda o editor è aperto, è necessario uno stato di dichiarazione per mantenere il valore dell'editor aperto. Usando l'hook useState
React, imposteremo lo stato che memorizzerà il nome della scheda dell'editor che è attualmente aperta quando si fa clic sul pulsante di quella scheda.
Ecco come lo facciamo:
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;
Qui abbiamo dichiarato il nostro stato. Prende il nome dell'editor attualmente aperto. Poiché il valore html
viene passato come valore predefinito dello stato, l'editor HTML sarebbe la scheda aperta per impostazione predefinita.
Andiamo avanti e scriviamo la funzione che utilizzerà setOpenedEditor
per modificare il valore dello stato quando si fa clic su un pulsante di scheda.
Nota: due schede potrebbero non essere aperte contemporaneamente, quindi dovremo tenerne conto quando scriviamo la nostra funzione.
Ecco come appare la nostra funzione, denominata onTabClick
:
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;
Qui, abbiamo passato un singolo argomento di funzione, che è il nome della scheda attualmente selezionata. Questo argomento verrebbe fornito ovunque venga chiamata la funzione e verrebbe passato il nome pertinente di quella scheda.
Creiamo tre istanze del nostro Button
per le tre schede di cui abbiamo bisogno:
<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>
Ecco cosa abbiamo fatto:
- Abbiamo iniziato aggiungendo un tag
p
, fondamentalmente solo per dare un po' di contesto all'argomento della nostra applicazione. - Abbiamo utilizzato un tag
div
per avvolgere i pulsanti delle schede. Il tagdiv
contiene unclassName
che useremo per definire lo stile dei pulsanti in una visualizzazione a griglia nel file CSS più avanti in questo tutorial. - Successivamente, abbiamo dichiarato tre istanze del componente
Button
. Se ricordi, il componenteButton
richiede due oggetti di scena,title
eonClick
. In ogni istanza del componenteButton
, vengono forniti questi due oggetti di scena. - Il
title
prop prende il titolo della scheda. - Il prop
onClick
prende una funzione,onTabClick
, che abbiamo appena creato e che accetta un solo argomento: il nome della scheda selezionata.
In base alla scheda attualmente selezionata, utilizzeremo l'operatore ternario JavaScript per visualizzare la scheda in modo condizionale. Ciò significa che se il valore dello stato openedEditor
è impostato su html
(ie setOpenedEditor('html')
), la scheda per la sezione HTML diventerebbe la scheda attualmente visibile. Lo capirai meglio mentre lo facciamo di seguito:
... 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> ); ...
Esaminiamo il codice sopra in un inglese semplice. Se il valore di openedEditor
è html
, visualizza la sezione HTML. Altrimenti, se il valore di openedEditor
è css
, visualizza la sezione CSS. Altrimenti, se il valore non è né html
né css
, significa che il valore deve essere js
, perché abbiamo solo tre valori possibili per lo stato openedEditor
; quindi, visualizzeremmo la scheda per JavaScript.
Abbiamo utilizzato i tag di paragrafo ( p
) per le diverse sezioni nelle condizioni dell'operatore ternario. Mentre procediamo, creeremo i componenti dell'editor e sostituiremo i tag p
con gli stessi componenti dell'editor.
Siamo già arrivati così lontano! Quando si fa clic su un pulsante, viene attivata l'azione che imposta la scheda che rappresenta su true
, rendendo quella scheda visibile. Ecco come appare attualmente la nostra app:
Aggiungiamo un po' di CSS al contenitore div
che contiene i pulsanti. Vogliamo che i pulsanti vengano visualizzati in una griglia, invece che impilati verticalmente come nell'immagine sopra. Vai al tuo file App.css
e aggiungi il codice seguente:
.tab-button-container{ display: flex; }
Ricordiamo che abbiamo aggiunto className="tab-button-container"
come attributo nel tag div
contenente i pulsanti a tre schede. Qui, abbiamo disegnato quel contenitore, usando CSS per impostarne la visualizzazione su flex
. Questo è il risultato:
Sii orgoglioso di quanto hai fatto per arrivare a questo punto. Nella prossima sezione creeremo i nostri editor, sostituendo i tag p
con essi.
Creazione degli editori
Poiché abbiamo già installato le librerie su cui lavoreremo all'interno del nostro editor CodeMirror, andiamo avanti e creiamo il nostro file Editor.jsx
nella cartella dei components
.
componenti > Editor.jsx
Dopo aver creato il nostro nuovo file, scriviamo del codice iniziale al suo interno:
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
Ecco cosa abbiamo fatto:
- Abbiamo importato React insieme
useState
perché ne avremo bisogno. - Abbiamo importato il file CSS CodeMirror (che proviene dalla libreria CodeMirror che abbiamo installato, quindi non devi installarlo in alcun modo speciale).
- Abbiamo importato
Controlled
dareact-codemirror2
, rinominandolo inControlledEditorComponent
per renderlo più chiaro. Lo useremo a breve. - Quindi, abbiamo dichiarato il nostro componente funzionale
Editor
e abbiamo un'istruzione return con undiv
vuoto, con unclassName
return per ora.
Nel nostro componente funzionale, abbiamo destrutturato alcuni valori dagli oggetti di scena, inclusi language
, value
e setEditorState
. Questi tre prop verrebbero forniti in qualsiasi istanza dell'editor quando viene chiamato in App.js
.
Usiamo ControlledEditorComponent
per scrivere il codice per il nostro editor. Ecco cosa faremo:
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
Esaminiamo ciò che abbiamo fatto qui, spiegando alcuni termini di CodeMirror.
Le modalità CodeMirror specificano a quale lingua è destinato un editor. Abbiamo importato tre modalità perché abbiamo tre editor per questo progetto:
- XML: questa modalità è per HTML. Usa il termine XML.
- JavaScript: questo (
codemirror/mode/javascript/javascript
) porta in modalità JavaScript. - CSS: questo (
codemirror/mode/css/css
) porta in modalità CSS.
Nota: poiché l'editor è creato come componente riutilizzabile, non è possibile inserire una modalità diretta nell'editor. Quindi, forniamo la modalità attraverso il supporto language
che abbiamo destrutturato. Ma questo non cambia il fatto che le modalità devono essere importate per funzionare.
Quindi, discutiamo le cose in ControlledEditorComponent
:
-
onBeforeChange
Viene chiamato ogni volta che scrivi o rimuovi dall'editor. Pensa a questo come al gestoreonChange
che normalmente avresti in un campo di input per tenere traccia delle modifiche. Usando questo, saremo in grado di ottenere il valore del nostro editor ogni volta che c'è una nuova modifica e salvarlo nello stato del nostro editor. Scriveremo la funzione{handleChange}
mentre procediamo. -
value = {value}
Questo è solo il contenuto dell'editor in un dato momento. Abbiamo passato un prop destrutturato denominatovalue
a questo attributo. Ilvalue
props è lo stato che detiene il valore di quell'editor. Questo verrebbe fornito dall'istanza dell'editor. -
className
="code-mirror-wrapper"
Questo nome di classe non è uno stile che creiamo noi stessi. Viene fornito dal file CSS di CodeMirror, che abbiamo importato sopra. -
options
Questo è un oggetto che prende le diverse funzionalità che vogliamo che il nostro editor abbia. Ci sono molte fantastiche opzioni in CodeMirror. Diamo un'occhiata a quelli che abbiamo usato qui:-
lineWrapping: true
Ciò significa che il codice dovrebbe andare a capo alla riga successiva quando la riga è piena. -
lint: true
Ciò consente di sfilacciare. -
mode: language
Questa modalità, come discusso in precedenza, utilizza la lingua per cui verrà utilizzato l'editor. La lingua è già stata importata sopra, ma l'editor applicherà una lingua basata sul valore dellalanguage
fornito all'editor tramite il prop. -
lineNumbers: true
Questo specifica che l'editor deve avere numeri di riga per ogni riga.
-
Successivamente, possiamo scrivere la funzione handleChange
per il gestore onBeforeChange
:
const handleChange = (editor, data, value) => { setEditorState(value); }
Il gestore onBeforeChange
ci dà accesso a tre cose: editor, data, value
.
Abbiamo solo bisogno del value
perché è quello che vogliamo passare nel nostro setEditorState
prop. La prop setEditorState
rappresenta il valore impostato per ogni stato dichiarato in App.js
, mantenendo il valore per ogni editor. Mentre andiamo avanti, vedremo come passare questo come supporto al componente Editor
.
Successivamente, aggiungeremo un menu a discesa che ci consente di selezionare diversi temi per l'editor. Quindi, diamo un'occhiata ai temi in CodeMirror.
Temi CodeMirror
CodeMirror ha più temi tra cui possiamo scegliere. Visita il sito ufficiale per vedere le demo dei diversi temi disponibili. Creiamo un menu a discesa con diversi temi tra cui l'utente può scegliere nel nostro editor. Per questo tutorial, aggiungeremo cinque temi, ma puoi aggiungerne quanti ne vuoi.
Innanzitutto, importiamo i nostri temi nel 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';
Quindi, crea una matrice di tutti i temi che abbiamo importato:
const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night']
Dichiariamo un hook useState
per mantenere il valore del tema selezionato e impostiamo il tema predefinito come dracula
:
const [theme, setTheme] = useState("dracula")
Creiamo il menu a tendina:
... 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> ) ...
Nel codice sopra, abbiamo utilizzato il tag HTML label
per aggiungere un'etichetta al nostro menu a discesa, quindi abbiamo aggiunto il tag HTML select
per creare il nostro menu a discesa. Il tag option
nell'elemento select
definisce le opzioni disponibili nel menu a discesa.
Poiché dovevamo riempire il menu a discesa con i nomi dei temi nel themeArray
che abbiamo creato, abbiamo utilizzato il metodo dell'array .map
per mappare themeArray
e visualizzare i nomi individualmente utilizzando il tag option
.
Aspetta: non abbiamo finito di spiegare il codice sopra. Nel tag di select
di apertura, abbiamo passato l'attributo onChange
per tenere traccia e aggiornare lo stato del theme
ogni volta che viene selezionato un nuovo valore nel menu a discesa. Ogni volta che viene selezionata una nuova opzione nel menu a discesa, il valore viene ottenuto dall'oggetto restituito. Successivamente, utilizziamo setTheme
dal nostro hook di stato per impostare il nuovo valore in modo che sia il valore che contiene lo stato.
A questo punto, abbiamo creato il nostro menu a discesa, impostato lo stato del nostro tema e scritto la nostra funzione per impostare lo stato con il nuovo valore. L'ultima cosa che dobbiamo fare per fare in modo che CodeMirror utilizzi il nostro tema è passare il tema all'oggetto options
in ControlledEditorComponent
. Nell'oggetto options
, aggiungiamo un valore denominato theme
e impostiamo il suo valore sul valore dello stato per il tema selezionato, anch'esso denominato theme
.
Ecco come sarebbe ControlledEditorComponent
ora:
<ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} />
Ora abbiamo creato un menu a discesa di diversi temi che possono essere selezionati nell'editor.
Ecco come appare al momento il codice completo in Editor.js
:
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
C'è solo un className
di cui abbiamo bisogno per lo stile. Vai su App.css
e aggiungi il seguente stile:
.editor-container{ padding-top: 0.4%; }
Ora che i nostri editor sono pronti, torniamo ad App.js
e usiamoli lì.
src > App.js
La prima cosa che dobbiamo fare è importare il componente Editor.js
qui:
import Editor from './components/Editor';
In App.js
dichiariamo gli stati che conterranno rispettivamente il contenuto degli editor HTML, CSS e JavaScript.
const [html, setHtml] = useState(''); const [css, setCss] = useState(''); const [js, setJs] = useState('');
Se ricordi, dovremo utilizzare questi stati per contenere e fornire i contenuti dei nostri editori.
Quindi, sostituiamo i tag paragrafo ( p
) che abbiamo usato per HTML, CSS e JavaScript nei rendering condizionali con i componenti dell'editor che abbiamo appena creato e passeremo anche l'appropriato prop a ciascuna istanza dell'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 hai seguito fino ad ora, capirai cosa abbiamo fatto nel blocco di codice sopra.
Eccolo in parole povere: abbiamo sostituito i tag p
(che erano presenti come segnaposto) con istanze dei componenti dell'editor. Quindi, abbiamo fornito le loro props language
, value
e setEditorState
, rispettivamente, in modo che corrispondano ai loro stati corrispondenti.
Siamo arrivati così lontano! Ecco come appare ora la nostra app:
Introduzione agli iframe
Utilizzeremo i frame inline (iframe) per visualizzare il risultato del codice inserito nell'editor.
Secondo MDN:
L'elemento HTML Inline Frame (
<iframe>
) rappresenta un contesto di navigazione nidificato, incorporando un'altra pagina HTML in quella corrente.
Come funzionano gli Iframe in React
Gli iframe sono normalmente usati con HTML semplice. L'uso di Iframes con React non richiede molte modifiche, la principale è la conversione dei nomi degli attributi in camelcase. Un esempio di ciò è che srcdoc
diventerebbe srcDoc
.
Il futuro degli iframe sul Web
Gli iframe continuano ad essere davvero utili nello sviluppo web. Qualcosa che potresti voler controllare è Portals. Come spiega Daniel Brain:
“I portali introducono una nuova potente serie di funzionalità in questo mix. Ora è possibile creare qualcosa che sembri un iframe, che può animare e trasformarsi senza problemi e occupare l'intera finestra del browser".
Una delle cose che Portals cerca di risolvere è il problema della barra degli URL. Quando si utilizza iframe, i componenti visualizzati nell'iframe non hanno un URL univoco nella barra degli indirizzi; in quanto tale, questo potrebbe non essere ottimo per l'esperienza dell'utente, a seconda del caso d'uso. Vale la pena dare un'occhiata a Portals e ti suggerisco di farlo, ma poiché non è l'obiettivo del nostro articolo, questo è tutto ciò che dirò al riguardo qui.
Creazione dell'iframe per ospitare il nostro risultato
Andiamo avanti con il nostro tutorial creando un iframe per ospitare il risultato dei nostri editor.
return ( <div className="App"> // ... <div> <iframe srcDoc={srcDoc} title="output" sandbox="allow-scripts" frameBorder="1" width="100%" height="100%" /> </div> </div> );
Qui abbiamo creato l'iframe e lo abbiamo ospitato in un tag contenitore div
. Nell'iframe, abbiamo passato alcuni attributi di cui abbiamo bisogno:
-
srcDoc
L'attributosrcDoc
è scritto in camelcase perché questo è il modo in cui scrivere gli attributi iframe in React. Quando si utilizza un iframe, è possibile incorporare una pagina Web esterna nella pagina o eseguire il rendering del contenuto HTML specificato. Per caricare e incorporare una pagina esterna, utilizzeremo invece la proprietàsrc
. Nel nostro caso, non stiamo caricando una pagina esterna; piuttosto, vogliamo creare un nuovo documento HTML interno che contenga il nostro risultato; per questo, abbiamo bisogno dell'attributosrcDoc
. Questo attributo prende il documento HTML che vogliamo incorporare (non l'abbiamo ancora creato, ma lo faremo presto). -
title
L'attributo title viene utilizzato per descrivere il contenuto del frame inline. -
sandbox
Questa proprietà ha molti scopi. Nel nostro caso, lo stiamo utilizzando per consentire l'esecuzione di script nel nostro iframe con il valoreallow-scripts
. Poiché stiamo lavorando con un editor JavaScript, questo sarebbe utile rapidamente. -
frameBorder
Questo definisce semplicemente lo spessore del bordo dell'iframe. -
width
eheight
Questo definisce la larghezza e l'altezza dell'iframe.
Questi termini ora dovrebbero avere più senso per te. Andiamo avanti e dichiariamo lo stato che conterrà il documento modello HTML per srcDoc
. Se osservi attentamente il blocco di codice sopra, vedrai che abbiamo passato un valore all'attributo srcDoc
: srcDoc
={srcDoc}
. Usiamo il nostro useState()
React per dichiarare lo stato srcDoc
. Per fare ciò, nel file App.js
, vai dove abbiamo definito gli altri stati e aggiungi questo:
const [srcDoc, setSrcDoc] = useState(` `);
Ora che abbiamo creato lo stato, la prossima cosa da fare è visualizzare il risultato nello stato ogni volta che digitiamo nell'editor di codice. Ma quello che non vogliamo è ridisegnare il componente ad ogni singolo tasto premuto. Con questo in mente, procediamo.
Configurazione dell'iframe per visualizzare il risultato
Ogni volta che c'è un cambiamento in uno qualsiasi degli editor per HTML, CSS e JavaScript, rispettivamente, vogliamo che useEffect()
venga attivato e questo renderà il risultato aggiornato nell'iframe. Scriviamo useEffect()
per farlo nel file App.js
:
Per prima cosa, importa l' useEffect()
:
import React, { useState, useEffect } from 'react';
Scriviamo useEffect()
in questo modo:
useEffect(() => { const timeOut = setTimeout(() => { setSrcDoc( ` <html> <body>${html}</body> <style>${css}</style> <script>${js}</script> </html> ` ) }, 250); return () => clearTimeout(timeOut) }, [html, css, js])
Qui, abbiamo scritto un useEffect()
che verrà sempre eseguito ogni volta che i valori dichiarati per gli editor HTML, CSS e JavaScript vengono modificati o aggiornati.
Perché abbiamo dovuto usare setTimeout()
? Bene, se lo scrivessimo senza di esso, ogni volta che viene premuto un singolo tasto in un editor, il nostro iframe verrebbe aggiornato, e questo non è eccezionale per le prestazioni in generale. Quindi usiamo setTimeout()
per ritardare l'aggiornamento di 250 millisecondi, dandoci abbastanza tempo per sapere se l'utente sta ancora digitando. Cioè, ogni volta che l'utente preme un tasto, riavvia il conteggio, quindi l'iframe verrà aggiornato solo quando l'utente è rimasto inattivo (non digitando) per 250 millisecondi. Questo è un ottimo modo per evitare di dover aggiornare l'iframe ogni volta che viene premuto un tasto.
La prossima cosa che abbiamo fatto sopra è stata aggiornare srcDoc
con le nuove modifiche. Il componente srcDoc
, come spiegato sopra, esegue il rendering del contenuto HTML specificato nell'iframe. Nel nostro codice, abbiamo passato un modello HTML, prendendo lo stato html
che contiene il codice che l'utente ha digitato nell'editor HTML e inserendolo tra i tag body
del nostro modello. Abbiamo anche preso lo stato css
che contiene gli stili che l'utente ha digitato nell'editor CSS e lo abbiamo passato tra i tag di style
. Infine, abbiamo preso lo stato js
che contiene il codice JavaScript che l'utente ha digitato nell'editor JavaScript e lo abbiamo passato tra i tag di script
.
Si noti che nell'impostazione setSrcDoc
, abbiamo usato i backtick ( ` `
) invece delle virgolette normali ( ' '
). Questo perché i backtick ci consentono di passare i valori di stato corrispondenti, come abbiamo fatto nel codice sopra.
L'istruzione return
useEffect()
è una funzione di pulizia che cancella setTimeout()
quando è completa, per evitare perdite di memoria. La documentazione contiene ulteriori informazioni su useEffect
.
Ecco come si presenta il nostro progetto in questo momento:
Componenti aggiuntivi CodeMirror
Con i componenti aggiuntivi di CodeMirror, possiamo migliorare il nostro editor con più del tipo di funzionalità che troveremmo in altri editor di codice. Esaminiamo un esempio di tag di chiusura aggiunti automaticamente quando viene digitato un tag di apertura e un altro esempio di parentesi che si chiude automaticamente quando viene immessa la parentesi di apertura:
La prima cosa da fare è importare l'addon per questo nel nostro file App.js
:
import 'codemirror/addon/edit/closetag'; import 'codemirror/addon/edit/closebrackets';
Passiamolo nelle opzioni di ControlledEditorComponent
:
<ControlledEditorComponent ... options={{ ... autoCloseTags: true, autoCloseBrackets: true, }} />
Ora ecco cosa abbiamo:
Potresti aggiungere un sacco di questi componenti aggiuntivi al tuo editor per dargli funzionalità più ricche. Non potremmo assolutamente esaminarli tutti qui.
Ora che abbiamo finito con questo, discutiamo brevemente delle cose che potremmo fare per migliorare l'accessibilità e le prestazioni della nostra app.
Prestazioni e accessibilità della soluzione
Guardando il nostro editor di codice web, alcune cose potrebbero sicuramente essere migliorate.
Poiché abbiamo prestato attenzione principalmente alla funzionalità, potremmo aver trascurato un po' il design. Per una migliore accessibilità, ecco alcune cose che potresti fare per migliorare questa soluzione:
- Puoi impostare una classe
active
sul pulsante per l'editor attualmente aperto. L'evidenziazione del pulsante migliorerebbe l'accessibilità fornendo agli utenti una chiara indicazione dell'editor su cui stanno attualmente lavorando. - Potresti volere che l'editor occupi più spazio sullo schermo di quello che abbiamo qui. Un'altra cosa che potresti provare è far apparire l'iframe con il clic di un pulsante che è agganciato da qualche parte a lato. In questo modo si darebbe all'editor più spazio sullo schermo.
- Questo tipo di editor sarebbe utile per le persone che desiderano eseguire un rapido esercizio sul proprio dispositivo mobile, quindi sarebbe necessario adattarlo completamente al dispositivo mobile (per non parlare di entrambi i punti sul dispositivo mobile sopra).
- Attualmente, siamo in grado di cambiare il tema del componente dell'editor tra i molteplici temi che abbiamo caricato, ma il tema generale della pagina rimane lo stesso. Puoi consentire all'utente di passare da un tema scuro a uno chiaro per l'intero layout. Questo sarebbe positivo per l'accessibilità, alleviando lo sforzo sugli occhi delle persone dal guardare uno schermo luminoso per troppo tempo.
- Non abbiamo esaminato i problemi di sicurezza con il nostro iframe, principalmente perché stavamo caricando un documento HTML interno nell'iframe, piuttosto che un documento esterno. Quindi non dobbiamo considerarlo troppo attentamente perché gli iframe si adattano bene al nostro caso d'uso.
- Con gli iframe, un'altra considerazione sarebbe il tempo di caricamento della pagina, perché il contenuto caricato nell'iframe sarebbe normalmente fuori dal tuo controllo. Nella nostra app, questo non è un problema perché il nostro contenuto iframe non è esterno.
Prestazioni e accessibilità meritano molta considerazione durante la creazione di un'applicazione perché determineranno l'utilità e l'usabilità dell'applicazione per i suoi utenti.
Shedrack ha svolto un buon lavoro nello spiegare i metodi per migliorare e ottimizzare le prestazioni nelle app React. Vale la pena dare un'occhiata!
Conclusione
Lavorare su diversi progetti ci aiuta a conoscere una vasta gamma di argomenti. Ora che hai esaminato questo articolo, sentiti libero di espandere la tua esperienza sperimentando più componenti aggiuntivi per rendere più ricco l'editor di codice, rinnovando l'interfaccia utente e risolvendo i problemi di accessibilità e prestazioni descritti sopra.
- L'intera base di codice per questo progetto è disponibile su GitHub.
Ecco la demo su Codesandbox:
Collegamenti e materiale
- "I portali di Google Chrome: come gli iframe, ma migliori e peggiori", Daniel Brain
- "Ottimizzazione delle prestazioni", documentazione React
- “Manuale d'uso e guida di riferimento”, documentazione CodeMirror