좋은 개발자가 되기 전에 좋은 사람이 먼저 되고 싶어요

프론트엔드

SSOT 그리고 제어 컴포넌트와 비제어 컴포넌트

개발하는 이정민 2024. 2. 16. 18:30

요번에 현 X.COM 구 TWITTER 를 클론 코딩하면서 Next.js에 대한 공부를 하고 있었다 (명확히는 14버전의 App Router)

그러다가 React에서 제공하는 API 중에 비제어 컴포넌트로 사용하는 formState 와 useFormState를 알게 되었고 사용해보면서 SSOT라는 개념 또한 알게되어 정리를 해보고자 한다

 

 

SSOT(Single Source of Truth) 란 ?

소프트웨어 개발에서의 SSOT 개념이란 데이터가 시스템 내의 한 위치에만 저장되어야 하며 해당 정보는 그 위치에 항상 최신 상태로 지정되고 신뢰할 수 있는 저장소이도록 보장해야 함을 나타내는 개념이다, 이렇게 되면 데이터의 일관성이 보장되고 데이터가 중복이 되거나 데이터가 UI의 표시에 있어 불일치 위험을 줄일 수 있고 이러한 효과로 서비스가 커지더라도 개발과 유지보수가 단순화 되는 이점을 얻을 수 있다

 

그렇다면 이러한 SSOT 개념의 위배되는 행동은 무엇 일까 상황의 예를 들어보고 직접 코드의 예로 들어보자

 

상황의 예)

만약 프로필 사진을 변경했으면 프로필 상세 페이지에서는 정상적으로 변경이 되었지만유저 탐색에 있어서는 내 프로필 사진이 변경이 되지 않는 경우가 해당 된다, 이 문제에서 만약 각 영역에서 보여줘야하는 내 프로필 사진의 데이터 저장소가 동일하게 엮어있지 않다면 SSOT 개념에 위배되는 것이다

 

 

간단한 코드의 예)

const form = document.querySelector('form');

let inputValue = '';
let textareaValue = '';
let selectValue = '';

const onSubmit = (e) => {
	e.preventDefault();
    
    inputValue = e.target.elements['input-elem'].value;
    inputValue = e.target.elements['textarea-elem'].value;
    inputValue = e.target.elements['select-elem'].value;
};

form.addEventListener('submit', onSubmit);

 

위의 코드를 보면 user가 값을 form tag에 input을 하면 value attribute에 저장이되고 그러한 데이터를 다시 let variable 형태로 데이터를 저장하고 있다, 여기서 만약 value attribute를 사용하는 A컴포넌트와 let variable을 사용하는 B컴포넌트 두개가 있다고 가정했을때에 만약 특정 이슈로 인하여 let variable 자체가 value attribute와 연관성이 끊어졌다고 하면 B컴포넌트는 더이상 데이터 최신화를 받을 수 없게 되고 이러한 경우가 SSOT 개념에 위반이 된다고 할 수 있다

 

 

그렇다면 React에서는 어떻게 해결을 했을까 ?

리액트에선 이러한 문제를 해결하기 위해 컴포넌트 단에서는 제어 컴포넌트비제어 컴포넌트로 해결하였고, 데이터 저장에 있어서는 상태 관리 라이브러리(Redux, Recoil,,,,,) 와 React의 내장 Context API 를 사용하여 데이터를 중앙 집중화 하였으며 프로젝트 구조 및 아키텍처 관리에 있어서는 state를 부모에서 자식으로 내려주는 방식을 통하여 SSOT 개념을 지켰다

 

요번 글에서는 제어 컴포넌트와 비 제어 컴포넌트 관련 글이기 때문에 조금 더 알아보자

 

리액트에서의 제어 컴포넌트

import {useState} from 'react';

export default function App() {
	const [name, setName] = useState('');
    
    return (
    	<input value={name} onChange={(e) => {setName(e.target.value)}} />
    )
}

 

제어 컴포넌트는 state를 value attribute과 결합하여 React가 직접 Form Element를 제어하여 state를 SSOT의 개념을 지켰다, 하지만 state를 모든 Form Element의 코드와 통합을 시켜야 하고 입력 값이 변경될 때마다 state는 리렌더링을 일으키기 때문에 렌더링 이슈가 생길 수가 있다 (자주 발생하는 리렌더링의 경우 Debouncing과 Throttling을 통하여 어느 정도 해결이 가능하다)

 

리액트에서의 비제어 컴포넌트

import {useState} from 'react';

export default function App() {
	const nameRef = useRef(null);
    
    return (
    	<input ref={nameRef} />
    )
}

 

비제어 컴포넌트는 제어 컴포넌트와 달리 ref를 사용하며 ref는 순수 자바스크립트 객체이기 때문에 ref.current 프로퍼티를 통해서 input태그에 직접 접근을 해서 React가 Form Element를 직접 제어하지 않아 데이터 관리 주체가 DOM 자체가 된다 그렇기에 value attribute가 SSOT의 개념을 지켰다, 하지만 ref의 특성상 렌더링이 되지 않기 때문에 입력 값에 대한 버튼의 비활성화 유무를 체크한다던지 입력에 있어 즉각적인 이벤트(유효성 검사, 특정 입력 형식 강요) 등을 할 수가 없다

 

그렇다면 여기서 짧지만 기본적인 지식 왜 Ref는 리렌더링을 발생하지 않을까?

이름에서 알 수 있다시피 ref(reference) 즉 참조라는 뜻이다 ref의 경우 Heap에 저장되는 순수 자바스크립트 객체라 매번 렌더링 할 때마다 동일한 객체를 제공한다, Heap에 저장되어 있기 때문에 애플리케이션이 종료되거나 Garbage Collection이 돌기 전까지, 참조(ref)할 때마다 같은 메모리 값을 가진다고 할 수 있다, 같은 메모리 주소를 가지고 있기 때문에 자바스크립트의 동치 연산(===)이 항상 true를 반환하기에 렌더링이 일어나지를 않는다.

 

 

기존에 나는 Form을 어떻게 다루었나?

기존 작업했던 내용들을 살펴보니 100이면 100 전부 제어 컴포넌트를 사용하였다, 이유인즉슨 리렌더링의 관리만 해주면 사용자 입력에 있어 즉각적인 변화를 줄 수 있는 UI가 가장 큰 장점으로 작용했었다.

 

 

 

첫 번째 사진처럼 값을 담을 state를 선언하던지 또는 상태관리 라이브러리를 recoil을 사용했어서 atom으로 선언하고 validation check에 맞는 에러 메시지 또한 valueErrors를 사용하여 UI에 표기하고는 했다, 다만 두 번째 사진처럼 component 간의 depth가 깊어져 state의 경우 props를 통해 values와 setValues를 넘겨줘야 하는 상황에서는 길어지고 복잡해지기 시작했고, 리렌더링의 문제의 경우 잦은 리렌더링의 경우 Debouncing과 Throttling을 통하여 해결하고, 사실상 리렌더링은 리액트의 당연한 동작이라 생각하여 크게 신경을 쓰지 않았던 것 같다 ref의 경우는 단순히 focusing을 위한,,?

 

 

앞으로는 써보자 비제어 컴포넌트

 

useFormState 를 사용하여 formAction을 form 태그에 할당하며 input 태그는 value attribute를 사용
useFormState의 첫번째 인자로 들어가는 onSubmit의 함수

 

 

위 Signup 파일은 useFormState에 파라미터로 들어가는 onSubmit 함수이다

'use server' 로 서버 컴포넌트를 만들고 서버 액션을 사용하여 FormData 객체를 이용해 엘리먼트들의 입력 값을 받아 와서 사용하고 있다, 또한 만약 위 함수와 같이 회원가입이 아닌 to do, 게시판과 같이 글을 작성하고 하단에 등록한 글을 바로 보여줘야하는 경우에는 next의 경우 서버 액션 사용시에 revalidatePath라는 함수를 이용하여 해당 url을 새로고침없이 바뀐 부분만 바꿔줄 수 있는 새로고침 함수 또한 있어 요긴하게 사용이 가능하다

 

무튼 앞서 말했듯이 비제어 컴포넌트는 즉각적으로 입력값에 따라 변하는 UI 변경 사항의 요구를 들어주기 힘들기 때문에 다양한 입맛에 맞는 커스텀이 힘들었지만 react-hook-form 도 있고 게다가 React 자체 API로 요번에 사용해보고 있는 formState와 useFormState 등이 있듯이 리렌더링의 이슈를 해결함과 동시에 제어 컴포넌트의 장점을 가져갈 수 있는 여러 가지 기능을 하나씩 사용해 봐야겠다는 생각이 든다.


해당 블로그는 공부한 내용의 기록으로 중점이 맞춰져있습니다.

스스로 어떻게 받아들이고 있는지에 대한 주관적인 견해가 많이 들어가 있습니다.
그렇기에 다소 설명이 부족하고 다른 방향으로 해석할 수 있는 여지가 있습니다.

우연찮게 지나가다가 발견하신 글이라면 다양한 의견 감사하게 받아들이겠습니다.

 

 

'프론트엔드' 카테고리의 다른 글

프론트엔드 성능 개선과 컨벤션 수립  (0) 2024.04.25
Unit Test and Jest  (0) 2024.03.19
Web Animation  (0) 2024.03.13