Skip to main content

2 posts tagged with "react"

View All Tags

React의 useState에서 피해야 할 4 가지 실수 🚫

· 5 min read

image

소개

React.js는 구성 요소 내에서 상태를 관리하는 고유한 접근 방식을 통해 현대 웹 개발의 초석이 되었습니다. 일반적인 후크 중 하나 useState는 기본이지만 종종 오용됩니다. 이러한 일반적인 실수를 이해하고 피하는 것은 효율적이고 버그 없는 애플리케이션을 만드는 것을 목표로 하는 초보자와 숙련된 개발자 모두에게 중요합니다.

useState이 블로그에서는 React에서 사용할 때 피해야 할 네 가지 중요한 실수에 대해 알아볼 것입니다 . 함께 React 기술을 향상시켜 봅시다!

실수 1: 이전 상태를 고려하는 것을 잊음 😨

React의 useState후크로 작업할 때 흔히 저지르는 실수는 업데이트할 때 가장 최근 상태를 고려하지 않는 것입니다. 이러한 방식은 특히 신속하거나 여러 상태 업데이트를 처리할 때 예기치 않은 동작으로 이어질 수 있습니다.

❌ 문제 이해

React에서 카운터를 구축한다고 가정해 보겠습니다. 당신의 목표는 버튼을 클릭할 때마다 횟수를 늘리는 것입니다. 간단한 접근 방식은 단순히 현재 상태 값에 1을 추가하는 것입니다. 그러나 이는 문제가 될 수 있습니다.

import React, { useState } from 'react';

const CounterComponent = () => {
const [counter, setCounter] = useState(0);

const incrementCounter = () => {
setCounter(counter + 1); // Might not always work as expected
};

return (
<div>
<p>Counter: {counter}</p>
<button onClick={incrementCounter}>Increment</button>
</div>
);
};

export default CounterComponent;

위 코드에서는 incrementCounter현재 값을 기준으로 카운터를 업데이트합니다. 이는 간단해 보이지만 문제가 발생할 수 있습니다. React는 여러 setCounter호출을 일괄적으로 일괄 처리하거나 다른 상태 업데이트가 방해를 받아 counter매번 올바르게 업데이트되지 않을 수 있습니다.

✅ 수정사항:

이 문제를 방지하려면 메서드의 기능적 형식을 사용하세요 setCounter. 이 버전은 React가 가장 최근의 상태 값으로 호출하는 함수를 인수로 사용합니다. 이렇게 하면 항상 최신 상태 값으로 작업할 수 있습니다.

import React, { useState } from 'react';

const CounterComponent = () => {
const [counter, setCounter] = useState(0);

const incrementCounter = () => {
setCounter(prevCounter => prevCounter + 1); // Correctly updates based on the most recent state
};

return (
<div>
<p>Counter: {counter}</p>
<button onClick={incrementCounter}>Increment</button>
</div>
);
};

export default CounterComponent;

이 수정된 코드에서는 incrementCounter함수를 사용하여 상태를 업데이트합니다. 이 함수는 가장 최근 상태( prevCounter)를 수신하고 업데이트된 상태를 반환합니다. 이 접근 방식은 특히 업데이트가 빠르게 또는 연속해서 여러 번 발생할 때 훨씬 더 안정적입니다.

실수 2: 상태 불변성 무시 🧊

❌ 문제 이해

React에서 상태는 불변으로 취급되어야 합니다. 일반적인 실수는 특히 객체 및 배열과 같은 복잡한 데이터 구조의 경우 상태를 직접 변경하는 것입니다.

상태 저장 객체에 대한 다음과 같은 잘못된 접근 방식을 고려해보세요.

import React, { useState } from 'react';

const ProfileComponent = () => {
const [profile, setProfile] = useState({ name: 'John', age: 30 });

const updateAge = () => {
profile.age = 31; // Directly mutating the state
setProfile(profile);
};

return (
<div>
<p>Name: {profile.name}</p>
<p>Age: {profile.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};

export default ProfileComponent;

이 코드는 profile개체를 직접 잘못 변경합니다. 이러한 변형은 다시 렌더링을 유발하지 않으며 예측할 수 없는 동작으로 이어지지 않습니다.

✅ 수정사항:

불변성을 유지하기 위해 상태를 업데이트할 때 항상 새 개체나 배열을 만듭니다. 이 목적으로 스프레드 연산자를 사용하십시오.

import React, { useState } from 'react';

const ProfileComponent = () => {
const [profile, setProfile] = useState({ name: 'John', age: 30 });

const updateAge = () => {
setProfile({...profile, age: 31}); // Correctly updating the state
};

return (
<div>
<p>Name: {profile.name}</p>
<p>Age: {profile.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};

export default ProfileComponent;

수정된 코드에서는 updateAge스프레드 연산자를 사용하여 profile상태 불변성을 유지하면서 업데이트된 수명으로 새 개체를 만듭니다.

실수 3: 비동기 업데이트에 대한 오해 ⏳

❌ 문제 이해

React의 상태 업데이트는 useState비동기식입니다. 이는 특히 여러 상태 업데이트가 빠르게 연속적으로 이루어질 때 혼란을 야기하는 경우가 많습니다. 개발자는 호출 직후 상태가 변경될 것으로 예상할 수 있지만 setState실제로 React는 성능상의 이유로 이러한 업데이트를 일괄 처리합니다.

이러한 오해로 인해 문제가 발생할 수 있는 일반적인 시나리오를 살펴보겠습니다.

import React, { useState } from 'react';

const AsyncCounterComponent = () => {
const [count, setCount] = useState(0);

const incrementCount = () => {
setCount(count + 1);
setCount(count + 1);
// Developer expects count to be incremented twice
};

return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment Count</button>
</div>
);
};

export default AsyncCounterComponent;

이 예에서 개발자는 count두 배로 증가시키려고 합니다. 그러나 상태 업데이트의 비동기적 특성으로 인해 두 setCount호출 모두 동일한 초기 상태를 기반으로 하므로 결과는 count한 번만 증가됩니다.

✅ 수정사항:

비동기 업데이트를 올바르게 처리하려면 의 기능 업데이트 형식을 사용하세요 setCount. 이렇게 하면 각 업데이트가 가장 최근 상태를 기반으로 합니다.

import React, { useState } from 'react';

const AsyncCounterComponent = () => {
const [count, setCount] = useState(0);

const incrementCount = () => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
// Now each update correctly depends on the most recent state
};
// Optional: Use useEffect to see the updated state
useEffect(() => {
console.log(count); // 2
}, [count]);

return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment Count</button>
</div>
);
};

export default AsyncCounterComponent;

위 코드에서 각 호출은 setCount최신 상태 값을 사용하여 정확하고 순차적인 업데이트를 보장합니다. 이 접근 방식은 현재 상태에 의존하는 작업, 특히 여러 상태 업데이트가 빠르게 연속적으로 발생할 때 매우 중요합니다.

실수 4: 파생 데이터의 상태 오용 📊

❌ 문제 이해

자주 발생하는 오류는 기존 상태나 소품에서 파생될 수 있는 데이터에 상태를 사용하는 것입니다. 이러한 중복 상태는 복잡하고 오류가 발생하기 쉬운 코드로 이어질 수 있습니다.

예를 들어:

import React, { useState } from 'react';

const GreetingComponent = ({ name }) => {
const [greeting, setGreeting] = useState(`Hello, ${name}`);

return (
<div>{greeting}</div>
);
};

export default GreetingComponent;

여기서 greeting상태는 에서 직접 파생될 수 있으므로 필요하지 않습니다 name.

✅ 수정사항:

상태를 사용하는 대신 기존 상태나 소품에서 직접 데이터를 파생하세요.

import React from 'react';

const GreetingComponent = ({ name }) => {
const greeting = `Hello, ${name}`; // Directly derived from props

return (
<div>{greeting}</div>
);
};

export default GreetingComponent;

수정된 코드에서는 greeting가 prop에서 직접 계산되어 name구성 요소를 단순화하고 불필요한 상태 관리를 방지합니다.

결론 🚀

안정적이고 효율적인 애플리케이션을 구축하려면 React에서 후크를 효과적으로 사용하는 것이 useState중요합니다. 이전 상태 무시, 상태 불변성 관리 잘못, 비동기 업데이트 간과, 파생 데이터의 중복 상태 방지와 같은 일반적인 실수를 이해하고 방지함으로써 구성 요소 동작을 보다 원활하고 예측 가능하게 보장할 수 있습니다. 도움이 되었길 바랍니다!

UI Library 제작 및 사용기(react - storybook - tailwind -netlify)

· 5 min read
Alex Han
Software Engineer

Untitled

배경

사내에서 react를 활용한 어드민 페이지 개발이 다수의 서비스에 도입되면서 다수의 어드민 페이지 개발을 통일성 있게 개발해 보자는 니즈가 있었고 이를 위해 공용 모듈을 통해 통일성을 찾아보자는 방법을 연구하기 시작합니다.

프로세스 설계

공통 UI Library 프로세스 설계

공통 UI Library 프로세스 설계

  1. UI Library Developer는 reactjs, storybook, tailwindcss를 이용해 공통으로 사용할 만한 UI 컴포넌트를 제작해 storybook을 통해 이를 테스트해 볼 수 있도록 하는 웹을 Netlify를 통해 배포합니다.
  2. UI Designer와 함께 웹을 보면서 Storybook을 통해 수치를 조정하거나 컴포넌트 변경에 대해 논의 및 수정합니다.
  3. 협의된 컴포넌트를 Npm을 통해 라이브러리로 배포합니다.
  4. Frontend Developer는 Storybook을 통해 컴포넌트의 UI셋팅값에 맞게 UI Designer와 함께 웹 서비스를 개발합니다.
  • 장점
  1. 매 프로젝트별 동일하게 사용하는 컴포넌트 개발을 프로젝트 별로 분리 개발하지 않고 설치해서 바로 사용 가능합니다.
  2. Storybook을 웹에 배포해 UI Designer와 함께 직접 컴포넌트 셋팅값을 조정해 개발적으로도 가능 범주를 조정해 줄 수 있고 UI Designer도 실제 눈앞에서 조정해 볼 수 있어 효율적입니다.
  3. UI 라이브러리를 지속적으로 업그레이드 해 기존 프로젝트 별 일회성 컴포넌트가 아닌 자산화가 가능합니다.
  • 단점
  1. 공용으로 사용하는 모듈의 사용을 강력히 권고하면 프로젝트 별 자유도를 떨어트립니다.
  2. UI 라이브러리의 지속적 업데이트 관리가 필요하며 사용하는 프로젝트에서의 라이브러리 버전 특성을 고려해야 합니다.
  • 주요 고려 사항
  1. 공용으로 사용하는 모듈의 사용은 편의를 제공하기 위함이므로 사용을 권고하기 보다 원하면 사용하도록 하는 것이 좋을 것으로 보입니다.
  2. 공용으로 반드시 사용할 수 있을 만한 컴포넌트 단위를 신중히 선정해야 할 것으로 보입니다.

개발 과정

React Typescript 빌드 설정

  1. React 프로젝트 생성: npm init -y

  2. 필수 라이브러리 설치: npm install --save-dev react react-dom @types/node @types/react @types/react-dom typescript

  3. package.json 설정

    • peerDependencies란 실제로 패키지에서 require, import 하지는 않지만 특정 라이브러리 툴에 호환성을 필요로 할 경우 명시하는 dependencies인데 아래와 같이 react 버전과 react-dom 버전을 명시함으로써 해당 버전에서 사용할 수 있는 라이브러리임을 명시해 오류를 줄이기 위함입니다.
    • filesnpm 라이브러리 배포 시 폴더를 설정하기 위함입니다.
    • scripts는 tsc를 통해 빌드하는 타입스크립트 빌드 build:esm 과 바닐라JS사용 유저를 위해 build:cjs 를 둘 다 빌드하게 합니다.
    {
    "peerDependencies": {
    "react": "^18.1.0",
    "react-dom": "^18.1.0"
    },
    "files": ["dist"],
    "scripts": {
    "build": "rm -rf /dist && npm run build:esm && npm run build:cjs",
    "build:esm": "tsc",
    "build:cjs": "tsc --module CommonJS --outDir dist/cjs"
    }
    }
  4. Typescript 설정

    • 타입스크립트 초기 설정: tsc --init
    • tsconfig.json 설정
    {
    "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "declaration": true,
    "esModuleInterop": true,
    "jsx": "react",
    "lib": ["ES5", "ES2015", "ES2016", "DOM", "ESNext"],
    "types": ["node"],
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noUnusedLocals": true,
    "outDir": "dist/esm",
    "sourceMap": true,
    "strict": true,
    "target": "ES6"
    },
    "include": ["src/**/*.ts", "src/**/*.tsx"]
    }

Storybook 설정

  1. Storybook 초기 설정: npx sb init

  2. 실행 확인: npm run storybook

    Untitled

Npm 배포

  1. 라이브러리로 배포 후 사용하기 위해서는 path 설정을 해줘야 하는데 이를 위해 컴포넌트 별로 export * from “./Button” 이런 식의 경로 설정을 폴더별 index.ts에 적용합니다.
  2. 빌드: npm run build
  3. 배포: npm publish (npm login이 안되어 있다면 이를 선행해야 합니다.)

스크린샷 2022-05-27 오전 10.53.18.png

Netlify Storybook 배포

  1. 위의 storybook 초기 설정을 하고 나면 자동으로 package.json"build-storybook": "build-storybook” script가 생성되는데 이를 이용해 static한 파일들을 storybook-static폴더에 생성할 수 있습니다.(storybook 배포용 폴더)
  2. Netlify에 접속해 해당 깃허브를 연결하고 배포 시 Build Command를 npm run build-storybook 으로 해 storybook-static 폴더를 생성하고 이를 Output Directory로 설정해 깃허브에 푸시하면 storybook 웹싸이트가 자동으로 publish 되도록 자동 배포가 쉽게 설정됩니다.

TailwindCss 적용

  1. 필요 라이브러리 설치 및 초기 설정합니다.
    • 아래를 실행하면 자동으로 tailwind.config.js 가 생성되는데 이 설정파일을 통해 tailwind에서 설정하고 있는 색, 크기, 적용범위, 플러그인 등을 모두 설정할 수 있습니다.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
  1. tailwind를 사용하기 위한 css 파일을 생성합니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 컴포넌트 라이브러리와 storybook 확인 시 모두 tailwind를 적용하기 위해 src/index.ts.storybook/preview.jstailwind.cssimport 하면 모두 적용됩니다.
  2. 빌드 셋팅

tailwind 공식 홈을 참고해 tailwindcss 파일을 빌드 파일에 추가합니다.

{
"build": "rm -rf /dist && npm run build:esm && npm run build:cjs && npm run build:tailwind",
"build:esm": "tsc",
"build:cjs": "tsc --module CommonJS --outDir dist/cjs",
"build:tailwind": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/cjs/styles/tailwind.css && tailwindcss -i ./src/styles/tailwind.css -o ./dist/esm/styles/tailwind.css"
}

결론

tailwindcss가 적용된 형태로 storybook 웹 싸이트가 구현된 모습

tailwindcss가 적용된 형태로 storybook 웹 싸이트가 구현된 모습입니다.

npm 패키지 배포된 모습

npm 패키지 배포된 모습입니다.

배포한 라이브러리 설치한 코드

배포한 라이브러리 설치한 코드입니다.

설치한 라이브러리의 버튼을 가져다 사용한 코드

설치한 라이브러리의 버튼을 가져다 사용한 코드입니다.

UI Library를 직접 설치해 웹에서 사용한 모습

UI Library를 직접 설치해 웹에서 사용한 모습입니다.

***참고사항

  1. 라이브러리를 설치는 정상적으로 되고 실행도 정상적으로 되지만 sourcemap 오류가 발생하는데 이를 해결하기 위해 .env 파일에 GENERATE_SOURCEMAP=false 를 작성해 저장하면 실행 시 오류 메시지가 보이지 않습니다.
  2. tailwindcss 설정 순서에 따라 적용이 잘 안 될 수 있어 순서대로 설정해야 합니다.
  3. 소스: https://github.com/hanhyeonkyu/react-ui-lib-ts-test

참고 문서

UI라이브러리 제작: https://javascript.plainenglish.io/build-a-react-component-library-using-typescript-storybook-86d3562aa53a

Storybook - Tailwind Netlify: https://theodorusclarence.com/blog/nextjs-storybook-tailwind

TailwindCss 공홈: https://tailwindcss.com/docs/guides/create-react-app