기억의 실마리
2024. 9. 22. 21:42

🐻Zustand

Zustand가 가진 특징으로 가벼운 번들사이즈,

폭넓은 확장 가능성, provider가 없는 구조,

Flux패턴의 단방향 Pub/Sub 모델구조이다.

 

이러한 특징들로 요즘 대세 상태관리 라이브러리로

각광받고있다.

 

npm 공식 페이지에서 주간 다운로드 횟수를 보면

놀라운 주간 다운로드 횟수를 갱신중이다.

 

※ 2024-09-22 주요 상태관리 라이브러리 주간 다운로드 횟수

- Redux : 10,278,099

- Zustand : 4,243,907

- Redux-toolkit : 3,609,254

- Jotai : 1,177,983

- Recoil : 489,922

 

부동의 1위인 오리진 Redux를 제외한다면

Redux의 본체라 해도 무방한 Redux-toolkit 조차도 뛰어넘은

대세 상태관리 라이브러리이다.

 

이번 포스팅은 Zustand를 React에서 사용한다는 것을

기반으로 Zustand의 특징과 간단한 예제코드를

포스팅하고자 한다.

 

🪶가벼운 번들사이즈

Zustand는 획기적으로 가벼운 번들사이즈를 자랑한다.

고작 1.16kb의 사이즈 만으로 전체 상태를 용이하게

관리할 수 있어 프로젝트 자체의 용량을 더욱 가볍게

가져갈 수 있다.

 

🪝확장 가능성

redux와 같은 Flux패턴이기 때문에 redux의

reducer와 같은 개념으로 확장하여 유지보수성을

높일 수 있고 Zustand에서 제공하는 미들웨어를 통해

Redux dev-tools를 활용할 수 있어 디버깅에도

용이하게 확장이 가능하다.

 

🧺Provider가 없는 구조

이는 단순하게 생각하면 provider가 없어서 어쩌라고?

라는 생각이 들 수 있다. 하지만 React의 구조가 이를

엄청난 최적화요소로 만들어준다.

 

바로 Re-Render에 대한 최적화이다.

Provider로 감싸지 않는 구조로 상태 변화에 따른

불필요한 렌더링을 최소화 할 수 있게 되고

멀리보면 UX까지 영향을 끼칠 수 있는

핵심적인 요소라고 볼 수 있다.

 

📑Flux패턴의 구조

Flux패턴은 간단히 이야기 하자면 React와 Redux처럼

논리가 단방향으로 흐르는 아키텍처이며 대표적인 특징으로

디버깅이 쉽다는 특징을 가지고 있다.

 

  • Flux 패턴 시각화

사용자 입력을 기반으로 Action을 만들고 Dispatcher에

전달한 후 Store의 데이터를 변경하고 View에 반영하여

단방향의 흐름으로 작동하며, View에서 State 변경이 감지되면

순서대로 재실행한다. 이러한 흐름을 통해 누락없이

변경된 State를 반영할 수 있다.

 

🔍Pub/Sub (발행/구독) 모델

Pub/Sub 모델은 Publish/Subscribe의 축약어로 메시지 기반의

미들웨어 시스템을 말하며, 메시지를 전송할 때 publisher(발행자)가

subscriber(구독자)에게 직접 메시지를 전송하지만 Pub/Sub 모델은

발행자(Pub)는 어떤 구독자(Sub)가 있는지 모르는 상태에서

메시지를 전송하고 구독자(Sub)발행자(Pub)에 대한 정보 없이

자신의 Interest(구독한 것)에 맞는 메시지만을 전송 받는 것을 의미한다.

 

즉, Zustand에서는 스토어의 상태 변경이 일어날 때 실행할

Listener 함수를 모아 두었다가(sub), 상태가 변경되었을 때

등록된 Listener 에게 상태가 변경되었다고 알려준다(pub).

 

🧑‍💻예제코드

간단한 예제코드로 Zustand의 미들웨어를 사용하여 Redux dev-tools를 활용가능한 코드이다.

import type {StoreApi, UseBoundStore} from "zustand";
import {create} from "zustand";
import {devtools} from "zustand/middleware";

export interface ITestObjState {
  id: number;
  title: string;
  content: string;
}

export interface ITestStore {
  testState: string;
  testObjState: ITestObjState;
  setTestState: (payload: string) => void;
  setTestObjState: (payload: ITestObjState) => void;
}

export const useTestStore: UseBoundStore<StoreApi<ITestStore>> = create(
  devtools((setState, getState, store) => ({
    testState: "default value",
    testObjState: {id: 0, title: "title", content: ""},
    setTestState: (payload: string) => setState({testState: payload}),
    setTestObjState: (payload: ITestObjState) => setState({testObjState: payload}),
  })),
);

 

위 예제코드처럼 단순하게 devtools를 감싸주기만 하면 Redux dev-tools를 활용하여 디버깅이 가능해진다.

 

✍️마치며...

기존에 Redux로 상태관리 라이브러리를 입문했지만, Zustand를 접하고 나서부터는 Redux를 거의 사용하지 않았던 것 같다. 그 만큼 DX가 좋은 라이브러리라고 생각이 들었고 대세 라이브러리로 부상한 의미를 바로 알 수 있었다. 실제로 현재 다니는 회사에서 새로 시작하게된 프로젝트에서 이전 프로젝트에서 활용하던 Redux를 그대로 채용하자고 했을 때 Zustand를 적극 추천하며 팀원을 설득한 끝에 Zustand를 본격 도입할 수 있었다. 그 이후 모두에게 확실히 편하게 써서 좋다는 말을 듣고 팀 단위에서 DX를 높인 것 같아 괜히 뿌듯했다. ㅎ

2024. 9. 18. 13:54

Data Type

자료의 "형"을 뜻하며 말 그대로 데이터의 타입이다.

필자는 Frontend가 주력인 개발자로서 Javascript를 기본으로

개발을 해왔기 때문에 Typescript 를 활용하지 않는다면

모든 객체(함수, obj, arr 등)와 변수에 별도로 타입을 지정할 일이 없다.

 

하지만 JAVA 언어를 사용하기 위해서는 모든 클래스에

타입 지정이 필수이며, 사실 Javascript를 제외한다면 대다수의

언어에서 타입지정은 필수이다.

 

JAVA에서 정의하는 타입은 Javascript에서 정의하는 타입과 크게

다르지 않고 일부 개념을 제외하면 거의 동일한 구조이다.

Javascript에서 크게 원시타입, 참조타입으로 구분한다면

JAVA에서는 일반형, 참조형으로 구분하고 있다.

 

일반형 (Primitive Type)

Javascript에서는 원시타입으로 불리는 Boolean, Number 등과 비슷한 형태로

메모리상에서 실제 값을 가지고 있는 자료형을 말하며 아래와 같은 일반형이 존재한다.

 

- 기본형 타입의 특징

  1. 소문자로 구성 되어있다.
  2. 변수에 선언과 동시에 초기화되어 null이 아닌 초기값이 존재한다.
  3. 비객체 타입(참조형이 아님)으로 모두 null을 할당받을 수 없다.
  4. 저장공간에 실제 값을 가지고 있다.
  5. 모든 타입은 메모리 Stack에 저장된다.

 

- 오해할 수 있는 문자형 타입

문자형 타입중에 String과 char가 존재한다.

진정한 기본형인 문자형 타입은 char이며,

String은 JAVA에서 문자열을 좀 더 편하게 사용할 수 있도록

제공하는 참조형 타입이다. 그렇기 때문에 타입 구성이

소문자로 되어있지 않고 대문자로 시작하는 "String"인 것이다.

 

  타입 할당 메모리 크기 기본 값 범위
논리형 boolean 1byte false true, false
문자형 char 2byte (Unicode) '\u0000' 0 ~ 65,535
정수형 int (default) 4byte 0 -2,147,483,648 ~ 2,147,483,647
byte 1byte 0 -128 ~ 127
short 2byte  0 -32,768 ~ 32,767
long 8byte 0L -9,223,372,036,854,775,808
~ 9,223,372,036,854,775,807
실수형 float 4byte 0.0F (3.4 X 10-38) ~ (3.4 X 1038) 의 근사값
double (default) 8byte 0.0 (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값

 

  • 일반형 예제 소스코드
class PrimitiveType() {
	int number;
    boolean isBoolean;
    double isDouble;
}

// number = 0
// isBoolean = false
// isDouble = 0.0

 

 

참조형 (Reference Type)

참조형 타입은 클래스, 배열, 열거형 타입 등을 참조형이라 하며,

변수에 할당되어 메모리상에 존재할 때 null 또는 객체가 가지는

참조가능한 주소를 할당할 수 있다.

 

- 참조형 타입의 특징

  1. 기본형과 다르게 실제 값이 아닌 데이터가 저장 되어있는 주소를 가진다.
  2. 참조형 내부의 값은 메모리 내부 힙(heap)에 저장하고 참조형 데이터가 가진 주소를 통해 접근가능하다.
  3. 참조형 변수는 null로 초기화 가능하고 이는 GC(Garbage Collection) 대상으로 이어질 수 있다.

 

타입 기본 값 할당 메모리 크기
배열 (Array) null 4byte
(주소만을 담기 때문)
열거 (Enumeration) null
클래스 (Class) null
인터페이스 (Interface) null

 

 

  • 참조형 예제 소스코드
class ReferenceType() {
	String title = "Title";
    String[] contents = new String[] { "배열임", "이것은 참조형" }
    Date date = new Date();
    Data data = new Data();
}

// title = "Title"
// contents = main.ref@682a0b20 (패키지경로@참조주소)
// date = main.ref@682a0b21 (패키지경로@참조주소)
// data = main.ref@682a0b22 (패키지경로@참조주소)

class Data() {
	int value;
    char content
}

 

 

마치며...

Javascript를 주력으로 개발을 하다보니 평소 자료형(Data Type)에 대해서 깊게 생각하지 못했던 것 같다. 타 언어에도 관심을 가지고 배우며 점점 개발자로서의 시야도 넓어지는 것 같다. 이번에 JAVA를 공부하면서 Javascript가 얼마나 편한 언어인지 깨닫는 계기가 되었다... JS 최고.

2024. 9. 15. 23:48

가장 자주 쓰일법한 코드 템플릿 단축키 6선

 

  • main 매서드 생성: psvm (PublicStaticVoidMain의 약자)
  • Run system Log print: sout (System.out.println)
  • 향상된 for문: iter
  • 일반 for문: itar
  • 일반 for문: itar
  • null 체크 if문: ifn
  • not null 체크 if문: inn
2024. 9. 8. 15:47

Jenkins

젠킨스는 지속적 통합, 배포를 용이하게 관리해주는 CI/CD이다.

젠킨스에서 제공하는 브라우저(UI)는 UX가 좋다는 것을 느꼈고

이를 통해 불편함 없이 점진적으로 확장 가능한 것이 장점이라고 생각했다.

 

파이프라인을 직접 작성하여 의도대로 프로세스(배치 등)를 나누어

예상 가능한 작업이 가능하도록 분기할 수 있었고 staging, build,

deploy 등 에러 발생 시 빠르게 파악가능하여 디버깅 및 대응이

빠른 것이 가장 큰 이점이라고 생각했다.

 

PipeLine

젠킨스 내에서 프로젝트를 구성하고 해당 프로젝트 내로 들어갔을때

좌측 상단메뉴를 통해 접근할 수 있다.

  • 구성 -> pipeline

위와 같이 파이프라인을 작성할 수 있는 Pipeline script 블록에서 작성할 수 있다.

 

 

Script

pipeline {
    agent any
    
    tools {
        nodejs 'NodeJS 20.16.0'
    }
    

    stages {
        stage('Checkout') {
             steps {
                 cleanWs()
                 git branch: 'main', credentialsId: 'your-project-token', url: 'project-git-repository-url'
             }
        }
        
        stage('Build') {
            steps{
                sh'''
                    echo Install Node.js and npm...
                    npm --version
                    node --version
                    npm install
                    npm run build
                '''
            }
        }
        
        stage('Deploy') {
            steps{
                sshPublisher(
                publishers:
                     [
                        sshPublisherDesc(
                        configName: 'linux-server',
                        transfers:
                            [
                                sshTransfer(
                                cleanRemote: false,
                                execTimeout: 120000,
                                flatten: false,
                                makeEmptyDirs: false,
                                noDefaultExcludes: false,
                                patternSeparator: '[, ]+',
                                remoteDirectory: '/opt/project',
                                remoteDirectorySDF: false,
                                removePrefix: '',
                                sourceFiles: '.next/**, public/**, package.json, .env.development, .env.production',
                                excludes: '',
                                execCommand: 'cd /opt/project && npm install && pm2 restart ecosystem.config.json',
                                )
                            ]
                        )
                    ]
                )
            }
        }
    }
}

 

필자는 Next.js 14버전 app-router 프로젝트를 기반으로 작성하였고

간단하게 Checkout, Build, Deploy로 나누었다.

 

Checkout: 빌드하게 될 깃 레포지토리를 지정

 

Build: 젠킨스 os환경에서 sh 명령문을 활용하여 npm install, build를 실행

 

Deploy: sshPublisher의 transfers를 통해 포함할 파일을 지정하여

배포될 서버 환경으로 전달해주고 execCommand를 통해 배포서버

환경에서 실행시킬 명령어를 전달하여 pm2를 활용한 서버 재시작으로

변경사항이 배포사이트에 적용될 수 있도록 해주었다.

 

마치며...

주로 프론트엔드 기술을 주력으로 개발자로 종사하고 있지만, 기회만 주어지게 된다면 개발자적 시야를 넓히고자 자발적으로 업무를 맡아보고 있다. 실제로 개발은 프론트와 서버로 나눈다는 개념보다는 프로그램을 만드는 것 자체가 개발이라고 생각하기 때문에 앞으로도 더 다양한 분야의 개발을 경험할 예정이다. 실제로 지금 java 언어와 스프링부트 프레임워크에 대해 공부도 겸하고 있다. 언제나 새로운 개념을 알았을 때, 그리고 그러한 개념이 지금까지의 기술력과 이어질 때만큼은 정말 보람차고 즐거운 것 같다😊

2024. 7. 13. 20:17

TinyMCE의 이미지 첨부

기본적으로 이미지 첨부 기능을 제공하지만,

UX를 고려했을 때 사용자에게 익숙하지 않은 방식의

이미지 첨부 기능을 제공하고 있다.

 

  • 기본으로 제공되는 TinyMCE 이미지 첨부 방식

위 이미지 처럼 기본적으로 소스를 붙여넣어서 첨부하는 방식으로

제공되고 있다.

 

이러한 방식은 UX에 좋지 않다는 생각이 들었고

업로드 버튼을 통해 이미지를 업로드하는 방식으로

UX를 향상시키고자 커스텀 방식에 대해 알아보게 되었고

 

실제로 커스텀하여 업로드 버튼을 통해서도 이미지 첨부가

가능하도록 만든 경험과 방법을 공유하고자 포스팅을 하게 됐다.

 

라이브러리는 이전 포스팅 https://zeriong.tistory.com/65

과 동일하게  tinymce/tinymce-react 를 활용하여 구현했다.

 

소스코드

const TinyMceEditor = () => {
  return (
    <Editor
      apiKey={YourApiKey}
      init={{

        // 필요한 툴을 공식 document에서 찾은 후 추가
        plugins: [ ... ],

        // 플러그인에 추가한 것을 툴바에 표시하기 위한 객체(생략)
        toolbar: " ... ",

        // 이미지 툴바 클릭 시 이미지 파일 첨부 설정 추가
        image_title: true,
        automatic_uploads: true,
        file_picker_types: "image",
        
        // 업로드 버튼을 눌렀을 때 실행되는 함수
        file_picker_callback: (callback, value, meta) => {
          const input = document.createElement("input");
          input.setAttribute("type", "file");
          input.setAttribute("accept", "image/*");

          input.addEventListener("change", (e) => {
            const file = e.target.files[0];
            const reader = new FileReader();

            reader.addEventListener("load", () => {
              // 파일 블로핑
              const id = "blobid" + new Date().getTime();
              const blobCache = window.tinymce.activeEditor.editorUpload.blobCache;
              const base64 = reader.result.split(",")[1];
              const blobInfo = blobCache.create(id, file, base64);
              blobCache.add(blobInfo);

              // 콜백을 호출하고 파일 이름으로 파일 데이터(file의 meta) 파일이름 적용
              callback(blobInfo.blobUri(), {title: file.name});
            });
            // 블로핑된 파일 URL을 읽어옴
            reader.readAsDataURL(file);
          });

		  // run function
          input.click();
        },
        
		// ...
        
      }}
    />
  );
};

컴포넌트 형식으로 구현하였고 필요한 코드만을 적어두었다.

나머지 필요 요소는 공식 문서(https://www.tiny.cloud/docs/tinymce/latest/)

를 확인하여 추가해주면 아래 이미지처럼

구현할 수 있게 된다.

 

  • 소스 인풋 옆에 추가된 업로드 버튼

 

마치며...

회사에서 여러 프로젝트를 진행하며 요구사항에 맞춘 라이브러리 커스텀이나 구현해보지 못한기능을 구현하는 일이 점점 잦아지는 것 같다. 새로운 기능을 구현하며 느낀 것 언제나 "정답은 공식문서(Docs)에서 찾을 수 있다." 라는 것이다. 타 블로그나 커뮤니티 글을 찾아보기 이전에 공식문서를 자세히 살펴 본 후 추가적인 아이디어를 얻기 위해 블로그 또는 커뮤니티 글을 참고하여 구현하는 것이 해당 라이브러리에 대한 이해도를 높이면서 활용도를 확장시킬 수 있는 것 같다.

'Frontend > Editor Tool' 카테고리의 다른 글

[ TinyMCE ] 1000회 무료 에디터툴!  (0) 2024.05.04
2024. 5. 19. 21:55

이번 포스팅에서 다룰 내용은

정말 단순하게 컴포넌트 순서에 대한 것이다.

 

제목에 "모달" 을 붙인 이유는 필자가

한 화면에서 여러 모달을 필요로 하고

모달 위에 또 모달을 띄우는 겹모달을

구현해야 할 때 컴포넌트 순서를 정리하여

간결하게 해결하는 것이 개인적으로

개발자 경험이 가장 좋다는 생각

들었기 때문에 포스팅하기로 했다.

 

컴포넌트의 순서

사실 컴포넌트의 순서라고 해봐야 결국

위에서 아래로 순서대로 쌓인다고 볼 수 있다.

필자의 주력인 React로 예시를 들어보겠다.

const Test = () => {
    return (
        <div>
            <p>1</p>
            <p>2</p>
            <p>3</p>
        </div>
    )
}

 

div 태그의 css 속성이 기본적으로 display: block 이라고 가정한다면

1
2
3

 

위와 같은 형태를 볼 수 있고 만약 display: flex 라고 가정하면 아래와 같이 화면에 나타날 것이다.

123

 

 

어찌보면 너무너무 당연한 이야기다.

 

하지만 position: fixed 또는 absolute라면 어떤가?

 

분명 컴포넌트의 순서를 생각해보지 않았다면

혼란이 올 것이다.

 

필자의 경우 위 123 태그들을 fixed로 띄운다면

순서에 관계 없이 동일선상에 띄워지는 것으로

생각하고 있었다.

 

그렇기 때문에 123 태그들의 우선순위를

각각 지정해주기 위해서는 css속성인 z-index를

활용해서 순서를 반드시 지정해주어야 하는 것으로

생각하고 있었다.

 

이 방법은 일차원적이면서도 가장 확실한 방법이기도 하다.

 

결론으로 이러한 상황에서 가장 간결하고

빠르게 해결할 수 있는 방법이 바로 컴포넌트의

순서를 활용하는 것이다.

 

일반적으로 html 태그가 위에서 아래로 쌓이는 것과

마찬가지로 position이 fixed거나 absolute라면

화면의 z축을 기준으로 태그가 한장씩 쌓이게 되는

개념이 되는 것이다.

 

즉, fixed 또는 absolute position을 가진 태그는

아래에 있는 컴포넌트일수록 더 높이 위치해있다고

볼 수 있다.

 

아래 예시 코드를 확인해보자.

const YoutubeQueuePlay = () => {
    return (
        <>
            {/* transform을 설정하여 내부 컨텐츠 fixed 의 기준을 지정 */}
            <div className="transform flex flex-col pc:flex-row w-full h-full cursor-default relative">

                {/* 저장된 플레이리스트 버튼 */}
                <SavedMusicListButton/>

                {/* 플레이어 컨텐츠 섹션 */}
                <PlayerSection/>

                {/* 어사이드 바 */}
                <PlayerAside/>
            </div>

            {/* 신청/수정 모달 */}
            <EditModal/>

            {/* 미리보기 모달 */}
            <PreViewModal/>
            
            {/* 저장된 리스트 모달 */}
            <SavedListModal/>
        </>
    )
}

위에서 총 3가지 모달이 있다.

< 신청, 수정 모달 >

< 저장된 리스트 모달 >

< 미리보기 모달 >

 

여기에서 이중 모달이 필요한 경우는

< 저장된 리스트 모달 >에서 저장된 유튜브의

미리보기를 볼 수 있도록 하기 위해서

저장된 리스트 모달 위에 < 미리보기 모달 >

띄워야 하는 경우이다.

 

이러한 경우 < 미리보기 모달 >

< 저장된 리스트 모달 > 보다 상단에 위치해야 한다.

 

그렇다면 지금 위 예시코드로는 불가능하다.

< 미리보기 모달 > < 저장된 리스트 모달 > 보다

상단에 존재하기 때문에 < 저장된 리스트 모달 >

우선순위가 더 높아져서 미리보기를 눌러서

< 미리보기 모달 >을 띄워도 가려지게 된다.

 

해결법은 단순히 순서만 바꿔주면 된다.

const YoutubeQueuePlay = () => {
    return (
        <>
            ...

            {/* 신청/수정 모달 */}
            <EditModal/>
            
            {/* 저장된 리스트 모달 */}
            <SavedListModal/>
            
            {/* 미리보기 모달 */}
            <PreViewModal/>
        </>
    )
}

 

 

마치며...

프로그래밍에 대한 이해도가 낮았을땐 결론을 짧은 경험과 유추만으로 결정짓는 습관이 고질적인 문제였다. 하지만 꾸준히 공부하고 개발 경험을 늘려갈수록 더 많은 것들이 보이게 되고 더 겸손해지고 부족함을 많이 느끼게 되는 것 같다. 스스로 유추하고 확신을 가지기보다 근거를 기반으로 확신을 내리는 방향성을 절대로 잊지 말자.

2024. 5. 4. 13:43

Editor Tool

일반적으로 에디터 툴은 프론트에서 서비스 이용자가

다양한 스타일로 글을 작성할 수록 있도록 돕는 툴이다.

다양한 에디터 툴이 존재하며 필자는 에디터 툴하면

가장 먼저 Toast UI Editor가  떠오른다.

 

필자 역시 개발을 진행하다 보면 에디터 툴이 필요하여

여러 에디터 툴을 알아보았고 Toast UI Editor를

사용하고 싶었으나 리액트 최신 버전을 지원하지 않아

당장은 사용할 수 없는 상황이었다.

 

필자는 리액트를 주력으로 개발하기에

결국 리액트 최신버전을 지원하는 에디터 툴을

찾다가 TinyMCE라는 에디터 툴을 알게되었고

 

해외에서는 꽤 활발한 커뮤니티가 형성되어 있어

접근성이 좋을 것으로 판단했고 리액트 최신버전을

지원하고 있어 선택하게 되었다.

 

TinyMCE

이번에 소개할 에디터 툴은 TinyMCE이다.

회원가입 시 계정 API key를 제공하고

월 1,000회 로드를 무료로 제공하고 있다.

 

Microsoft, NASA, DRIFT, ATLASSIAN 등 해외 유명 기업에서

많이 활용되고 있고 확장성이 뛰어나 다양하게

활용할 수 있는 장점이 있고

 

React 주력 개발자로서 React 최신버전을 지원한다는

면이 필자는 가장 마음에 들었다.

또한 React, Vue, Angular, JAVA 등 다양한 언어를

지원하고 있어 주력 에디터로서도

좋은 선택지라는 생각이 들었다.

 

Install

npm install --save @tinymce/tinymce-react

 

 

예시코드

import React from 'react';
import { Editor } from '@tinymce/tinymce-react';

export default function App() {
  return (
    <Editor
      apiKey='your api key'
      init={{
        plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount checklist mediaembed casechange export formatpainter pageembed linkchecker a11ychecker tinymcespellchecker permanentpen powerpaste advtable advcode editimage advtemplate ai mentions tinycomments tableofcontents footnotes mergetags autocorrect typography inlinecss markdown',
        toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat',
        tinycomments_mode: 'embedded',
        tinycomments_author: 'Author name',
        mergetags_list: [
          { value: 'First.Name', title: 'First Name' },
          { value: 'Email', title: 'Email' },
        ],
        ai_request: (request, respondWith) => respondWith.string(() => Promise.reject("See docs to implement AI Assistant")),
      }}
      initialValue="Welcome to TinyMCE!"
    />
  );
}

Tiny MCE에서 제공하고 있는 아주 간단 소개정도의 구현코드이다.

ai_request 기능을 제공하고 있어 ai를 활용할 수 있는

몇가지 기능도 제공하는 것으로 보인다.

 

다음에는 현재 필자가 직접 커스텀한 방식이나 방법에 대해서

Tiny MCE 게시글을 추가적으로 포스팅할 예정이다.

 

마치며...

서비스 이용자로서 에디터 툴은 많이 사용해봤지만 막상 서비스를 만드는 개발자 입장에서 에디터 툴을 직접 구현하기 위해서는 복잡하고 많은 프로세스를 요구한다는 것을 깨닫고 직접적으로 구현한다는 것은 매우 어렵다는 것을 깨달았다. 좋은 에디터 툴을 라이브러리로 제공하는 선두 개발자들에게 깊은 감사를 느꼈다... 그리고 이번 경험으로 또 하나의 목표가 생겼다. 바로 에디터 툴을 직접 구현해보는 것이다. 혼자의 힘으론 어려울 수 있겠지만 분명 좋은 경험이 될 것 같다.

2024. 4. 4. 21:25

findIndex

특정 배열에서 중복 객체를 제외하는 많은 방법 중

주관적인 견해로 가장 간결하게 처리할 수 있는 방법으로

findIndex를 활용하는 방식을 채택하여 실제 프로젝트에

적용한적이 있다.

 

findIndex매서드는 iterable한(반복 가능한) 객체에서

사용 가능한 매서드로 해당 객체를 0번째 요소부터

순회하며 매개변수내에서 true를 반환하게 되면

해당 요소의 index를 반환하는 매서드이다.

 

사용예시

const array = [{id:1, title: "test"}, {id:2, title: "test"}, {id:3, title: "test"}, ];

// id가 2인 객체의 인덱스를 반환
const findSecondId = array.findIndex((value) => value.id === 2);

console.log(findSecondId); // 1

이런 방식으로 특정 조건을 만족하게 되면 true를 반환하게 되고

findIndex는 true를 감지하면 순회를 멈추고 해당 데이터의 index를

반환하게 된다.

 

어떻게 findIndex로 중복 객체를 제거 할까?

순회의 프로세스를 순차적으로 이해한다면

비교적 간단하게 접근하여 적용할 수 있다.

 

실질적인 데이터베이스에 저장된 데이터들은

각각 고유값(id)을 가지고 있기 때문에 이를 통해서

중복 객체를 제거할 수 있다.

 

꼭 고유 id가 아니더라도 중복되는 데이터를 타겟으로

filter와 findIndex를 활용한다면 쉽게 가공할 수 있다.

 

  • 예시코드
//  데이터 중 title이 "제목입니다." 인 객체는 createAt, title, id 모두 동일하다.
const data = [
  { id: 1, title: "textTitle.", createAt: 1235 },
  { id: 2, title: "제목입니다.", createAt: 1236 },
  { id: 2, title: "제목입니다.", createAt: 1236 },
  { id: 4, title: "newTitle.", createAt: 1238 },
  { id: 5, title: "allNewTitle.", createAt: 12359 },
];
// data 깊은 복사
const data2 = JSON.parse(JSON.stringify(data));

// id를 비교한 경우
const filteredById = data.filter((obj, idx) =>
  (data.findIndex(obj2 => obj.id === obj2.id)) === (idx)
)

console.log(filteredById);
/*
  [
    { id: 1, title: "textTitle.", createAt: 1235 },
    { id: 2, title: "제목입니다.", createAt: 1236 },
    { id: 4, title: "newTitle.", createAt: 1238 },
    { id: 5, title: "allNewTitle.", createAt: 12359 },
  ]
*/


//  createAt를 비교한 경우
const filteredByCreateAt = data2.filter((obj, idx) =>
  (data2.findIndex(obj2 => obj.createAt === obj2.createAt)) === (idx)
)

console.log(filteredByCreateAt);
/*
  [
    { id: 1, title: "textTitle.", createAt: 1235 },
    { id: 2, title: "제목입니다.", createAt: 1236 },
    { id: 4, title: "newTitle.", createAt: 1238 },
    { id: 5, title: "allNewTitle.", createAt: 12359 },
  ]
*/

 

왜 중복이 제거되는걸까?

원리는 아주아주 심플하다.

배열 내의 특정 데이터가 유일한 경우에는 해당 데이터를 가진

데이터의 index 또한 유일해야만 한다.

하지만 같은 데이터가 존재한다면 index만 유일한 값이 되고

특정 데이터는 중복된 데이터를 가지게 된다.

 

그렇기 때문에 배열에서 반드시 유일할 수 밖에 없는

index값을 비교할 수 있도록 findIndex와 filter의 index를

활용하면 중복되는 특정 값을 가진 객체를 제외시킬 수 있다.

 

findIndex는 index 0부터 탐색을 시작하고 해당 조건이 맞는 순간

순회를 멈추고 해당 index를 반환하기 때문에 순회 도중

같은 데이터가 발견되면 서로 index가 달라지기 때문

 

filter의 조건인 currentIndex === findIndex 가 false를

반환하게 되어 결국 중복된 데이터는 제외하고 반환하게

되기 때문이다.

 

마치며...

간혹 개발을 하다보면 이론적으로는 알고 있지만 막상 코드로 구현하고자 할 때 이상하리 만치 고민하게 되는 경우가 종종 있는 것 같다. 이번의 경우도 그랬다. 분명 어려운 것은 아니지만 딱히 활용할만한 상황이 없어서 였는지 설계할 때 고민을 꽤 했던 것 같다. 이론적으로 알고 있더라도 결국 직접 문제를 직면하고 해결하기 위해 설계를 떠올려보고 구상해보는 것이

더 깊은 이해도를 가질 수 있고 내 것으로 만들 수 있는 것 같다. 기술이든 개념이든 내가 설명할 수 있을 정도의 이해도를 갖춰야만 활용할 수 있다는 것을 깨달았다. 앞으로도 내가 아는 것을 설명할 수 있는 사람이 되자.