Tworzenie edytora kodu internetowego

Opublikowany: 2022-03-10
Krótkie podsumowanie ↬ Jeśli jesteś programistą, który myśli o zbudowaniu platformy wymagającej edytora kodu w takiej czy innej formie, ten artykuł jest dla Ciebie. W tym artykule wyjaśniono, jak stworzyć edytor kodu internetowego, który wyświetla wyniki w czasie rzeczywistym za pomocą niektórych HTML, CSS i JavaScript.

Internetowy edytor kodu internetowego jest najbardziej przydatny, gdy nie masz możliwości korzystania z aplikacji do edycji kodu lub gdy chcesz szybko wypróbować coś w sieci za pomocą komputera lub nawet telefonu komórkowego. Jest to również ciekawy projekt do pracy, ponieważ posiadanie wiedzy na temat budowania edytora kodu da Ci pomysły, jak podejść do innych projektów, które wymagają integracji edytora kodu, aby pokazać jakąś funkcjonalność.

Oto kilka koncepcji React, które musisz znać, aby podążać za tym artykułem:

  • haczyki,
  • Struktura komponentów,
  • elementy funkcjonalne,
  • Rekwizyty.

Korzystanie z CodeMirror

Do budowy naszego edytora użyjemy biblioteki o nazwie CodeMirror. CodeMirror to wszechstronny edytor tekstu zaimplementowany w JavaScript dla przeglądarki. Jest przeznaczony szczególnie do edycji kodu i zawiera wiele trybów językowych i dodatków zapewniających bardziej zaawansowaną funkcjonalność edycji.

Bogate programistyczne API i system motywów CSS umożliwiają dostosowanie CodeMirror do Twojej aplikacji i rozszerzenie jej o nowe funkcje. Daje nam możliwość tworzenia bogatego edytora kodu, który działa w sieci i pokazuje nam wynik naszego kodu w czasie rzeczywistym.

W następnej sekcji skonfigurujemy nasz nowy projekt React i zainstalujemy biblioteki potrzebne do zbudowania naszej aplikacji internetowej.

Tworzenie nowego projektu React

Zacznijmy od stworzenia nowego projektu React. W interfejsie wiersza poleceń przejdź do katalogu, w którym chcesz utworzyć projekt, i stwórzmy aplikację React i nazwijmy ją code_editor :

 npx create-react-app code_editor

Po utworzeniu naszej nowej aplikacji React przejdźmy do katalogu tego projektu w interfejsie wiersza poleceń:

 cd code_editor

W tym miejscu musimy zainstalować dwie biblioteki: codemirror i react-codemirror2 .

 npm install codemirror react-codemirror2

Po zainstalowaniu bibliotek, których potrzebujemy do tego projektu, utwórzmy nasze zakładki i włączmy przełączanie pomiędzy trzema zakładkami, które pojawią się w naszym edytorze (dla HTML, CSS i JavaScript).

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Komponent przycisku

Zamiast tworzyć pojedyncze przyciski, zróbmy z nich komponent, który można ponownie wykorzystać. W naszym projekcie przycisk miałby trzy wystąpienia, zgodnie z trzema zakładkami, których potrzebujemy.

Utwórz folder o nazwie components w folderze src . W tym nowym folderze components utwórz plik JSX o nazwie Button.jsx .

Oto cały kod potrzebny w komponencie 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

Oto pełne wyjaśnienie tego, co zrobiliśmy powyżej:

  • Stworzyliśmy funkcjonalny komponent o nazwie Button , który następnie wyeksportowaliśmy.
  • Zdestrukturyzowaliśmy title i onClick z rekwizytów wchodzących w skład komponentu. Tutaj title byłby ciągiem tekstu, a onClick byłby funkcją, która jest wywoływana po kliknięciu przycisku.
  • Następnie użyliśmy elementu button do zadeklarowania naszego przycisku i użyliśmy atrybutów style , aby wystylizować nasz przycisk, aby wyglądał reprezentacyjnie.
  • Dodaliśmy atrybut onClick i przekazaliśmy do niego nasze zdestrukturyzowane właściwości funkcji onClick .
  • Ostatnią rzeczą, jaką zauważyliśmy w tym komponencie, jest przekazanie {title} jako zawartości tagu button . Pozwala nam to na dynamiczne wyświetlanie tytułu na podstawie tego, jaki atrybut jest przekazywany do instancji komponentu przycisku po jego wywołaniu.

Teraz, gdy stworzyliśmy komponent przycisku wielokrotnego użytku, przejdźmy dalej i przenieś nasz komponent do App.js. Przejdź do App.js i zaimportuj nowo utworzony komponent przycisku:

 import Button from './components/Button';

Aby śledzić, która karta lub edytor jest otwarta, potrzebujemy stanu deklaracji do przechowywania wartości otwartego edytora. Za pomocą haka useState React skonfigurujemy stan, w którym będzie przechowywana nazwa karty edytora, która jest aktualnie otwarta po kliknięciu przycisku tej karty.

Oto jak to robimy:

 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;

Tutaj zadeklarowaliśmy nasz stan. Przyjmuje nazwę edytora, który jest aktualnie otwarty. Ponieważ wartość html jest przekazywana jako wartość domyślna stanu, edytor HTML byłby domyślnie otwartą kartą.

Przejdźmy dalej i napiszmy funkcję, która użyje setOpenedEditor do zmiany wartości stanu po kliknięciu przycisku tabulatora.

Uwaga: Dwie karty mogą nie być otwarte w tym samym czasie, więc będziemy musieli wziąć to pod uwagę podczas pisania naszej funkcji.

Oto jak wygląda nasza funkcja o nazwie 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;

Tutaj przekazaliśmy pojedynczy argument funkcji, który jest nazwą aktualnie wybranej karty. Argument ten byłby podawany w dowolnym miejscu wywołania funkcji i przekazywana byłaby odpowiednia nazwa tej karty.

Stwórzmy trzy instancje naszego Button dla trzech potrzebnych nam kart:

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

Oto, co zrobiliśmy:

  • Zaczęliśmy od dodania tagu p , po prostu po to, aby nadać kontekst temu, o czym jest nasza aplikacja.
  • Użyliśmy znacznika div , aby zawinąć nasze przyciski zakładek. Znacznik div zawiera className , której użyjemy do stylizacji przycisków w formie siatki w pliku CSS w dalszej części tego samouczka.
  • Następnie zadeklarowaliśmy trzy instancje komponentu Button . Jeśli pamiętasz, komponent Button ma dwa atrybuty, title i onClick . W każdym wystąpieniu komponentu Button dostępne są te dwie rekwizyty.
  • Rekwizyt title przyjmuje tytuł karty.
  • Właściwość onClick przyjmuje funkcję onTabClick , którą właśnie utworzyliśmy i która przyjmuje pojedynczy argument: nazwę wybranej karty.

Na podstawie aktualnie wybranej karty użylibyśmy trójskładnikowego operatora JavaScript, aby wyświetlić kartę warunkowo. Oznacza to, że jeśli wartość stanu openedEditor jest ustawiona na html (tj setOpenedEditor('html') ), to zakładka dla sekcji HTML stanie się aktualnie widoczną zakładką. Zrozumiesz to lepiej, gdy zrobimy to poniżej:

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

Przyjrzyjmy się powyższemu kodowi w prostym języku angielskim. Jeśli wartością openedEditor jest html , wyświetl sekcję HTML. W przeciwnym razie, jeśli wartość openedEditor to css , wyświetl sekcję CSS. W przeciwnym razie, jeśli wartością nie jest ani html ani css , oznacza to, że wartość musi być js , ponieważ mamy tylko trzy możliwe wartości dla stanu openedEditor ; więc wyświetlilibyśmy zakładkę dla JavaScript.

Użyliśmy znaczników akapitu ( p ) dla różnych sekcji w warunkach operatora trójskładnikowego. W dalszej części utworzymy komponenty edytora i zastąpimy znaczniki p samymi komponentami edytora.

Zaszliśmy już tak daleko! Po kliknięciu przycisku uruchamia akcję, która ustawia kartę, którą reprezentuje, na true , dzięki czemu ta karta jest widoczna. Oto jak obecnie wygląda nasza aplikacja:

GIF przedstawiający przełącznik karty, który obecnie posiadamy.
GIF przedstawiający przełącznik karty, który obecnie posiadamy. (duży podgląd)

Dodajmy trochę CSS do kontenera div zawierającego przyciski. Chcemy, aby przyciski były wyświetlane w siatce, a nie ułożone pionowo, jak na powyższym obrazku. Przejdź do swojego pliku App.css i dodaj następujący kod:

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

Przypomnijmy, że dodaliśmy className="tab-button-container" jako atrybut w znaczniku div zawierającym przyciski z trzema zakładkami. Tutaj nadaliśmy stylowi ten kontener, używając CSS, aby ustawić jego wyświetlanie na flex . Oto wynik:

Używamy CSS, aby ustawić jego wyświetlanie na flex
(duży podgląd)

Bądź dumny z tego, ile zrobiłeś, aby dojść do tego punktu. W następnej sekcji stworzymy nasze edytory, zastępując nimi tagi p .

Tworzenie redaktorów

Ponieważ mamy już zainstalowane biblioteki, nad którymi będziemy pracować w naszym edytorze CodeMirror, stwórzmy nasz plik Editor.jsx w folderze components .

komponenty > Editor.jsx

Po utworzeniu naszego nowego pliku napiszmy w nim początkowy kod:

 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

Oto, co zrobiliśmy:

  • Zaimportowaliśmy Reacta wraz z hakiem useState , ponieważ będziemy go potrzebować.
  • Zaimportowaliśmy plik CodeMirror CSS (pochodzący z biblioteki CodeMirror, którą zainstalowaliśmy, więc nie musisz go instalować w żaden specjalny sposób).
  • Zaimportowaliśmy Controlled z react-codemirror2 , zmieniając jego nazwę na ControlledEditorComponent , aby była bardziej przejrzysta. Niedługo będziemy z tego korzystać.
  • Następnie zadeklarowaliśmy nasz komponent funkcjonalny Editor i na razie mamy instrukcję return z pustym div , z className w instrukcji return.

W naszym komponencie funkcjonalnym zdestrukturyzowaliśmy niektóre wartości z właściwości, w tym language , value i setEditorState . Te trzy właściwości zostaną dostarczone w dowolnej instancji edytora, gdy zostanie wywołana w App.js .

Użyjmy ControlledEditorComponent do napisania kodu dla naszego edytora. Oto, co zrobimy:

 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

Przejdźmy przez to, co tutaj zrobiliśmy, wyjaśniając niektóre terminy CodeMirror.

Tryby CodeMirror określają, dla jakiego języka przeznaczony jest edytor. Zaimportowaliśmy trzy tryby, ponieważ mamy do tego projektu trzy edytory:

  1. XML: Ten tryb jest przeznaczony dla HTML. Używa terminu XML.
  2. JavaScript: To ( codemirror/mode/javascript/javascript ) wprowadza tryb JavaScript.
  3. CSS: To ( codemirror/mode/css/css ) wprowadza tryb CSS.

Uwaga: Ponieważ edytor jest zbudowany jako komponent wielokrotnego użytku, nie możemy umieścić w edytorze trybu bezpośredniego. Tak więc dostarczamy tryb za pośrednictwem właściwości language , którą zdestrukturyzowaliśmy. Ale to nie zmienia faktu, że tryby muszą być importowane, aby działały.

Następnie omówmy rzeczy w ControlledEditorComponent :

  • onBeforeChange
    Jest to wywoływane za każdym razem, gdy piszesz do lub usuwasz z edytora. Pomyśl o tym jak o obsłudze onChange , którą normalnie miałbyś w polu wejściowym do śledzenia zmian. Korzystając z tego, będziemy mogli uzyskać wartość naszego edytora za każdym razem, gdy pojawi się nowa zmiana i zapisać ją w stanie naszego edytora. W dalszej części napiszemy funkcję {handleChange} .
  • value = {value}
    To jest tylko zawartość edytora w danym momencie. Przekazaliśmy temu atrybutowi zdestrukturyzowaną właściwość o nazwie value . value props to stan, w którym znajduje się wartość tego edytora. Byłoby to dostarczane z instancji redaktora.
  • className ="code-mirror-wrapper"
    Ta nazwa klasy nie jest stylem, który sami tworzymy. Jest on dostarczany z pliku CSS CodeMirror, który zaimportowaliśmy powyżej.
  • options
    Jest to obiekt, który przyjmuje inną funkcjonalność, jaką chcemy mieć w naszym edytorze. W CodeMirror jest wiele niesamowitych opcji. Spójrzmy na te, których tutaj użyliśmy:
    • lineWrapping: true
      Oznacza to, że kod powinien zostać zawinięty do następnej linii, gdy linia jest pełna.
    • lint: true
      Pozwala to na szarpanie.
    • mode: language
      Ten tryb, jak omówiono powyżej, przyjmuje język, w którym będzie używany edytor. Język został już zaimportowany powyżej, ale edytor zastosuje język na podstawie wartości language dostarczonej do edytora za pomocą właściwości.
    • lineNumbers: true
      Określa to, że edytor powinien mieć numery linii dla każdej linii.

Następnie możemy napisać funkcję handleChange dla handlera onBeforeChange :

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

Handler onBeforeChange daje nam dostęp do trzech rzeczy: editor, data, value .

Potrzebujemy tylko tej value , ponieważ to właśnie chcemy przekazać w naszej setEditorState . Właściwość setEditorState reprezentuje ustawioną wartość dla każdego stanu, który zadeklarowaliśmy w App.js , przechowując wartość dla każdego edytora. W dalszej części przyjrzymy się, jak przekazać to jako rekwizyt do komponentu Editor .

Następnie dodamy listę rozwijaną, która pozwoli nam wybrać różne motywy dla edytora. Spójrzmy więc na motywy w CodeMirror.

Motywy CodeMirror

CodeMirror ma wiele motywów, z których możemy wybierać. Odwiedź oficjalną stronę internetową, aby zobaczyć dema różnych dostępnych motywów. Zróbmy listę rozwijaną z różnymi motywami, z których użytkownik może wybierać w naszym edytorze. W tym samouczku dodamy pięć motywów, ale możesz dodać tyle, ile chcesz.

Najpierw zaimportujmy nasze motywy w komponencie 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';

Następnie utwórz tablicę wszystkich motywów, które zaimportowaliśmy:

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

Zadeklarujmy hak useState do przechowywania wartości wybranego motywu i ustaw domyślny motyw jako dracula :

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

Stwórzmy listę rozwijaną:

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

W powyższym kodzie użyliśmy tagu HTML label , aby dodać etykietę do naszego menu rozwijanego, a następnie dodaliśmy tag HTML select , aby utworzyć menu rozwijane. Znacznik option w elemencie select definiuje opcje dostępne na liście rozwijanej.

Ponieważ musieliśmy wypełnić menu rozwijane nazwami motywów w utworzonym przez nas themeArray , użyliśmy metody .map array do mapowania themeArray i wyświetlania nazw indywidualnie za pomocą znacznika option .

Poczekaj — nie skończyliśmy wyjaśniać powyższego kodu. W otwierającym tagu select przekazaliśmy atrybut onChange , aby śledzić i aktualizować stan theme za każdym razem, gdy z listy rozwijanej zostanie wybrana nowa wartość. Za każdym razem, gdy wybierana jest nowa opcja z listy rozwijanej, wartość jest pobierana z obiektu zwróconego do nas. Następnie używamy setTheme z naszego zaczepu stanu, aby ustawić nową wartość na wartość, którą posiada stan.

W tym momencie stworzyliśmy naszą listę rozwijaną, ustawiliśmy stan naszego motywu i napisaliśmy naszą funkcję, aby ustawić stan z nową wartością. Ostatnią rzeczą, jaką musimy zrobić, aby CodeMirror używał naszego motywu, jest przekazanie motywu do obiektu options w ControlledEditorComponent . W obiekcie options dodajmy wartość o nazwie theme i ustawmy jej wartość na wartość stanu dla wybranego motywu, również o nazwie theme .

Oto jak teraz wyglądałby ControlledEditorComponent :

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

Teraz stworzyliśmy listę różnych motywów, które można wybrać w edytorze.

Oto jak w tej chwili wygląda pełny kod w 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

Jest tylko jedna className , którą musimy zmienić. Przejdź do App.css i dodaj następujący styl:

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

Teraz, gdy nasze edytory są gotowe, wróćmy do App.js i tam ich używaj.

src > App.js

Pierwszą rzeczą, którą musimy zrobić, to zaimportować tutaj komponent Editor.js :

 import Editor from './components/Editor';

W App.js zadeklarujmy stany, które będą przechowywać zawartość odpowiednio edytorów HTML, CSS i JavaScript.

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

Jeśli pamiętasz, będziemy musieli użyć tych stanów do przechowywania i dostarczania treści naszych redaktorów.

Następnie zastąpmy znaczniki paragrafu ( p ), które użyliśmy dla HTML, CSS i JavaScript w renderowaniu warunkowym, na komponenty edytora, które właśnie stworzyliśmy, a także przekażemy odpowiednią właściwość do każdej instancji edytora składnik:

 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;

Jeśli do tej pory śledziłeś, zrozumiesz, co zrobiliśmy w powyższym bloku kodu.

Tutaj jest w prostym języku angielskim: Zamieniliśmy znaczniki p (które były tam jako symbole zastępcze) na wystąpienia komponentów edytora. Następnie dostarczyliśmy ich właściwości, odpowiednio, language , value i setEditorState , aby dopasować odpowiadające im stany.

Zaszliśmy tak daleko! Oto jak teraz wygląda nasza aplikacja:

Jak teraz wygląda nasza aplikacja
(duży podgląd)

Wprowadzenie do ramek iframe

Będziemy korzystać z wbudowanych ramek (iframe) do wyświetlania wyniku kodu wprowadzonego w edytorze.

Według MDN:

Element HTML Inline Frame ( <iframe> ) reprezentuje zagnieżdżony kontekst przeglądania, osadzając inną stronę HTML w bieżącej.

Jak działają iframe w React

Ramki iframe są zwykle używane ze zwykłym kodem HTML. Używanie iframe z Reactem nie wymaga wielu zmian, z których najważniejszą jest konwersja nazw atrybutów na camelcase. Przykładem tego jest to, że srcdoc stanie się srcDoc .

Przyszłość ramek iframe w sieci

Ramki iframe nadal są bardzo przydatne w tworzeniu stron internetowych. Coś, co możesz chcieć sprawdzić, to portale. Jak wyjaśnia Daniel Brain:

„Portale wprowadzają do tej mieszanki nowy, potężny zestaw funkcji. Teraz możliwe jest zbudowanie czegoś, co przypomina element iframe, który może płynnie animować i przekształcać, a także przejąć całe okno przeglądarki”.

Jedną z rzeczy, które portale próbują rozwiązać, jest problem z paskiem adresu URL. Podczas korzystania z iframe komponenty renderowane w iframe nie mają unikalnego adresu URL w pasku adresu; w związku z tym może to nie być dobre dla wygody użytkownika, w zależności od przypadku użycia. Portale są warte sprawdzenia i sugeruję, abyś to zrobił, ale ponieważ nie jest to główny temat naszego artykułu, to wszystko, co tutaj powiem.

Tworzenie iframe, aby pomieścić nasz wynik

Przejdźmy dalej z naszym samouczkiem, tworząc ramkę iframe do przechowywania wyników naszych edytorów.

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

Tutaj stworzyliśmy element iframe i umieściliśmy go w tagu kontenera div . W iframe przekazaliśmy kilka atrybutów, których potrzebujemy:

  • srcDoc
    Atrybut srcDoc jest napisany w camelcase, ponieważ tak można zapisywać atrybuty iframe w React. Używając elementu iframe, możemy albo osadzić zewnętrzną stronę internetową na stronie, albo wyrenderować określoną zawartość HTML. Aby załadować i osadzić stronę zewnętrzną, użyjemy zamiast tego właściwości src . W naszym przypadku nie ładujemy zewnętrznej strony; raczej chcemy stworzyć nowy wewnętrzny dokument HTML, który zawiera nasz wynik; w tym celu potrzebujemy atrybutu srcDoc . Atrybut ten pobiera dokument HTML, który chcemy osadzić (jeszcze go nie stworzyliśmy, ale wkrótce to zrobimy).
  • title
    Atrybut title służy do opisu zawartości ramki wbudowanej.
  • sandbox
    Ta właściwość ma wiele celów. W naszym przypadku używamy go, aby umożliwić uruchamianie skryptów w naszym iframe z wartością allow-scripts . Ponieważ pracujemy z edytorem JavaScript, przydałoby się to szybko.
  • frameBorder
    Definiuje to jedynie grubość obramowania elementu iframe.
  • width i height
    Definiuje szerokość i wysokość elementu iframe.

Te terminy powinny teraz mieć dla Ciebie więcej sensu. Przejdźmy dalej i zadeklarujmy stan, w którym będzie przechowywany dokument szablonu HTML dla srcDoc . Jeśli przyjrzysz się uważnie powyższemu blokowi kodu, zobaczysz, że przekazaliśmy wartość do atrybutu srcDoc : srcDoc ={srcDoc} . Użyjmy naszego useState() React, aby zadeklarować stan srcDoc . Aby to zrobić, w pliku App.js przejdź do miejsca, w którym zdefiniowaliśmy inne stany i dodaj ten:

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

Teraz, gdy stworzyliśmy stan, następną rzeczą do zrobienia jest wyświetlenie wyniku w stanie za każdym razem, gdy wpisujemy w edytorze kodu. Ale nie chcemy ponownie renderować komponentu po każdym naciśnięciu klawisza. Mając to na uwadze, przejdźmy dalej.

Konfigurowanie iframe do wyświetlania wyniku

Za każdym razem, gdy nastąpi zmiana w dowolnym edytorze, odpowiednio dla HTML, CSS i JavaScript, chcemy, aby useEffect() była wyzwalana, co spowoduje wyrenderowanie zaktualizowanego wyniku w ramce iframe. useEffect() , aby to zrobić w pliku App.js :

Najpierw zaimportuj useEffect() :

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

useEffect() tak:

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

W tym miejscu napisaliśmy useEffect() , który będzie się uruchamiał zawsze, gdy wartość zadeklarowana dla edytorów HTML, CSS i JavaScript zostanie zmieniona lub zaktualizowana.

Dlaczego musieliśmy użyć setTimeout() ? Cóż, jeśli napisaliśmy to bez tego, to za każdym razem, gdy w edytorze zostanie naciśnięty jeden klawisz, nasz element iframe zostanie zaktualizowany, a to ogólnie nie jest dobre dla wydajności. Dlatego używamy setTimeout() , aby opóźnić aktualizację o 250 milisekund, co daje nam wystarczająco dużo czasu, aby wiedzieć, czy użytkownik nadal pisze. Oznacza to, że za każdym razem, gdy użytkownik naciśnie klawisz, zliczanie jest ponownie uruchamiane, więc ramka iframe zostanie zaktualizowana tylko wtedy, gdy użytkownik będzie bezczynny (bez pisania) przez 250 milisekund. Jest to fajny sposób na uniknięcie konieczności aktualizowania ramki iframe za każdym razem, gdy zostanie naciśnięty klawisz.

Następną rzeczą, którą zrobiliśmy powyżej, była aktualizacja srcDoc o nowe zmiany. Komponent srcDoc , jak wyjaśniliśmy powyżej, renderuje określoną zawartość HTML w ramce iframe. W naszym kodzie przekazaliśmy szablon HTML, pobierając stan html zawierający kod wpisany przez użytkownika w edytorze HTML i umieszczając go między tagami body naszego szablonu. Wzięliśmy również stan css , który zawiera style, które użytkownik wpisał w edytorze CSS, i przekazaliśmy go między tagami style . Na koniec wzięliśmy stan js zawierający kod JavaScript wpisany przez użytkownika w edytorze JavaScript i przekazaliśmy go między tagami script .

Zauważ, że w ustawieniu setSrcDoc użyliśmy backticków ( ` ` ) zamiast normalnych cudzysłowów ( ' ' ). Dzieje się tak, ponieważ backticki pozwalają nam przekazać odpowiednie wartości stanu, tak jak to zrobiliśmy w powyższym kodzie.

Instrukcja return w useEffect() jest funkcją czyszczącą, która czyści setTimeout() po jej zakończeniu, aby uniknąć wycieku pamięci. Dokumentacja zawiera więcej informacji o useEffect .

Oto jak obecnie wygląda nasz projekt:

Jak obecnie wygląda nasz projekt
(duży podgląd)

Dodatki do CodeMirror

Dzięki dodatkom do CodeMirror możemy wzbogacić nasz edytor o więcej funkcji, które można znaleźć w innych edytorach kodu. Przejrzyjmy przykład automatycznego dodawania tagów zamykających po wpisaniu tagu otwierającego oraz inny przykład nawiasu zamykającego się automatycznie po wprowadzeniu nawiasu otwierającego:

Pierwszą rzeczą do zrobienia jest zaimportowanie dodatku do naszego pliku App.js :

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

Przekażmy to w opcjach ControlledEditorComponent :

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

Oto, co mamy:

Jak wygląda nasz projekt
(duży podgląd)

Możesz dodać mnóstwo tych dodatków do swojego edytora, aby uzyskać bogatsze funkcje. Nie moglibyśmy przejrzeć ich wszystkich tutaj.

Teraz, gdy już z tym skończyliśmy, omówmy krótko, co możemy zrobić, aby poprawić dostępność i wydajność naszej aplikacji.

Wydajność i dostępność rozwiązania

Patrząc na nasz edytor kodu internetowego, niektóre rzeczy można zdecydowanie poprawić.

Ponieważ zwracaliśmy uwagę przede wszystkim na funkcjonalność, być może trochę zaniedbaliśmy projektowanie. Aby uzyskać lepszą dostępność, oto kilka rzeczy, które możesz zrobić, aby ulepszyć to rozwiązanie:

  1. Możesz ustawić active klasę na przycisku dla aktualnie otwartego edytora. Wyróżnienie przycisku poprawiłoby dostępność, dając użytkownikom jasne wskazanie, nad którym edytorem aktualnie pracują.
  2. Możesz chcieć, aby edytor zajmował więcej miejsca na ekranie niż to, co tutaj mamy. Inną rzeczą, którą możesz spróbować, jest wyskakujące okienko iframe za pomocą kliknięcia przycisku zadokowanego gdzieś z boku. Dzięki temu edytor miałby więcej miejsca na ekranie.
  3. Ten rodzaj edytora byłby przydatny dla osób, które chcą wykonać szybkie ćwiczenie na swoim urządzeniu mobilnym, więc konieczne byłoby pełne dostosowanie go do urządzenia mobilnego (nie wspominając o obu powyższych punktach dotyczących urządzeń mobilnych).
  4. Obecnie możemy przełączać motyw komponentu edytora spośród wielu motywów, które załadowaliśmy, ale ogólny motyw strony pozostaje taki sam. Możesz umożliwić użytkownikowi przełączanie między ciemnym i jasnym motywem dla całego układu. Byłoby to dobre dla dostępności, odciążając oczy ludzi od zbyt długiego patrzenia na jasny ekran.
  5. Nie przyjrzeliśmy się problemom z bezpieczeństwem naszego elementu iframe, głównie dlatego, że ładowaliśmy wewnętrzny dokument HTML do elementu iframe, a nie dokument zewnętrzny. Nie musimy więc rozważać tego zbyt uważnie, ponieważ ramki iframe dobrze pasują do naszego przypadku użycia.
  6. W przypadku elementów iframe kolejną kwestią jest czas wczytywania strony, ponieważ treść ładowana w iframe normalnie byłaby poza kontrolą. W naszej aplikacji nie stanowi to problemu, ponieważ nasza zawartość iframe nie jest zewnętrzna.

Wydajność i dostępność są warte dużo uwagi podczas kompilowania dowolnej aplikacji, ponieważ określają one, jak użyteczna i użyteczna jest Twoja aplikacja dla jej użytkowników.

Shedrack wykonał dobrą robotę, wyjaśniając metody poprawy i optymalizacji wydajności w aplikacjach React. Warto to sprawdzić!

Wniosek

Praca nad różnymi projektami pomaga nam poznać szeroką gamę tematów. Teraz, po zapoznaniu się z tym artykułem, możesz swobodnie poszerzyć swoje doświadczenie, eksperymentując z większą liczbą dodatków, aby wzbogacić edytor kodu, przerobić interfejs użytkownika i naprawić opisane powyżej problemy z dostępnością i wydajnością.

  • Cała baza kodu dla tego projektu jest dostępna na GitHub.

Oto demo na Codesandbox:

Linki i materiały

  • „Portale Google Chrome: jak iframe, ale lepsze i gorsze”, Daniel Brain
  • „Optymalizacja wydajności”, dokumentacja React
  • „Podręcznik użytkownika i przewodnik referencyjny”, dokumentacja CodeMirror