Monaco React 공부

이창준·2024년 7월 14일

Monaco

Microsoft의 Visual Studio Code에서 사용되는 코드 편집기 컴포넌트를 웹에서 사용할 수 있게 만든 라이브러리이다. Monaco Editor는 다양한 프로그래밍 언어에 대한 구문 강조, 자동 완성, 디버깅 지원, 코드 탐색 기능을 제공한다.

주요 특징:

  1. Syntax Highlighting: 다양한 언어에 대한 구문 강조
  2. IntelliSense: 코드 자동 완성과 제안 기능
  3. Multiple Language Support: 여러 언어 지원
  4. Customizable: 확장 가능하고 사용자 정의 가능한 기능
  • 라이브러리 뜯는 순서에 대한 GPT 답변 React Monaco 라이브러리의 내부 구조를 분석하고, 코드를 이해하며, 작동 원리를 파악하려면 다음 순서로 진행하는 것이 좋습니다:
    1. 리포지토리 구조 파악
    • 먼저 리포지토리의 파일 및 디렉토리 구조를 확인합니다. 이를 통해 프로젝트의 전반적인 구조와 주요 파일들을 이해할 수 있습니다.
    • src 디렉토리와 그 하위 파일들을 특히 주의 깊게 봅니다.
    1. README 파일 읽기
    • 프로젝트의 최상위 디렉토리에 있는 README.md 파일을 읽어 프로젝트의 개요, 설치 방법, 사용법 등을 파악합니다.
    • 이 파일은 프로젝트의 주요 기능과 사용법을 간단히 설명하므로, 전체적인 이해에 도움이 됩니다.
    1. 패키지 의존성 확인
    • package.json 파일을 열어 프로젝트가 어떤 의존성을 가지고 있는지 확인합니다.
    • 여기서 사용하는 주요 라이브러리들과 그 버전을 파악합니다.
    1. 주요 컴포넌트 및 파일 분석
    • src 디렉토리 내 주요 파일들을 하나씩 살펴봅니다.
    • 특히 MonacoEditor.js 또는 MonacoEditor.tsx와 같은 파일이 있다면, 이 파일이 라이브러리의 핵심 컴포넌트이므로 자세히 분석합니다.
    • 컴포넌트의 props, state, lifecycle 메서드 등을 확인하며, 코드의 흐름을 이해합니다.
    1. 핵심 기능 이해
    • Monaco Editor를 초기화하고 사용하는 부분의 코드를 집중적으로 분석합니다.
    • Monaco Editor와의 상호작용, 이벤트 핸들링, 코드 편집 기능 등이 어떻게 구현되어 있는지 살펴봅니다.
    1. 테스트 코드 확인
    • 프로젝트에 테스트 코드가 있다면, 이를 통해 주요 기능들이 어떻게 테스트되는지 확인합니다.
    • 테스트 코드는 일반적으로 주요 기능을 검증하므로, 코드의 의도와 작동 방식을 이해하는 데 도움이 됩니다.
    1. 문서 및 주석 확인
    • 코드 내 주석과 추가 문서를 통해 상세한 설명을 확인합니다.
    • 주석은 코드의 특정 부분이 어떤 목적을 가지고 작성되었는지 이해하는 데 유용합니다.
    1. 실습
    • 직접 프로젝트를 클론하고, 개발 환경을 설정한 후, 간단한 수정이나 추가 기능을 구현해 봅니다.
    • 실습을 통해 코드를 더 깊이 이해할 수 있습니다.

[링크]

GitHub - suren-atoyan/monaco-react: Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins

구조

install

npm install @monaco-editor/react
or
yarn add @monaco-editor/react

import

import Editor, { DiffEditor, useMonaco, loader } from '@monaco-editor/react';

Editor 컴포넌트가 우리가 사용해야 하는 컴포넌트임.

current value 가져오기

  1. editor 인스턴스의 현재 모델 값을 통해서 가져오기
function App() {
	const editRef = useRef(null);

	function handleEditorDidMount(editor, monaco) {
		editorRef.current = editor;
	}
	
	function showValue() {
		alert(editorRef.current.getValue());
	}

  return (
    <>
      <button onClick={showValue}>Show value</button>
      <Editor
        height="90vh"
        defaultLanguage="javascript"
        defaultValue="// some comment"
        onMount={handleEditorDidMount}
      />
    </>
  );
}
  1. onChange prop를 통해서 가져오기
function App() {
  function handleEditorChange(value, event) {
    console.log('here is the current model value:', value);
  }

  return (
    <Editor
      height="90vh"
      defaultLanguage="javascript"
      defaultValue="// some comment"
      onChange={handleEditorChange}
    />
  );
}

DiffEditor 사용하기

DiffEditor은 한 개의 Editor에서 화면을 분할하여 두 개의 코드(original, modified)를 볼 수 있다. original코드 측은 읽기 전용으로, modified는 편집 가능으로 표시된다.

얘네는 DiffEditor에서 파생된 getOriginalEditor()과 getModifiedEditor()가 있어서 따로 불러서 처리할 수 있다.

import React, { useRef } from 'react';
import { DiffEditor } from '@monaco-editor/react';

function MyDiffEditor() {
  const diffEditorRef = useRef(null);

  function handleEditorDidMount(editor) {
    diffEditorRef.current = editor;
  }

  function runOriginalCode() {
    const originalCode = diffEditorRef.current.getOriginalEditor().getValue();
    // 실행 로직 추가
    console.log('Original Code Output:', originalCode);
  }

  function runModifiedCode() {
    const modifiedCode = diffEditorRef.current.getModifiedEditor().getValue();
    // 실행 로직 추가
    console.log('Modified Code Output:', modifiedCode);
  }

  return (
    <div>
      <DiffEditor
        height="90vh"
        original="// Original code here"
        modified="// Modified code here"
        language="javascript"
        onMount={handleEditorDidMount}
      />
      <button onClick={runOriginalCode}>Run Original Code</button>
      <button onClick={runModifiedCode}>Run Modified Code</button>
    </div>
  );
}

export default MyDiffEditor;

Editor 인스턴스

Editor mount될 때 Ref를 만들어 current값에 넣어주는 게 선행되어야 한다.

function App() {
  const editorRef = useRef(null);

  function handleEditorDidMount(editor, monaco) {
    // here is the editor instance
    // you can store it in `useRef` for further usage
    editorRef.current = editor;
  }

  return (
    <Editor
      height="90vh"
      defaultLanguage="javascript"
      defaultValue="// some comment"
      onMount={handleEditorDidMount}
    />
  );
}

Monaco 인스턴스

Monaco 인스턴스는 Monaco Editor의 핵심 객체로, Monaco Editor의 기능과 설정을 관리하는 데 사용된다. 이 인스턴스를 통해 다양한 언어 지원, 구문 강조, 코드 자동 완성, 코드 편집 및 탐색 기능 등을 사용할 수 있다.

function App() {
  const monacoRef = useRef(null);

  function handleEditorWillMount(monaco) {
    // here is the monaco instance
    // do something before editor is mounted
    monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
  }

  function handleEdito rDidMount(editor, monaco) {
    // here is another way to get monaco instance
    // you can also store it in `useRef` for further usage
    monacoRef.current = monaco;
  }

  return (
    <Editor
      height="90vh"
      defaultLanguage="javascript"
      defaultValue="// some comment"
      beforeMount={handleEditorWillMount}
      onMount={handleEditorDidMount}
    />
  );
}

handleEditorWillMount(monaco) 에서 Editor가 마운트되기 전에 Editor에 대한 설정을 할 수 있다.

useMonaco를 통해 현재 Monaco Editor 인스턴스를 반환시킬 수도 있음.

const monaco = useMonaco();

useEffect(() => {
  if (monaco) {
    console.log('Monaco 인스턴스:', monaco);
  }
}, [monaco]);

Multi-Model Editor 사용하기

멀티모델 에디터는 다중 모델로 복수의 탭을 만들어서 코드를 작성하는 구조라고 보면 된다.

주요 포인트

  1. 모델 생성
  • monaco.editor.createModel(value, language, monaco.Uri.parse(path))를 사용하여 모델을 생성.
  • path는 모델의 식별자 역할을 한다.
  1. Editor 컴포넌트의 path prop
  • path prop을 지정하면 해당 경로에 모델이 있는지 확인하고, 있으면 기존 모델을 보여주고, 없으면 새 모델을 생성한다.
  1. 다중 모델 편집기
  • 파일 경로와 모델을 연결하여 완전한 다중 모델 편집기를 구현
  • 파일 간 전환 시 이전 상태(뷰 상태, 텍스트 선택, 실행 취소 스택, 스크롤 위치 등)를 유지

#JSON형식으로 파일 정보를 적어놓은 예시

const files = {
  'script.js': {
    name: 'script.js',
    language: 'javascript',
    value: someJSCodeExample,
  },
  'style.css': {
    name: 'style.css',
    language: 'css',
    value: someCSSCodeExample,
  },
  'index.html': {
    name: 'index.html',
    language: 'html',
    value: someHTMLCodeExample,
  },
};

export default files;

#위 JSON 예시 코드를 통해 파일을 변경하는 예시

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

import Editor from '@monaco-editor/react';
import files from "./files";

function App() {
  const [fileName, setFileName] = useState('script.js');

  const file = files[fileName];

  return (
    <>
      <button disabled={fileName === 'script.js'} onClick={() => setFileName('script.js')}>
        script.js
      </button>
      <button disabled={fileName === 'style.css'} onClick={() => setFileName('style.css')}>
        style.css
      </button>
      <button disabled={fileName === 'index.html'} onClick={() => setFileName('index.html')}>
        index.html
      </button>
      <Editor
        height="80vh"
        theme="vs-dark"
        path={file.name}
        defaultLanguage={file.language}
        defaultValue={file.value}
      />
    </>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
profile
개발자가 되고싶은 먼지입니다.

0개의 댓글