<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>CreateU</title>
    <link>https://noeyh66.tistory.com/</link>
    <description>기록하고 성장하는 개발자, 최유현 입니다.
github : https://github.com/Choiyuhyeon
</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 05:20:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>noeyh</managingEditor>
    <image>
      <title>CreateU</title>
      <url>https://tistory1.daumcdn.net/tistory/5623832/attach/c50315648bb6455884b8aac070192933</url>
      <link>https://noeyh66.tistory.com</link>
    </image>
    <item>
      <title>Next.js + Storybook(Vite) 환경에서 SVG 컴포넌트(SVGR) 로드 에러 해결기</title>
      <link>https://noeyh66.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코드잇 프론트엔드 부트캠프 23기 파이널 프로젝트를 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 초기 공통컴포넌트 설계 중, Next.js 프로젝트에서 디자인 아이콘(svg)을 깔금하게 관리하기 위해 SVGR을 설정하여 React&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트처럼 임포트해서 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779987790980&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ImgEmpty from '@/assets/icons/img-empty.svg';

// JSX에서 일반 컴포넌트처럼 사용
&amp;lt;ImgEmpty width={122} height={122} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js환경(웹 브라우저)에서는 정상적으로 렌더링되던 컴포넌트였다. 하지만 이번 프로젝트는 cdd개발을 도입하여 storybook으로 모든 컴포넌트를 테스트 하는 방식을 택했기 때문에 stories.tsx파일을 따로 만들어 테스트들 돌리던 와중...!&lt;br /&gt;실행하자마자 다음과 같은 런타임 에러를 뱉었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779988157385&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
  at createFiberFromElement (/node_modules/.cache/storybook/.../client-DW84vZ63.js:2945:11)
  at reconcileChildFibersImpl (/node_modules/.cache/storybook/.../client-DW84vZ63.js:3947:401)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 이게 무슨일이람? 어쨌든 에러 메세지가 말하길 &quot;React 컴포넌트나 내장 태그(String)이 오길 원했는데, Object가 들어와서 오류가 발생한 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 원인 분석&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 파악해보니 Next.js의 빌드 환경(Webpack/Turbopack)과 스토리북의 빌드 환경(vite)간의 설정이 차이가 있었고, 프레임워크 플러그인 간의 간섭이 얽혀있는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해 자세하게 설명하면,&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로젝트는 Next.js가 메인 프레임워크이면서, Storybook과 Vitest의 빌더로는 Vite를 사용하는 하이브리드 환경이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 SVGR을 적용할때 Next.js(webpack/turbopack)과 Storybook/Vitest(Vite) 양쪽 모두에 독립적인 설정을 맞춰줘야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 원인을 파악했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기 위해 시도한 방법을 아래에 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Try&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780083602562&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존 오류가 있던 코드
const config: StorybookConfig = {
  &quot;stories&quot;: [...],
  &quot;addons&quot;: [...],
  &quot;framework&quot;: &quot;@storybook/nextjs-vite&quot; // 단순 문자열로 지정됨
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 vite-plugin-svgr을 최우선으로 install 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1779988555843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -D vite-plugin-svgr&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 @storybook/nextjs-vite가 SVG파일을 가로채지 못하도록 excludeFiles설정을 적용하여 이미지 플러그인 대상에서 제외시켰다. 아래의 코드처럼 따로 저런 설정을 하지 않으면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기본 이미지 로더가 SVG를 미리 가로채기 때문에 SVGR 플러그인이 적용되지 않는다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Vite 빌더가 미리 가로채서 이것저것 하는것을 미리 하는 것을 방지)&lt;/p&gt;
&lt;pre id=&quot;code_1779988637314&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;framework: {
  name: '@storybook/nextjs-vite',
  options: {
    image: {
      excludeFiles: ['**/*.svg'], // SVG 파일 처리 제외
    },
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로는 viteFinal 훅을 추가하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storybook의 빌더인 vite에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;vite-plugin-svgr&lt;/span&gt; 플러그인을 넣었고, 스토리북 실행 시 모든 SVG파일을 React컴포넌트로 변환하도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779988723699&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; async viteFinal(config) { // 변경점 ②: Vite 빌드 환경설정 확장
    config.plugins = [
      svgr({ include: '**/*.svg' }), // SVGR 플러그인을 Vite 플러그인 목록에 추가
      ...(config.plugins ?? []),
    ];
    return config;
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 완성된 .storybook/main.ts의 모습은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779988757486&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { StorybookConfig } from '@storybook/nextjs-vite';
import svgr from 'vite-plugin-svgr';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    '@chromatic-com/storybook',
    '@storybook/addon-vitest',
    '@storybook/addon-a11y',
    '@storybook/addon-docs',
    '@storybook/addon-mcp',
  ],
  framework: {
    name: '@storybook/nextjs-vite',
    options: {
      image: {
        excludeFiles: ['**/*.svg'], // Next.js 이미지 모킹 간섭 방지
      },
    },
  },
  async viteFinal(config) {
    // svgr을 최우선 순위로 설정하여 기본 에셋 로더보다 먼저 가로채도록 함
    config.plugins = [
      svgr({
        exportAsDefault: true,
        include: /\.svg/i, // 쿼리 스트링(?import) 대응용 정규표현식
      }),
      ...(config.plugins ?? []),
    ];
    return config;
  },
};
export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 오류 정리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 svgr설정을 하지 않았기 때문에 결국 import Left 이런식으로 svgr을 쓰려 했을때 Left변수에 컴포넌트 함수가 아니라, 자바스크립트 객체가 할당이 되었고, 그 상태에서 쓰려고 했기 때문에 React가 뚱딴지 같은 객체를 보고 오류를 발생시킨 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 마치며 배운점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치며 배운점은 cdd개발에서의 storybook과 해당 프로젝트의 빌더의 특성을 파악하는 것이 굉장히 중요하단걸 느꼈다. Webpack기반의 Next.js의 설정 방식을 빡빡하게 이해하는 것이 중요하고 Vite 기반의 스토리북 설정을 혼용할 때 파일 확장자에 따른 변환 룰이 충돌하지 않는지, 아니면 빼먹은게 없는지에 관한 지식이 필요하다고 느꼈다.&lt;br /&gt;&lt;br /&gt;솔직히 어려운 문제였고 이곳에서 어려움을 겪어서 몇시간동안 어제 팀원들과 많은 시간을 소모했던 것 같다. 라이브러리나 프레임워크를 쓸 때, 좀 더 디테일한 오류를 개선하는 실력을 기르고 싶다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web/next</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/40</guid>
      <comments>https://noeyh66.tistory.com/40#entry40comment</comments>
      <pubDate>Fri, 29 May 2026 02:24:48 +0900</pubDate>
    </item>
    <item>
      <title>리액트 Context에 대해 알아보고 다크모드를 만들어보자! (feat: custom hook)</title>
      <link>https://noeyh66.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 사용하다 보면 컴포넌트 구조가 깊어지는 경우가 있다. 나는 예전에 리액트를 처음 공부할 때, atomic단위로 컴포넌트를 잘게잘게 쪼개어 개발한 경험이 있는데, 이때 상위 컴포넌트와 하위의 하위의 하위~ 컴포넌트의 깊이가 너무 깊어졌고, 이때 상위 컴포넌트의 데이터를 맨 밑바닥의 컴포넌트에게 전달해야 할 때, 불필요한 데이터를 중간단계에 위치한 컴포넌트들이 전달하는 불필요한 상황을 경험했다. 공부해보니 이를 'Props Drilling' 현상이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 설명할 Context API는 이런 번거로움 없이 데이터를 신의탑 웹툰의 '신수'마냥 &lt;b&gt;전역적으로 공유할 수 있게 해주는 도구&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(신수는 공기와 비슷한,,, 애니만화 덕후라 죄송합니다;;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이것을 어떻게 사용할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Context를 생성한다. createContext로 생성하며 이는 데이터를 담을 바구니라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Provider를 설정한다. 데이터를 공유할 범위를 정하고 값을 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Consumer를 사용한다. useContext로 필요한 곳에서 데이터를 꺼내 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드&lt;/p&gt;
&lt;pre id=&quot;code_1774815172606&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createContext, useContext } from 'react';

// 1. 바구니 만들기
const ThemeContext = createContext('light');

function App() {
  return (
    // 2. Provider로 감싸고 값 전달하기
    &amp;lt;ThemeContext.Provider value=&quot;dark&quot;&amp;gt;
      &amp;lt;Toolbar /&amp;gt;
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
}

function Toolbar() {
  return &amp;lt;ThemeButton /&amp;gt;;
}

function ThemeButton() {
  // 3. useContext 훅으로 데이터 바로 꺼내기
  const theme = useContext(ThemeContext);
  return &amp;lt;button&amp;gt;현재 테마: {theme}&amp;lt;/button&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다크 모드 응용 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774815261274&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { createContext, useContext, useState } from 'react';

// 1. Context 생성 (기본값 설정)
const ThemeContext = createContext();

export default function App() {
  const [theme, setTheme] = useState('light');

  // 테마를 전환하는 함수
  const toggleTheme = () =&amp;gt; {
    setTheme((prev) =&amp;gt; (prev === 'light' ? 'dark' : 'light'));
  };

  // 2. Provider로 상태와 함수를 객체로 전달
  return (
    &amp;lt;ThemeContext.Provider value={{ theme, toggleTheme }}&amp;gt;
      &amp;lt;div style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff',
        height: '100vh',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center'
      }}&amp;gt;
        &amp;lt;h1&amp;gt;{theme.toUpperCase()} 모드입니다&amp;lt;/h1&amp;gt;
        &amp;lt;ThemeToggleButton /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
}

// 3. 자식 컴포넌트 (중간 단계 없이 직접 데이터 사용)
function ThemeToggleButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    &amp;lt;button onClick={toggleTheme} style={{ padding: '10px 20px', cursor: 'pointer' }}&amp;gt;
      {theme === 'light' ? '  다크모드로 변경' : '☀️ 라이트모드로 변경'}
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value={{ theme, toggleTheme }}로 상태 theme와 함수인 toggleTheme까지도 전달하는 것을 볼 수 있다. 살짝 혁신적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 자식컴포넌트의 useContext를 사용하여 데이터를 직접 사용한 것을 볼 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰고싶을때 어디서나 저장해두었던 데이터를 사용하여 좀 더 편리하게 데이터를 다룰 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한단계 더 나아가서 구조를 더 깔끔하게 관리하기 위해 파일을 분리해보도록 하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일 분리 및 커스텀 훅 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThemeContext.js&lt;/p&gt;
&lt;pre id=&quot;code_1774816137911&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { createContext, useContext, useState } from 'react';

// 1. Context 생성
const ThemeContext = createContext();

// 2. 전역 상태를 관리할 Provider 컴포넌트
export function ThemeProvider({ children }) {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const toggleTheme = () =&amp;gt; setIsDarkMode((prev) =&amp;gt; !prev);

  return (
    &amp;lt;ThemeContext.Provider value={{ isDarkMode, toggleTheme }}&amp;gt;
      {children}
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
}

// 3. 외부에서 편하게 쓸 수 있는 커스텀 훅
export function useTheme() {
  return useContext(ThemeContext);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.js&lt;/p&gt;
&lt;pre id=&quot;code_1774816158358&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ThemeProvider } from './ThemeContext';
import Home from './Home';

function App() {
  return (
    &amp;lt;ThemeProvider&amp;gt;
      &amp;lt;Home /&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Home.js&lt;/p&gt;
&lt;pre id=&quot;code_1774816178790&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useTheme } from './ThemeContext';

function Home() {
  // useContext(ThemeContext) 대신 직접 만든 훅 사용!
  const { isDarkMode, toggleTheme } = useTheme();

  return (
    &amp;lt;div style={{ background: isDarkMode ? '#222' : '#fff', color: isDarkMode ? '#fff' : '#000' }}&amp;gt;
      &amp;lt;h1&amp;gt;{isDarkMode ? '다크 모드' : '라이트 모드'}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={toggleTheme}&amp;gt;모드 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 첫번째 바뀐점은 컴포넌트를 3단계로 분리했다는 것이다. 좀더 코드가 간결해지고 유지보수가 편해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로는 ThemeContext.js파일에서 useTheme라는 커스텀훅을 만들어 사용했다. 이를 통하여 consumer를 적용할 컴포넌트에서 (Home.js) useContext(ThemeContext)라고 길게 쓸 필요없이 useTheme라고 만들어놓은 커스텀훅을 사용하면 코드가 더 줄어든다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 커스텀훅을 만들때 if (!context) throw new Error(...)와 같은 예외처리를 넣어서 Provider 밖에서 사용하였을때의 에러를 미리 방지할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 react의 context API와 Custom Hook를 이용하여 간단한 다크모드를 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 context API도 완벽하지는 않은것이 Context의 값이 만약 바뀌면 해당 Context를 사용하는 모든 컴포넌트가 리렌더링 된다는 점에서, 입력창의 타이핑 데이터와 같이 너무 자주 바뀌는 컴포넌트에서 효율이 좋진 못하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새는 Redux나 Zustand와 같은 좀더 편리한 전역 상태 관리 라이브러리를 많이 사용하는 추세이긴 하지만, 그 전 단계로 상태관리의 개념을 이해하는데에도 도움이 많이 되었던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스팅은 여기서 끝~&lt;/p&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/39</guid>
      <comments>https://noeyh66.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 30 Mar 2026 05:43:24 +0900</pubDate>
    </item>
    <item>
      <title>리액트에서 왜 불변성을 지켜야 할까?</title>
      <link>https://noeyh66.tistory.com/38</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;intro&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 오늘은 도대체 왜 리액트에서 불변성을 강조하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;또 불변성을 지키면서 코드를 짜야 하는지 의문이 들어 게시글을 포스팅하게 되었습니다. 그럼 바로 시작해보겠습니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;불변성이란?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 공부하다 보면 가장 많이 듣는 단어 중 하나가 바로 '불변성'이죠. 의미는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상태를 직접 수정하지 말고&lt;/span&gt; 변하지 않게 하라는 말입니다. 하지만 왜 그렇게 번거롭게 그렇게 해야할까요?&lt;br /&gt;&lt;br /&gt;저는 리액트에서 불변성을 지켜야 하는 이유를 3가지로 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. 효율적인 상태 변경 감지&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;리액트는 상태가 변했는지 확인하기 위해 '얕은 비교'를 수행합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;리액트에서는 얕은 비교와 깊은 비교가 있는데요, 여기서 얕은 비교는 객체의 깊숙한 곳 까지 일일이 비교하지 않고 성능상 빠르게 비교하는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;상태는 크게 2가지로 구분할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;가변(mutable)상태와 불변(Immutable)상태&lt;/span&gt;이고,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;가변: 객체 내부의 값만 바꾸면 메모리 주소(참조값)은 그대로입니다. 리액트 입장에서는 주소가 똑같은 것만 보고, 바뀐게 없다고 판단하여 화면에 리렌더링 시키지 않습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;불변: 새로운 객체를 생성하면 메모리 주소가 달라집니다. 리액트는 주소가 바뀐 것을 인지하고 화면을 다시 그리는 리렌더링 과정을 수행합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;2. 예측 가능한 렌더링&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 직접 수정하게 되면 의도치 않은 버그가 발생할 확률이 높다고 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예시로, 여러 컴포넌트가 하나의 객체를 참고하고 있을 때 한 곳에서 데이터를 슬쩍 바꿔버리면, 다른 컴포넌트들은 왜 데이터가 변했는지 모르는 상태에서 화면이 꼬이게 됩니다. 불변성을 지키면 데이터의 흐름이 단방향으로 명확해지고, 상태 변화가 예측 가능해집니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3. 리액트의 고급 기능 활용&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;불변성을 유지한다는 것은 이전 상태의 복사본을 보관하고 있다는 뜻과 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Redux와 같은 상태 관리 도구에서 이전 상태로 되돌아가거나, 앞단계로 가는 기능을 배우신 분도 있고, 또 앞으로 배울 분들도 있으실텐데, 이 기능은 바로 이전 상태 객체들이 메모리에 살아있기 때문에 가능한 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;그러면 어떻게 하면 불변성을 지키는 코드를 작성할까?&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;리액트에서 불변성을 지키는 가장 대중적인 방법은 스프레드 오퍼레이터나 배열 메서드 (map, filter, concat)등을 사용하는 것입니다. 아래의 예시 코드를 참고해주세요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773327642795&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 나쁜 예: 원본을 직접 수정
const [user, setUser] = useState({ name: 'Choi', age: 25 });
user.name = 'Kim'; 

// ✅ 좋은 예: 새로운 객체를 생성
setUser({ ...user, name: 'Kim' });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 객체의 구조가 너무 깊어 관리가 힘들다면, Immer 라는 라이브러리가 있다고 합니다. 이를 사용하면 마치 직접 데이터를 수정하는 것 처럼! 쉽게 데이터를 변경하며 코드를 짜면서 불변성을 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 간단한 리액트의 불변성과 관련한 게시물을 써봤는데요, 포스팅이 점점 쌓이면서 재미가 조금씩 붙는다는 생각이 좀 듭니다. 좋은하루 보내세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;043&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/043.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/043.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/38</guid>
      <comments>https://noeyh66.tistory.com/38#entry38comment</comments>
      <pubDate>Fri, 13 Mar 2026 00:04:35 +0900</pubDate>
    </item>
    <item>
      <title>useRef는 어떻게 데이터를 변화시키지 않을 수 있을까?</title>
      <link>https://noeyh66.tistory.com/37</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;intro&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 오늘은 useRef를 사용하는 중, 왜 useRef는 리렌더링을 피할수 있는지에 대한 의문이 들어 포스팅을 하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. useRef가 리렌더링을 피하는 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;useRef가 값을 변화시켜도 UI를 변화시키지 않는 이유는 리액트의 렌더링 사이클에 개입하지 않기 때문이라고 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;자주쓰이는 useState와 비교를 하자면,&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- useState: 값이 변하면 리액트에게 &quot;데이터 변했으니까 화면 다시 그려!&quot;라고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;트리거(알람)을 보냅니다&lt;/span&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- useRef: 값이 변해도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;리액트에게 아무런 소식을 전하지 않습니다&lt;/span&gt;. 그냥 메모리 어딘가에 저장된 자바스크립트 객체의 속성만 바꿀 뿐입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;객체의 속성을 그냥 바꾸기만 한다고?&lt;/u&gt; 그게 무슨소리일까요?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 통해 예시를 들어보겠습니다. 아래는 useRef의 내부 구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772126712305&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function useRef(initialValue) {
  return { current: initialValue };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서 중요한 점은 ref.current = 10과 같이 값을 변경할 때, 이는 객체의 속성을 수정할 뿐 이라는 것입니다. 리액트는 State가 변경될 때처럼 &quot;상태 추적&quot;을 하지 않기 때문에, 값이 바뀌었는지조차 알지 못하고 결과적으로 렌더링을 하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 그러면 useRef가 왜 필요하고 이러한 기능을 쓰는 이유가 무엇일까요?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;저는 3가지로 질문에 대한 답을 정리했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UI와 상관없는 변수 관리&lt;/b&gt;: 이전 상태 값 저장, setTimeout, 스크롤 위치 등 화면에 직접 보여줄 필요가 없지만 유지해야하는것&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DOM요소 직접 접근&lt;/b&gt;: 애니메이션을 위해 DOM을 직접 만져야 하거나, 특정 인풋창에 포커스를 줄때도 사용합니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 최적화&lt;/b&gt;: 0.1초마다 변하는 값을 state로 관리하면 화면이 계속 깜빡거리며 성능이 저하되지만, useRef를 쓰면 내부적으로 값만 조용히 업데이트 하기 때문에 최적화를 했다고 볼수 있겠죠?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로&amp;nbsp; useRef는 렌더링과 독립적인 저장공간을 필요로 할때 사용하는 것! 이라고 정리했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/37</guid>
      <comments>https://noeyh66.tistory.com/37#entry37comment</comments>
      <pubDate>Fri, 27 Feb 2026 02:35:51 +0900</pubDate>
    </item>
    <item>
      <title>Tailwind가 대세가 된 이유에 관한 고찰</title>
      <link>https://noeyh66.tistory.com/36</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;intro&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 styled-components 수업을 듣던 중, 왜 tailwind가 아닌 해당 라이브러리를 사용하고 배우며 그것에 관해 고찰한 내용을 글로 써보았다. 하나의 주제를 잡고 파고들어 고찰을 하는 글을 써보는 것은 처음이지만, 구글링과 공부하면서 배운 내용을 정리해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Styled-components의 '유지보수 모드' 진입: 왜?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 해당 라이브러리는 javascript 안에서 css를 작성할 수 있게 했던 대표적인 Css-In-JS라이브러리였다. 컴포넌트 단위로 스타일을 정의할 수도 있고, props기반으로 동적 스타일링도 가능했기 때문에, React프로젝트에서 많이 사용되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React스타일링의 표준이라고 하면 아직 나에게는 Styled-components이다. 실제로 1-2년 전 리액트 프로젝트 2가지의 디자인을 모두 해당 라이브러리를 쓴 경험이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 라이브러리가 더이상 적극적인 개발을 진행하지 않으며, 기존 기능의 유지보수만 이루어진다는 '유지보수 모드'에 진입한다고 공식 발표를 했다. 이유를 찾아보니 다음과 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 리액트의 방향성 변화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Core팀은 Context API의 사용을 지양하는 방향을 택하고 있다. 특히 Server Component에서는 Context API를 사용할 수 없고, 그에 대한 별도의 마이그레이션 경로도 제시하지 않았다고 한다. 여기서 문제점이 발생하는데, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;styled-components는 내부적으로 Context를 사용하는 구조이기 때문에, Server Components환경에서 자연스럽게 호환이 어려워졌다&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. CSS-in-JS의 하락세, Tailwind CSS의 부상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한때 대세였던 CSS-in-JS방식은 점점 사람들에게 쓰이지 않기 시작했다. Tailwind CSS와 같은 유틸리티 퍼스트 방식*의 스타일링이 디자인 시스템에서 표준으로 잡기 용이하고 성능, 디버깅 편의성 등의 이유로 자연스럽게 Tailwind CSS를 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;u&gt;유틸리티 퍼스트 방식이란 클래스를 조합에서 스타일을 만드는 도구&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cytUMO/dJMcadOzOol/2Mo8Req6or4o7LbQLfReJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cytUMO/dJMcadOzOol/2Mo8Req6or4o7LbQLfReJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cytUMO/dJMcadOzOol/2Mo8Req6or4o7LbQLfReJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcytUMO%2FdJMcadOzOol%2F2Mo8Req6or4o7LbQLfReJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;929&quot; height=&quot;796&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 tailwindcss의 다운로드 량이 급부상하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 갑자기 tailwind가 대세가 된 이유?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 글 맥락과 비슷한 2번 주제이다. 어쩌면 대세라는 단어도 맞지만 이제는 '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;표준&lt;/span&gt;'이 될 정도로 리액트에 걸맞는 스타일링 도구라고 생각한다. 특히나 최근 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;Tailwind CSS v4&lt;/span&gt;의 등장은 프론트엔드 스타일링의 판도를 완전히 바꿨다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 엔진의 혁신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v4는 Rust언어로 작성된 새로운 엔진을 도입했다. 빌드 속도가 이전 버전보다 10배 이상이나 빨라졌다고 하며, 이로 인해 개발자 경험 (DX)의 느낌이 전과 다르다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Zero-Runtime &amp;amp; Zero-Config&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 비용이 없다. 모든 스타일이 빌드 타임에 정적 CSS파일로 생성되기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 복잡한 tailwind.config.js가 필요가 없어졌다. css파일 내에서 직접 변수를 선언하고 테마를 확장하는 방식으로 바뀌어서 설정이 매우 편리해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;글을 마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새로운 리액트 프로젝트를 시작해야 한다면 주저하지 말고 지금 가장 성능적으로도, 편의성도 뛰어난 Tailwind CSS v4를 사용해보는것이 좋을 것 같다. 물론 기존 프로젝트가 styled-components를 사용하면 그것을 어쩔수 없이 써야겠지만, 만약 그 프로젝트가 해당 라이브러리로 인하여 성능이슈를 낸다면 Tailwind로 갈아타는 것을 깊게 고려해봐야 할 지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음시간에는 '그래서 그렇게 좋은 Tailwind. 어떻게 사용하는 걸까?' 에 대한 포스팅을 진행하도록 하겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>web/css</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/36</guid>
      <comments>https://noeyh66.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 26 Feb 2026 20:19:02 +0900</pubDate>
    </item>
    <item>
      <title>리액트 공식문서 과제 오답노트</title>
      <link>https://noeyh66.tistory.com/35</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;intro&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 오늘도 리액트 공식문서 읽기로 돌아왔습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번시간이 공식문서 관련해서 포스팅하는건 마지막같네요.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 게시글은 리액트 공식문서에서 파트별로 마지막에 챌린지를 하는 부분에서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어렵거나 헷갈렸던 코드들을 정리해보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 바로 start~&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트에 Props 전달하기&lt;/p&gt;
&lt;pre id=&quot;code_1771486094030&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Card({ children, title }) {
  return (
    &amp;lt;div className=&quot;card&quot;&amp;gt;
      &amp;lt;div className=&quot;card-content&quot;&amp;gt;
        &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default function Profile() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Card title=&quot;Photo&quot;&amp;gt;
        &amp;lt;img
          className=&quot;avatar&quot;
          src=&quot;https://i.imgur.com/OKS67lhm.jpg&quot;
          alt=&quot;Aklilu Lemma&quot;
          width={100}
          height={100}
        /&amp;gt;
      &amp;lt;/Card&amp;gt;
      &amp;lt;Card title=&quot;About&quot;&amp;gt;
        &amp;lt;p&amp;gt;Aklilu Lemma was a distinguished Ethiopian scientist who discovered a natural treatment to schistosomiasis.&amp;lt;/p&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;card&amp;gt;태그 내부에 있는 것들은 모두 children이라는 예약된 props로 들어감.!&lt;br /&gt;children의 개념을 잘 몰랐어서 어떻게 img태그의 요소들이 props로 들어가나 했었다!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;조건부 렌더링&lt;/p&gt;
&lt;pre id=&quot;code_1771488839212&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Item({ name, isPacked }) {
  return (
    &amp;lt;li className=&quot;item&quot;&amp;gt;
      {isPacked ? (
        &amp;lt;del&amp;gt;
          {name + ' ✅'}
        &amp;lt;/del&amp;gt;
      ) : (
        name
      )}
    &amp;lt;/li&amp;gt;
  );
}

export default function PackingList() {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Sally Ride's Packing List&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;Item
          isPacked={true}
          name=&quot;Space suit&quot;
        /&amp;gt;
        &amp;lt;Item
          isPacked={true}
          name=&quot;Helmet with a golden leaf&quot;
        /&amp;gt;
        &amp;lt;Item
          isPacked={false}
          name=&quot;Photo of Tam&quot;
        /&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Item컴포넌트 내부에서 삼항연산자를 사용하는 것이 때로는 헷갈렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 안에서 많이 쓰이는 패턴이라고 들어서 삼항연산자 사용에 익숙해져야겠다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 and연산자도 조건부 렌더링을 할때 사용이 되는데,&lt;/p&gt;
&lt;pre id=&quot;code_1771489010146&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (
  &amp;lt;li className=&quot;item&quot;&amp;gt;
    {name} {isPacked &amp;amp;&amp;amp; '✅'}
  &amp;lt;/li&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 사용하여, ispacked면 체크를 렌더링 하고, 그렇지 않으면 아무것도 렌더링 하지 않는다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트에 응답하기: 해당코드의 문제점이 뭘까?&lt;/p&gt;
&lt;pre id=&quot;code_1771494625805&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function LightSwitch() {
  function handleClick() {
    let bodyStyle = document.body.style;
    if (bodyStyle.backgroundColor === 'black') {
      bodyStyle.backgroundColor = 'white';
    } else {
      bodyStyle.backgroundColor = 'black';
    }
  }

  return (
    &amp;lt;button onClick={handleClick()}&amp;gt;
      Toggle the lights
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 return문에서 handleClick옆에 괄호를 붙혀 렌더링 중 호출한다는 오류가 발생되게금 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;()호출을 삭제하여 &lt;span style=&quot;background-color: #343a46; color: #f6f7f9; text-align: start;&quot;&gt; &lt;/span&gt;&amp;lt;button onClick={handleClick}&amp;gt;으로 수정하면 잘 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;260219 &lt;br /&gt;다음 번 공부할 내용은 상호 작용성 더하기 부분이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/35</guid>
      <comments>https://noeyh66.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 19 Feb 2026 19:05:21 +0900</pubDate>
    </item>
    <item>
      <title>코드잇 스프린트 23기 프론트엔드 과정 1달차 회고록</title>
      <link>https://noeyh66.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1.&amp;nbsp;시작할&amp;nbsp;때의&amp;nbsp;목표 &lt;br /&gt;&lt;br /&gt;코드잇&amp;nbsp;스프린트&amp;nbsp;23기&amp;nbsp;프론트엔드&amp;nbsp;과정을&amp;nbsp;시작할&amp;nbsp;때,&amp;nbsp;가장&amp;nbsp;큰&amp;nbsp;목표는&amp;nbsp;두&amp;nbsp;가지였다. &lt;br /&gt;첫째, 자바스크립트와 리액트를 그냥 아는 수준에서 직접 구현할 수 있는 수준까지로 끌어올리는 것. &lt;br /&gt;둘째, 혼자 프론트엔드를 공부할 때 자주 무너졌던 학습 루틴을 부트캠프 환경에서 안정적으로 정착시키는 것이었다. &lt;br /&gt;&lt;br /&gt;처음에는 일단 대학교 프로젝트나 회사일에서 경험을 조금은 해봤으니까, &amp;ldquo;강의 내용을 따라가면 되겠지&amp;rdquo;라고 가볍게 생각했지만, 실제로는 개념 이해 + 실습 + 복습 + 과제까지 이어지면서 생각보다 빡빡했던 한달이었다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2.&amp;nbsp;한&amp;nbsp;달&amp;nbsp;동안&amp;nbsp;배운&amp;nbsp;것과&amp;nbsp;달라진&amp;nbsp;점 &lt;br /&gt;(1)&amp;nbsp;지식보다&amp;nbsp;중요한&amp;nbsp;건&amp;nbsp;구현력이라는&amp;nbsp;걸&amp;nbsp;체감 &lt;br /&gt;&lt;br /&gt;기본 문법은 알고 있다고 생각했는데, 막상 직접 코드를 짜보면 생각보다 막막한 부분이 많았고, 자주 막혔다. &lt;br /&gt;예를&amp;nbsp;들면, &lt;br /&gt;&lt;br /&gt;상태(state)와 이벤트 처리 연결&lt;br /&gt;&lt;br /&gt;컴포넌트 분리 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 올바른 사용법 등&lt;br /&gt;&lt;br /&gt;같은 부분에서 &amp;ldquo;이론은 아는데 코드로는 어색한&amp;rdquo; 순간이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;어려웠던&amp;nbsp;점과&amp;nbsp;극복&amp;nbsp;방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;학습량이 생각보다 많아 코딩테스트나 여러가지 프로젝트와 동시에 강의를 듣기에 생각보다 양이 많았는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간을 잘 쪼개서 하루 할당량을 무조건 채운다는 느낌으로 공부했다.&lt;br /&gt;&lt;br /&gt;막히는 코드 최소 재현 후 원인 분리도 진행했고,&lt;br /&gt;&lt;br /&gt;오늘 배운 것 3줄 요약기록을 메모장에 무조건 기록했다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;이번&amp;nbsp;달&amp;nbsp;아쉬운&amp;nbsp;점 &lt;br /&gt;&lt;br /&gt;진도&amp;nbsp;따라가기에&amp;nbsp;집중하다&amp;nbsp;보니,&amp;nbsp;개인&amp;nbsp;미니&amp;nbsp;프로젝트&amp;nbsp;시도&amp;nbsp;횟수가&amp;nbsp;적었다. &lt;br /&gt;&lt;br /&gt;&amp;ldquo;왜&amp;nbsp;이&amp;nbsp;구조로&amp;nbsp;짜야&amp;nbsp;하는지&amp;rdquo;에&amp;nbsp;대한&amp;nbsp;설계&amp;nbsp;고민보다,&amp;nbsp;정답&amp;nbsp;구현에&amp;nbsp;치우친&amp;nbsp;순간이&amp;nbsp;있었다. &lt;br /&gt;&lt;br /&gt;체력/집중력&amp;nbsp;관리가&amp;nbsp;흔들린&amp;nbsp;날에는&amp;nbsp;학습&amp;nbsp;효율이&amp;nbsp;확&amp;nbsp;떨어졌다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;회고록은 처음이라 좀 더 상세하게 쓰고 싶었지만 아직 글을 잘 쓰지 못하는 것 같다!&lt;br /&gt;다음주차 회고록부턴 상세한 상황과 글들을 쓰도록 하겠다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>memo</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/34</guid>
      <comments>https://noeyh66.tistory.com/34#entry34comment</comments>
      <pubDate>Sun, 15 Feb 2026 13:52:08 +0900</pubDate>
    </item>
    <item>
      <title>리액트 공식문서 - 틱택토 게임</title>
      <link>https://noeyh66.tistory.com/33</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;intro&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 시간에는 리액터 공식문서 초반 페이지에 있는 틱택토 게임을 만들어 보고, 학습한 내용을 정리해보겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. full code&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770796130610&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function Square({ value, onSquareClick }) {
  return (
    &amp;lt;button className=&quot;square&quot; onClick={onSquareClick}&amp;gt;
      {value}
    &amp;lt;/button&amp;gt;
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i &amp;lt; lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] &amp;amp;&amp;amp; squares[a] === squares[b] &amp;amp;&amp;amp; squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = &quot;Winner: &quot; + winner;
  } else {
    status = &quot;Next player: &quot; + (xIsNext ? &quot;X&quot; : &quot;O&quot;);
  }

  function handleClick(i) {
    if (squares[i] || calculateWinner(squares)) {
      // 애초에 square에 값이 있으면 || 승자가 정해졌다면 =&amp;gt;  조기리턴
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = &quot;X&quot;;
    } else {
      nextSquares[i] = &quot;O&quot;;
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;div className=&quot;board-row&quot;&amp;gt;
        &amp;lt;Square value={squares[0]} onSquareClick={() =&amp;gt; handleClick(0)} /&amp;gt;
        &amp;lt;Square value={squares[1]} onSquareClick={() =&amp;gt; handleClick(1)} /&amp;gt;
        &amp;lt;Square value={squares[2]} onSquareClick={() =&amp;gt; handleClick(2)} /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;board-row&quot;&amp;gt;
        &amp;lt;Square value={squares[3]} onSquareClick={() =&amp;gt; handleClick(3)} /&amp;gt;
        &amp;lt;Square value={squares[4]} onSquareClick={() =&amp;gt; handleClick(4)} /&amp;gt;
        &amp;lt;Square value={squares[5]} onSquareClick={() =&amp;gt; handleClick(5)} /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;board-row&quot;&amp;gt;
        &amp;lt;Square value={squares[6]} onSquareClick={() =&amp;gt; handleClick(6)} /&amp;gt;
        &amp;lt;Square value={squares[7]} onSquareClick={() =&amp;gt; handleClick(7)} /&amp;gt;
        &amp;lt;Square value={squares[8]} onSquareClick={() =&amp;gt; handleClick(8)} /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;status&quot;&amp;gt;{status}&amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 주요 핵심개념&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 재사용성: Square라는 작은 단위의 컴포넌트를 만들어 Board에서 9번 재사용&lt;/li&gt;
&lt;li&gt;State 끌어올리기: 각 사각형의 상태를 Square가 직접 가지지 않고, 부모 컴포넌트인 Board에서 관리하게 함으로써 데이터의 일관성을 유지했음&lt;/li&gt;
&lt;li&gt;불변성 지키기: 배열의 값을 직접 수정x, squares.slice()를 사용하여 기존 배열을 직접 수정하지 않고 복사본을 만들어 업데이트함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 코드 분석하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Square 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- value를 받아 화면에 표시하고, 클릭 시 부모로부터 받은 onSquareClick 함수를 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;calcualteWinner(승리에 관한 로직)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 8가지 승리 조합(가로, 세로, 대각선)을 미리 정의해두고, 현재 squares 상태와 비교하여 승자를 판별함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleClick (게임 제어 관련 로직)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조기리턴: 이미 값이 있거나 승자가 결정되었다면 클릭을 무시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상태 업데이트: 현재 턴에 맞춰 배열을 업데이트하고 다음 턴으로 넘김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 flow 요약&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 Square를 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Board의 handleClick함수가 호출됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. squares배열의 복사본을 만들어 해당 인덱스에 x또는 o를 채움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. setSAquares와 setXIsNext로 상태를 업데이트함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 상태가 변했으므로 화면이 다시 그려지고, 승리 여부를 체크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틱택토 만들기를 마치며,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 배열을 복사해서 써야 한다는 개념이 때로는 낯설었지만, 리액트의 렌더링 원리를 이해하는 데 큰 도움이 되었고, 여러가지 로직과 props, state, 변수의 현재 상태 등의 개념을 학습하면서 리액트의 기초에 대해 많이 배워가는 느낌이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음시간에도 리액트 공부로 돌아오겠습니다~&lt;/p&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/33</guid>
      <comments>https://noeyh66.tistory.com/33#entry33comment</comments>
      <pubDate>Wed, 11 Feb 2026 16:57:10 +0900</pubDate>
    </item>
    <item>
      <title>리액트 기본 - 공식문서 리딩</title>
      <link>https://noeyh66.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;intro&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;안녕하세요. 오늘은 리액트를 공부할 때 가장 좋은방안을 찾다가 리액트 공식문서가 그렇게 좋다고 많은 사람들이 얘기하셔서 속는셈치고 공부를 해봤습니다. 그런데 다른 어떤 책들보다 설명이 잘 되어 있는 것 같더라고요. 그러면 시작해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;gogo~&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. count가 화면에 렌더링되는 버튼을 두가지 만들었는데, 첫번째 버튼과 두번째 버튼의 count가 따로 올라가는 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770790107730&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function MyApp() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Counters that update separately&amp;lt;/h1&amp;gt;
      &amp;lt;MyButton /&amp;gt;
      &amp;lt;MyButton /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      Clicked {count} times
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. count가 화면에 렌더링되는 버튼을 두가지 만들었는데, 첫번째 버튼과 두번째 버튼의 count가 동시에 올라가는 코드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770790182799&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Counters that update together&amp;lt;/h1&amp;gt;
      &amp;lt;MyButton count={count} onClick={handleClick} /&amp;gt;
      &amp;lt;MyButton count={count} onClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function MyButton({ count, onClick }) {
  return (
    &amp;lt;button onClick={onClick}&amp;gt;
      Clicked {count} times
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;두 코드의 차이점은 무엇일까요?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;두 코드의 핵심 차이점은 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;0&quot;&gt;상태(State)를 어디에서 관리하느냐&lt;/b&gt;와 그로 인해 &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;0&quot;&gt;데이터가 어떻게 공유되느냐&lt;/b&gt;에 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;리액트에서는 이를 보통 **&quot;State 끌어올리기(Lifting State Up)&quot;**라는 개념으로 설명합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-path-to-node=&quot;2&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. 코드 1: 개별적인 상태 관리 (Isolated State)&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 코드에서는 useState가 MyButton 컴포넌트 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;4&quot;&gt;내부&lt;/b&gt;에 정의되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;독립성:&lt;/b&gt; 각 MyButton 인스턴스는 자신만의 count 상태를 가집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;동작:&lt;/b&gt; 왼쪽 버튼을 클릭해도 오른쪽 버튼의 숫자는 변하지 않습니다. 메모리상에서 두 버튼의 상태는 완전히 별개입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;데이터 흐름:&lt;/b&gt; 상태가 컴포넌트 내부에서 폐쇄적으로 관리됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;6&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 코드 2: 공유된 상태 관리 (Shared State)&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이 코드에서는 useState가 부모 컴포넌트인 MyApp으로 옮겨졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;동기화:&lt;/b&gt; 부모인 MyApp이 하나의 count 상태를 관리하고, 이를 두 자식(MyButton)에게 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Props&lt;/b&gt;로 내려줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;동작:&lt;/b&gt; 어떤 버튼을 누르든 부모의 handleClick이 실행되어 부모의 상태가 변하고, 그 결과 두 버튼의 숫자가 &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;9,1,0&quot;&gt;동시에&lt;/b&gt; 업데이트됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;데이터 흐름:&lt;/b&gt; 상태가 부모에서 자식으로 흐르는 &quot;단방향 데이터 흐름&quot;을 보여줍니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 틱택토 게임 만들어보기를 해보겠습니다~&lt;/p&gt;</description>
      <category>web/react</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/32</guid>
      <comments>https://noeyh66.tistory.com/32#entry32comment</comments>
      <pubDate>Wed, 11 Feb 2026 15:46:08 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트와 브라우저 관련 - (element.children 과 같은 ~)</title>
      <link>https://noeyh66.tistory.com/31</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;intro&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저번 포스팅에 이어 이번에는 자바스크립트와 브라우저에 대한 정리를 해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;1.&amp;nbsp;window&amp;nbsp;객체&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;window&amp;nbsp;객체는&amp;nbsp;브라우저&amp;nbsp;창을&amp;nbsp;대변하면서&amp;nbsp;자바스크립트에서&amp;nbsp;최상단에&amp;nbsp;존재하는&amp;nbsp;객체입니다. &lt;br /&gt;자바스크립트&amp;nbsp;코드&amp;nbsp;어느&amp;nbsp;곳에서나&amp;nbsp;항상&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;객체이기&amp;nbsp;때문에&amp;nbsp;전역&amp;nbsp;객체,&amp;nbsp;영어로는&amp;nbsp;Global&amp;nbsp;Object라고&amp;nbsp;부릅니다. &lt;br /&gt;어떤&amp;nbsp;프로퍼티나&amp;nbsp;메소드를&amp;nbsp;사용하든&amp;nbsp;결국&amp;nbsp;전역&amp;nbsp;객체&amp;nbsp;내부의&amp;nbsp;것이기&amp;nbsp;때문에&amp;nbsp;앞에&amp;nbsp;window.을&amp;nbsp;생략할&amp;nbsp;수도&amp;nbsp;있습니다. &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;2.&amp;nbsp;DOM&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;DOM이란&amp;nbsp;Document&amp;nbsp;Object&amp;nbsp;Model의&amp;nbsp;약자로,&amp;nbsp;한국어로는&amp;nbsp;문서&amp;nbsp;객체&amp;nbsp;모델입니다. &lt;br /&gt;간단하게&amp;nbsp;표현하면&amp;nbsp;웹&amp;nbsp;페이지에&amp;nbsp;나타나는&amp;nbsp;HTML&amp;nbsp;문서&amp;nbsp;전체를&amp;nbsp;객체로&amp;nbsp;표현한&amp;nbsp;것으로&amp;nbsp;생각하면&amp;nbsp;됩니다. &lt;br /&gt;이때&amp;nbsp;각&amp;nbsp;객체를&amp;nbsp;노드(Node)라는&amp;nbsp;용어로&amp;nbsp;표현하고,&amp;nbsp;태그는&amp;nbsp;요소&amp;nbsp;노드,&amp;nbsp;문자는&amp;nbsp;텍스트&amp;nbsp;노드로&amp;nbsp;구분됩니다. &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;3.&amp;nbsp;DOM&amp;nbsp;트리&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;HTML의&amp;nbsp;계층&amp;nbsp;구조는&amp;nbsp;DOM에서도&amp;nbsp;반영되는데&amp;nbsp;이러한&amp;nbsp;계층구조를&amp;nbsp;나무에&amp;nbsp;비유해서&amp;nbsp;DOM&amp;nbsp;트리라고&amp;nbsp;부릅니다. &lt;br /&gt;각&amp;nbsp;노드&amp;nbsp;간의&amp;nbsp;관계는&amp;nbsp;부모,&amp;nbsp;자식,&amp;nbsp;형제라는&amp;nbsp;용어로&amp;nbsp;표현합니다. &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;4.&amp;nbsp;DOM&amp;nbsp;이동&amp;nbsp;시&amp;nbsp;활용&amp;nbsp;가능한&amp;nbsp;프로퍼티&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로퍼티 유형 결과&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;element.children&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 자식 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;element의 자식 요소 모음(HTMLCollection) &lt;br /&gt;element.firstElementChild&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;자식 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;element의 첫 번째 자식 요소 하나 &lt;br /&gt;element.lastElementChild&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;자식 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; element의 마지막 자식 요소 하나 &lt;br /&gt;element.parentElement&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;부모 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; element의 부모 요소 하나 &lt;br /&gt;element.previousElementSibling 형제 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;element의 이전(previous) 혹은 좌측(left)에 있는 요소 하나 &lt;br /&gt;element.nextElementSibling&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 형제 요소 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;element의 다음(next) 혹은 우측(right)에 있는 요소 하나 &lt;br /&gt;node.childNodes&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 자식 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;node의 자식 노드 모음(NodeList) &lt;br /&gt;node.firstChild&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 자식 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; node의 첫 번째 자식 노드 하나 &lt;br /&gt;node.lastChild&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;자식 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;node의 마지막 자식 노드 하나 &lt;br /&gt;node.parentNode&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 부모 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;node의 부모 요소 하나 &lt;br /&gt;node.previousSibling&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;형제 노드&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; node의 이전(previous) 혹은 좌측(left)에 있는 노드 하나 &lt;br /&gt;node.nextSibling&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;형제 노드 node의 다음(next) 혹은 우측(right)에 있는 노드 하나&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;5.&amp;nbsp;주요&amp;nbsp;요소&amp;nbsp;노드&amp;nbsp;프로퍼티&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로퍼티 내용 참고사항 &lt;br /&gt;element.innerHTML 요소&amp;nbsp;노드&amp;nbsp;내부의&amp;nbsp;HTML코드&amp;nbsp;문자열로&amp;nbsp;리턴 요소&amp;nbsp;안의&amp;nbsp;정보를&amp;nbsp;확인할&amp;nbsp;수도&amp;nbsp;있지만, &lt;br /&gt;내부의&amp;nbsp;HTML&amp;nbsp;자체를&amp;nbsp;수정할&amp;nbsp;때&amp;nbsp;좀&amp;nbsp;더&amp;nbsp;자주&amp;nbsp;활용 &lt;br /&gt;element.outerHTML 요소&amp;nbsp;노드&amp;nbsp;자체의&amp;nbsp;전체적인&amp;nbsp;HTML&amp;nbsp;코드를&amp;nbsp;문자열로&amp;nbsp;리턴 outerHTML은&amp;nbsp;새로운&amp;nbsp;값을&amp;nbsp;할당하면 &lt;br /&gt;요소&amp;nbsp;자체가&amp;nbsp;교체되어&amp;nbsp;버리기&amp;nbsp;때문에&amp;nbsp;주의 &lt;br /&gt;element.textContent 요소&amp;nbsp;노드&amp;nbsp;내부의&amp;nbsp;내용들&amp;nbsp;중에서&amp;nbsp;HTML을&amp;nbsp;제외하고&amp;nbsp;텍스트만&amp;nbsp;리턴 textContent는&amp;nbsp;말그대로&amp;nbsp;텍스트만&amp;nbsp;다루기&amp;nbsp;때문에 &lt;br /&gt;HTML태그를&amp;nbsp;쓰더라도&amp;nbsp;모두&amp;nbsp;텍스트로&amp;nbsp;처리됨&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;6.&amp;nbsp;요소&amp;nbsp;노드&amp;nbsp;다루기&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;요소&amp;nbsp;노드&amp;nbsp;만들기:&amp;nbsp;document.createElement('태그이름') &lt;br /&gt;요소&amp;nbsp;노드&amp;nbsp;꾸미기:&amp;nbsp;element.textContent,&amp;nbsp;element.innerHTML,&amp;nbsp;... &lt;br /&gt;요소&amp;nbsp;노드&amp;nbsp;추가&amp;nbsp;혹은&amp;nbsp;이동하기:&amp;nbsp;element.prepend,&amp;nbsp;element.append,&amp;nbsp;element.after,&amp;nbsp;element.before &lt;br /&gt;요소&amp;nbsp;노드&amp;nbsp;삭제하기:&amp;nbsp;element.remove()&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;7.&amp;nbsp;HTML&amp;nbsp;속성&amp;nbsp;다루기&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대부분의&amp;nbsp;HTML&amp;nbsp;속성은&amp;nbsp;DOM&amp;nbsp;객체의&amp;nbsp;프로퍼티로&amp;nbsp;변환이&amp;nbsp;됩니다. &lt;br /&gt;하지만,&amp;nbsp;표준&amp;nbsp;속성이&amp;nbsp;아닌&amp;nbsp;경우에는&amp;nbsp;프로퍼티로&amp;nbsp;변환이&amp;nbsp;안&amp;nbsp;되는데요.&amp;nbsp;아래&amp;nbsp;메소드를&amp;nbsp;활용하면&amp;nbsp;표준이&amp;nbsp;아닌&amp;nbsp;HTML&amp;nbsp;속성들도&amp;nbsp;다룰&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&gt;속성에&amp;nbsp;접근하기:&amp;nbsp;element.getAttribute('속성') &lt;br /&gt;속성&amp;nbsp;추가(수정)하기:&amp;nbsp;element.setAttribute('속성',&amp;nbsp;'값') &lt;br /&gt;속성&amp;nbsp;제거하기:&amp;nbsp;element.removeAttribute('속성')&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;8.&amp;nbsp;스타일&amp;nbsp;다루기&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자바스크립트로&amp;nbsp;태그의&amp;nbsp;스타일을&amp;nbsp;다루는&amp;nbsp;방법에는&amp;nbsp;크게&amp;nbsp;두&amp;nbsp;가지가&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&gt;style&amp;nbsp;프로퍼티&amp;nbsp;활용하기:&amp;nbsp;element.style.styleName&amp;nbsp;=&amp;nbsp;'value'; &lt;br /&gt;class&amp;nbsp;변경을&amp;nbsp;통해&amp;nbsp;간접적으로&amp;nbsp;스타일&amp;nbsp;적용하기:&amp;nbsp;element.className,&amp;nbsp;element.classList&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;8-1.&amp;nbsp;classList의&amp;nbsp;유용한&amp;nbsp;메소드 &lt;br /&gt;메소드 내용 참고사항 &lt;br /&gt;classList.add 클래스&amp;nbsp;추가하기 여러&amp;nbsp;개의&amp;nbsp;값을&amp;nbsp;전달하면&amp;nbsp;여러&amp;nbsp;클래스&amp;nbsp;추가&amp;nbsp;가능 &lt;br /&gt;classList.remove 클래스&amp;nbsp;삭제하기 여러&amp;nbsp;개의&amp;nbsp;값을&amp;nbsp;전달하면&amp;nbsp;여러&amp;nbsp;클래스&amp;nbsp;삭제&amp;nbsp;가능 &lt;br /&gt;classList.toggle 클래스&amp;nbsp;없으면&amp;nbsp;추가,&amp;nbsp;있으면&amp;nbsp;삭제하기 하나의&amp;nbsp;값만&amp;nbsp;적용&amp;nbsp;가능하고, &lt;br /&gt;두&amp;nbsp;번째&amp;nbsp;파라미터로&amp;nbsp;추가&amp;nbsp;또는&amp;nbsp;삭제&amp;nbsp;기능을&amp;nbsp;강제할&amp;nbsp;수&amp;nbsp;있음 &lt;/p&gt;</description>
      <category>web/js</category>
      <author>noeyh</author>
      <guid isPermaLink="true">https://noeyh66.tistory.com/31</guid>
      <comments>https://noeyh66.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 2 Feb 2026 19:45:03 +0900</pubDate>
    </item>
  </channel>
</rss>