Создание редактора веб-кода

Опубликовано: 2022-03-10
Краткое резюме ↬ Если вы разработчик и думаете о создании платформы, для которой в той или иной форме требуется редактор кода, то эта статья для вас. В этой статье объясняется, как создать редактор веб-кода, отображающий результат в режиме реального времени с помощью HTML, CSS и JavaScript.

Онлайн-редактор веб-кода наиболее полезен, когда у вас нет возможности использовать приложение для редактирования кода или когда вы хотите быстро опробовать что-то в Интернете на своем компьютере или даже на мобильном телефоне. Это также интересный проект для работы, потому что знание того, как создать редактор кода, даст вам идеи о том, как подойти к другим проектам, требующим интеграции редактора кода для демонстрации некоторых функций.

Вот несколько концепций React, которые вам нужно знать, чтобы следовать этой статье:

  • крючки,
  • Компонентная структура,
  • функциональные компоненты,
  • Реквизит.

Использование CodeMirror

Мы будем использовать библиотеку CodeMirror для создания нашего редактора. CodeMirror — универсальный текстовый редактор, реализованный на JavaScript для браузера. Он специально предназначен для редактирования кода и поставляется с рядом языковых режимов и надстроек для более продвинутых функций редактирования.

Богатый программный API и система тем CSS доступны для настройки CodeMirror в соответствии с вашим приложением и расширения его новыми функциями. Это дает нам функциональность для создания богатого редактора кода, который работает в Интернете и показывает нам результат нашего кода в режиме реального времени.

В следующем разделе мы настроим наш новый проект React и установим библиотеки, необходимые для создания нашего веб-приложения.

Создание нового проекта React

Начнем с создания нового проекта React. В интерфейсе командной строки перейдите в каталог, в котором вы хотите создать свой проект, и давайте создадим приложение React и назовем его code_editor :

 npx create-react-app code_editor

Создав наше новое приложение React, давайте перейдем к каталогу этого проекта в интерфейсе командной строки:

 cd code_editor

Здесь нам нужно установить две библиотеки: codemirror и react-codemirror2 .

 npm install codemirror react-codemirror2

Установив необходимые для этого проекта библиотеки, давайте создадим наши вкладки и включим переключение вкладок между тремя вкладками, которые появятся в нашем редакторе (для HTML, CSS и JavaScript).

Еще после прыжка! Продолжить чтение ниже ↓

Компонент кнопки

Вместо создания отдельных кнопок давайте сделаем кнопку компонентом, который можно использовать повторно. В нашем проекте кнопка будет иметь три экземпляра в соответствии с тремя необходимыми вкладками.

Создайте папку с именем components в папке src . В этой новой папке components создайте файл JSX с именем Button.jsx .

Вот весь код, необходимый для компонента 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

Вот полное объяснение того, что мы сделали выше:

  • Мы создали функциональный компонент с именем Button , который затем экспортировали.
  • Мы деструктурировали title и onClick из реквизитов, поступающих в компонент. Здесь title будет строкой текста, а onClick будет функцией, которая вызывается при нажатии кнопки.
  • Затем мы использовали элемент button для объявления нашей кнопки и использовали атрибуты style , чтобы придать нашей кнопке презентабельный вид.
  • Мы добавили атрибут onClick и передали ему реквизиты нашей деструктурированной функции onClick .
  • Последнее, что вы заметите, мы сделали в этом компоненте, это передали {title} в качестве содержимого тега button . Это позволяет нам отображать заголовок динамически, основываясь на том, какое свойство передается экземпляру компонента кнопки при его вызове.

Теперь, когда мы создали повторно используемый компонент кнопки, давайте продолжим и перенесем наш компонент в App.js. Перейдите в App.js и импортируйте только что созданный компонент кнопки:

 import Button from './components/Button';

Чтобы отслеживать, какая вкладка или редактор открыты, нам нужно состояние объявления для хранения значения открытого редактора. Используя useState React, мы настроим состояние, в котором будет храниться имя вкладки редактора, которая в данный момент открыта при нажатии кнопки этой вкладки.

Вот как мы это делаем:

 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;

Здесь мы объявили наше состояние. Он принимает имя открытого в данный момент редактора. Поскольку значение html передается как значение состояния по умолчанию, HTML-редактор будет открытой вкладкой по умолчанию.

Давайте продолжим и напишем функцию, которая будет использовать setOpenedEditor для изменения значения состояния при нажатии кнопки вкладки.

Примечание. Две вкладки не могут быть открыты одновременно, поэтому мы должны учитывать это при написании нашей функции.

Вот как выглядит наша функция с именем 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;

Здесь мы передали единственный аргумент функции, который является именем выбранной в данный момент вкладки. Этот аргумент будет передаваться везде, где вызывается функция, и будет передано соответствующее имя этой вкладки.

Давайте создадим три экземпляра нашей Button для трех необходимых нам вкладок:

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

Вот что мы сделали:

  • Мы начали с добавления тега p , в основном просто для того, чтобы дать некоторый контекст тому, что представляет собой наше приложение.
  • Мы использовали тег div , чтобы обернуть наши кнопки вкладок. Тег div содержит className , которое мы будем использовать для оформления кнопок в виде сетки в файле CSS позже в этом руководстве.
  • Затем мы объявили три экземпляра компонента Button . Если вы помните, компонент Button принимает два реквизита: title и onClick . Эти два реквизита предоставляются в каждом экземпляре компонента Button .
  • Свойство title принимает заголовок вкладки.
  • Свойство onClick принимает функцию onTabClick , которую мы только что создали и которая принимает один аргумент: имя выбранной вкладки.

В зависимости от выбранной в данный момент вкладки мы будем использовать тернарный оператор JavaScript для условного отображения вкладки. Это означает, что если значение состояния openedEditor установлено в html (т.е. setOpenedEditor('html') ), то вкладка для раздела HTML станет видимой в данный момент вкладкой. Вы поймете это лучше, когда мы сделаем это ниже:

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

Давайте рассмотрим приведенный выше код на простом английском языке. Если значение openedEditor равно html , отобразите раздел HTML. В противном случае, если значение openedEditor равно css , отобразите раздел CSS. В противном случае, если значение не равно ни html , ни css , это означает, что значение должно быть js , потому что у нас есть только три возможных значения для состояния openedEditor ; Итак, мы бы отобразили вкладку для JavaScript.

Мы использовали теги абзаца ( p ) для разных разделов в условиях тернарного оператора. По мере продвижения мы создадим компоненты редактора и заменим теги p самими компонентами редактора.

Мы уже зашли так далеко! Когда кнопка нажата, она запускает действие, которое устанавливает для вкладки, которую она представляет, значение true , делая эту вкладку видимой. Вот как сейчас выглядит наше приложение:

GIF-файл, показывающий переключатель вкладок, который у нас есть.
GIF-файл, показывающий переключатель вкладок, который у нас есть. (Большой превью)

Давайте добавим немного CSS в контейнер div , содержащий кнопки. Мы хотим, чтобы кнопки отображались в виде сетки, а не располагались вертикально, как на изображении выше. Перейдите в свой файл App.css и добавьте следующий код:

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

Напомним, что мы добавили className="tab-button-container" в качестве атрибута в тег div , содержащего кнопки с тремя вкладками. Здесь мы стилизовали этот контейнер, используя CSS, чтобы настроить его отображение на flex . Вот результат:

Мы используем CSS, чтобы настроить его отображение на гибкость.
(Большой превью)

Гордитесь тем, как много вы сделали, чтобы добраться до этой точки. В следующем разделе мы создадим наши редакторы, заменив ими теги p .

Создание редакторов

Поскольку мы уже установили библиотеки, над которыми будем работать, в нашем редакторе CodeMirror, давайте продолжим и создадим наш файл Editor.jsx в папке components .

компоненты > Editor.jsx

Создав наш новый файл, напишем в нем исходный код:

 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

Вот что мы сделали:

  • Мы импортировали React вместе с хуком useState , потому что он нам понадобится.
  • Мы импортировали CSS-файл CodeMirror (который взят из библиотеки CodeMirror, которую мы установили, поэтому вам не нужно устанавливать его каким-то особым образом).
  • Мы импортировали Controlled из react-codemirror2 , переименовав его в ControlledEditorComponent , чтобы сделать его более понятным. Мы будем использовать это в ближайшее время.
  • Затем мы объявили наш функциональный компонент Editor , и у нас есть оператор return с пустым div , с className в операторе return на данный момент.

В нашем функциональном компоненте мы деструктурировали некоторые значения из реквизита, включая language , value и setEditorState . Эти три реквизита будут предоставлены в любом экземпляре редактора, когда он вызывается в App.js

Давайте воспользуемся ControlledEditorComponent для написания кода нашего редактора. Вот что мы сделаем:

 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

Давайте пройдемся по тому, что мы здесь сделали, объяснив некоторые термины CodeMirror.

Режимы CodeMirror определяют, для какого языка предназначен редактор. Мы импортировали три режима, потому что у нас есть три редактора для этого проекта:

  1. XML: этот режим предназначен для HTML. Он использует термин XML.
  2. JavaScript: это ( codemirror/mode/javascript/javascript ) вводит режим JavaScript.
  3. CSS: это ( codemirror/mode/css/css ) вводит режим CSS.

Примечание. Поскольку редактор построен как компонент, который можно использовать повторно, мы не можем поместить в редактор прямой режим. Итак, мы обеспечиваем режим через language опору, которую мы деструктурировали. Но это не меняет того факта, что для работы режимы необходимо импортировать.

Далее давайте обсудим вещи в ControlledEditorComponent :

  • onBeforeChange
    Это вызывается каждый раз, когда вы пишете или удаляете из редактора. Думайте об этом как об обработчике onChange , который вы обычно используете в поле ввода для отслеживания изменений. Используя это, мы сможем получать значение нашего редактора в любое время при появлении нового изменения и сохранять его в состоянии нашего редактора. По мере продвижения мы напишем функцию {handleChange} .
  • value = {value}
    Это просто содержимое редактора в любой момент времени. Мы передали этому атрибуту деструктурированное свойство с именем value . value props — это состояние, содержащее значение этого редактора. Это будет предоставлено экземпляром редактора.
  • className ="code-mirror-wrapper"
    Это имя класса не является стилем, который мы делаем сами. Он поставляется из CSS-файла CodeMirror, который мы импортировали выше.
  • options
    Это объект, который использует другую функциональность, которую мы хотим, чтобы наш редактор имел. В CodeMirror есть много удивительных опций. Давайте посмотрим на те, которые мы использовали здесь:
    • lineWrapping: true
      Это означает, что код должен переноситься на следующую строку, когда строка заполнена.
    • lint: true
      Это позволяет линтинг.
    • mode: language
      Этот режим, как обсуждалось выше, использует язык, для которого будет использоваться редактор. Язык уже был импортирован выше, но редактор собирается применить язык на основе значения language , предоставленного редактору через свойство.
    • lineNumbers: true
      Это указывает, что редактор должен иметь номера строк для каждой строки.

Далее мы можем написать функцию handleChange для обработчика onBeforeChange :

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

Обработчик onBeforeChange дает нам доступ к трем вещам: editor, data, value .

Нам нужно только value , потому что это то, что мы хотим передать в нашей реквизите setEditorState . Свойство setEditorState представляет заданное значение для каждого состояния, которое мы объявили в App.js , и содержит значение для каждого редактора. По мере продвижения мы рассмотрим, как передать это в качестве реквизита компоненту Editor .

Далее мы добавим раскрывающийся список, который позволит нам выбирать различные темы для редактора. Итак, давайте посмотрим на темы в CodeMirror.

Темы CodeMirror

CodeMirror имеет несколько тем, из которых мы можем выбирать. Посетите официальный сайт, чтобы увидеть демонстрации различных доступных тем. Давайте создадим раскрывающийся список с различными темами, которые пользователь может выбрать в нашем редакторе. В этом уроке мы добавим пять тем, но вы можете добавить столько, сколько захотите.

Во-первых, давайте импортируем наши темы в компонент 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';

Затем создайте массив всех тем, которые мы импортировали:

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

Давайте объявим хук useState для хранения значения выбранной темы и установим тему по умолчанию как dracula :

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

Давайте создадим раскрывающийся список:

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

В приведенном выше коде мы использовали HTML-тег label , чтобы добавить метку в раскрывающийся список, а затем добавили HTML-тег select , чтобы создать раскрывающийся список. Тег option в элементе select определяет параметры, доступные в раскрывающемся списке.

Поскольку нам нужно было заполнить раскрывающийся список именами тем в созданном нами themeArray тем, мы использовали метод массива .map для сопоставления themeArray и отображения имен по отдельности с помощью тега option .

Подождите — мы еще не закончили объяснять приведенный выше код. В открывающем теге select мы передали атрибут onChange для отслеживания и обновления состояния theme всякий раз, когда в раскрывающемся списке выбирается новое значение. Всякий раз, когда в раскрывающемся списке выбирается новый параметр, значение извлекается из возвращаемого нам объекта. Затем мы используем setTheme из нашего хука состояния, чтобы установить новое значение как значение, которое содержится в состоянии.

На данный момент мы создали раскрывающийся список, настроили состояние нашей темы и написали нашу функцию для установки состояния с новым значением. Последнее, что нам нужно сделать, чтобы CodeMirror использовал нашу тему, — это передать тему объекту options в ControlledEditorComponent . В объект options давайте добавим значение с именем theme и установим его значение равным значению состояния для выбранной темы, также названному theme .

Вот как теперь будет выглядеть ControlledEditorComponent :

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

Теперь мы сделали раскрывающийся список различных тем, которые можно выбрать в редакторе.

Вот как на данный момент выглядит полный код в 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

Нам нужно стилизовать только одно className . Перейдите в App.css и добавьте следующий стиль:

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

Теперь, когда наши редакторы готовы, давайте вернемся к App.js и воспользуемся ими там.

исходный код > App.js

Первое, что нам нужно сделать, это импортировать сюда компонент Editor.js :

 import Editor from './components/Editor';

В App.js давайте объявим состояния, которые будут содержать содержимое редакторов HTML, CSS и JavaScript соответственно.

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

Если вы помните, нам нужно будет использовать эти состояния для хранения и предоставления содержимого наших редакторов.

Затем давайте заменим теги абзаца ( p ), которые мы использовали для HTML, CSS и JavaScript в условных рендерингах, только что созданными компонентами редактора, а также передадим соответствующий реквизит каждому экземпляру редактора. компонент:

 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;

Если вы следили за этим до сих пор, вы поймете, что мы сделали в блоке кода выше.

Вот это простым языком: мы заменили теги p (которые были там в качестве заполнителей) экземплярами компонентов редактора. Затем мы предоставили их свойства language , value и setEditorState соответственно, чтобы они соответствовали их соответствующим состояниям.

Мы зашли так далеко! Вот как сейчас выглядит наше приложение:

Как наше приложение выглядит сейчас
(Большой превью)

Введение в фреймы

Мы будем использовать встроенные фреймы (iframes) для отображения результата кода, введенного в редакторе.

Согласно МДН:

Элемент встроенного фрейма HTML ( <iframe> ) представляет вложенный контекст просмотра, встраивая другую HTML-страницу в текущую.

Как работают фреймы в React

Iframes обычно используются с простым HTML. Использование фреймов с React не требует много изменений, основным из которых является преобразование имен атрибутов в верблюжий регистр. Примером этого является то, что srcdoc станет srcDoc .

Будущее фреймов в Интернете

Iframes продолжают быть очень полезными в веб-разработке. Что-то, что вы, возможно, захотите проверить, это порталы. Как объясняет Дэниел Брейн:

«Порталы привносят в этот микс новый мощный набор возможностей. Теперь можно создать что-то похожее на iframe, которое может плавно анимировать и трансформироваться и занимать все окно браузера».

Одной из проблем, которую Portals пытается решить, является проблема с адресной строкой. При использовании iframe компоненты, отображаемые в iframe, не содержат уникальный URL-адрес в адресной строке; как таковой, это может быть не очень удобно для пользователя, в зависимости от варианта использования. Порталы стоит проверить, и я бы посоветовал вам это сделать, но, поскольку это не является предметом нашей статьи, это все, что я скажу об этом здесь.

Создание iframe для размещения нашего результата

Давайте продолжим наше руководство, создав iframe для размещения результатов наших редакторов.

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

Здесь мы создали iframe и разместили его в теге контейнера div . В iframe мы передали некоторые атрибуты, которые нам нужны:

  • srcDoc
    srcDoc написан на верблюжьем регистре, потому что именно так пишутся атрибуты iframe в React. При использовании iframe мы можем либо встроить внешнюю веб-страницу на страницу, либо отобразить указанный HTML-контент. Чтобы загрузить и внедрить внешнюю страницу, вместо этого мы будем использовать свойство src . В нашем случае мы не загружаем внешнюю страницу; скорее, мы хотим создать новый внутренний HTML-документ, в котором будет размещен наш результат; для этого нам понадобится атрибут srcDoc . Этот атрибут принимает HTML-документ, который мы хотим встроить (мы еще не создали его, но скоро создадим).
  • title
    Атрибут title используется для описания содержимого встроенного фрейма.
  • sandbox
    Это свойство имеет много целей. В нашем случае мы используем его, чтобы разрешить запуск скриптов в нашем iframe со значением allow-scripts . Поскольку мы работаем с редактором JavaScript, это может быстро пригодиться.
  • frameBorder
    Это просто определяет толщину границы iframe.
  • width и height
    Это определяет ширину и высоту iframe.

Теперь эти термины должны иметь для вас больше смысла. Давайте продолжим и объявим состояние, в котором будет храниться документ HTML-шаблона для srcDoc . Если вы внимательно посмотрите на блок кода выше, то увидите, что мы передали значение srcDoc : srcDoc ={srcDoc} . Давайте используем наш React-хук useState() для объявления состояния srcDoc . Для этого в файле App.js переходим туда, где мы определяли остальные состояния и добавляем вот это:

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

Теперь, когда мы создали состояние, следующее, что нужно сделать, — отображать результат в состоянии всякий раз, когда мы вводим текст в редакторе кода. Но чего мы не хотим, так это перерисовывать компонент при каждом нажатии клавиши. Имея это в виду, давайте продолжим.

Настройка iframe для отображения результата

Каждый раз, когда происходит изменение в любом из редакторов для HTML, CSS и JavaScript, соответственно, мы хотим, чтобы useEffect() запускался, и это будет отображать обновленный результат в iframe. Давайте напишем useEffect() для этого в файле App.js :

Во-первых, импортируйте useEffect() :

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

Давайте напишем useEffect() так:

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

Здесь мы написали useEffect() , который всегда будет запускаться всякий раз, когда состояния значений, объявленные нами для редакторов HTML, CSS и JavaScript, изменяются или обновляются.

Зачем нам нужно было использовать setTimeout() ? Ну, если бы мы написали это без него, то каждый раз, когда в редакторе делается одно нажатие клавиши, наш iframe будет обновляться, а это не очень хорошо для производительности в целом. Поэтому мы используем setTimeout() , чтобы отложить обновление на 250 миллисекунд, что дает нам достаточно времени, чтобы узнать, печатает ли пользователь. То есть каждый раз, когда пользователь нажимает клавишу, счетчик перезапускается, поэтому iframe будет обновляться только тогда, когда пользователь бездействует (не печатает) в течение 250 миллисекунд. Это отличный способ избежать необходимости обновлять iframe каждый раз, когда нажимается клавиша.

Следующее, что мы сделали выше, это обновили srcDoc новыми изменениями. Компонент srcDoc , как мы объяснили выше, отображает указанный HTML-контент в iframe. В нашем коде мы передали шаблон HTML, взяв состояние html , содержащее код, введенный пользователем в редакторе HTML, и поместив его между тегами body нашего шаблона. Мы также взяли состояние css , содержащее стили, введенные пользователем в редакторе CSS, и передали его между тегами style . Наконец, мы взяли состояние js , содержащее код JavaScript, введенный пользователем в редакторе JavaScript, и передали его между тегами script .

Обратите внимание, что при установке setSrcDoc мы использовали обратные кавычки ( ` ` ) вместо обычных кавычек ( ' ' ). Это связано с тем, что обратные кавычки позволяют нам передавать соответствующие значения состояния, как мы это делали в приведенном выше коде.

Оператор return в useEffect() — это функция очистки, которая очищает setTimeout() по завершении, чтобы избежать утечки памяти. В документации есть больше информации об useEffect .

Вот как выглядит наш проект на данный момент:

Как выглядит наш проект на данный момент
(Большой превью)

Дополнения CodeMirror

С надстройками CodeMirror мы можем расширить наш редактор, добавив больше функций, которые мы могли бы найти в других редакторах кода. Давайте рассмотрим пример автоматического добавления закрывающих тегов при вводе открывающего тега и другой пример автоматического закрытия скобки при вводе открывающей скобки:

Первое, что нужно сделать, это импортировать аддон для этого в наш файл App.js :

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

Давайте передадим его в параметрах ControlledEditorComponent :

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

Теперь вот что у нас есть:

Как выглядит наш проект
(Большой превью)

Вы можете добавить массу этих дополнений в свой редактор, чтобы расширить его возможности. Мы не могли пройти через все из них здесь.

Теперь, когда мы закончили с этим, давайте кратко обсудим, что мы можем сделать, чтобы улучшить доступность и производительность нашего приложения.

Производительность и доступность решения

Глядя на наш редактор веб-кода, некоторые вещи определенно можно было бы улучшить.

Поскольку мы уделяли внимание в первую очередь функциональности, мы могли немного пренебречь дизайном. Чтобы улучшить доступность, вот некоторые вещи, которые вы могли бы сделать, чтобы улучшить это решение:

  1. Вы можете установить active класс на кнопку для открытого в данный момент редактора. Выделение кнопки улучшит доступность, давая пользователям четкое представление о том, над каким редактором они сейчас работают.
  2. Вы можете захотеть, чтобы редактор занимал больше места на экране, чем здесь. Еще одна вещь, которую вы можете попробовать, — заставить iframe всплывать нажатием кнопки, которая пристыкована где-то сбоку. Это даст редактору больше места на экране.
  3. Такой редактор будет полезен для людей, которые хотят выполнить быстрое упражнение на своем мобильном устройстве, поэтому потребуется его полная адаптация к мобильным устройствам (не говоря уже об обоих пунктах о мобильных устройствах выше).
  4. В настоящее время мы можем переключать тему компонента редактора из нескольких тем, которые мы загрузили, но общая тема страницы остается прежней. Вы можете разрешить пользователю переключаться между темной и светлой темой для всего макета. Это было бы хорошо для доступности, снимая нагрузку на глаза людей от слишком долгого взгляда на яркий экран.
  5. Мы не рассматривали проблемы безопасности с нашим iframe, главным образом потому, что мы загружали внутренний HTML-документ в iframe, а не внешний документ. Так что нам не нужно рассматривать это слишком тщательно, потому что iframes хорошо подходят для нашего варианта использования.
  6. В случае с iframe еще одним соображением будет время загрузки страницы, потому что контент, загружаемый в iframe, обычно находится вне вашего контроля. В нашем приложении это не проблема, потому что наш контент iframe не является внешним.

Производительность и доступность заслуживают большого внимания при создании любого приложения, поскольку они определяют, насколько полезно и удобно ваше приложение для пользователей.

Шедрак проделал хорошую работу по объяснению методов улучшения и оптимизации производительности в приложениях React. Это стоит проверить!

Заключение

Работа над различными проектами помогает нам узнать о широком круге предметов. Теперь, когда вы ознакомились с этой статьей, не стесняйтесь расширять свой опыт, экспериментируя с дополнительными надстройками, чтобы сделать редактор кода богаче, обновляя пользовательский интерфейс и устраняя проблемы доступности и производительности, описанные выше.

  • Вся кодовая база этого проекта доступна на GitHub.

Вот демо на Codesandbox:

Ссылки и материалы

  • «Порталы Google Chrome: как фреймы, но лучше и хуже», Дэниел Брейн
  • «Оптимизация производительности», документация React
  • «Руководство пользователя и справочное руководство», документация CodeMirror