React/공식 문서

상태 구조 설계와 공유

favor 2026. 5. 7. 16:09

State를 사용해 Input 다루기

React는 선언적인 방식으로 UI를 조작
개별적인 UI를 직접 조작하는 것 대신, 컴포넌트 내부에 여러 state를 묘사하고 사용자의 입력에 따라 state를 변경

 

✔️ 선언형 UI와 명령형 UI 비교

선언형

  • 무엇을(What)에 집중
  • 결과가 중요
  • 리액트처럼 상태에 따라 화면을 그리는 것 

 

명령형

  • 어떻게(How)에 집중
  • 과정이 중요
  • 한 단계라도 빼먹으면 안 됨
  • 자바스크립트로 DOM을 직접 설정하는 것 

 

✔️ UI를 선언적인 방식으로 생각하기

UI를 React에서 다시 구현하는 과정

  1. 컴포넌트의 다양한 시각적 state를 확인
  2. 무엇이 state 변화를 트리거하는지 알아내기
  3. useState를 사용해서 메모리의 state를 표현
  4. 불필요한 state 변수를 제거
  5. state 설정을 위해 이벤트 핸들러를 연결

 

✔️ 첫 번째: 컴포넌트의 다양한 시각적 state 확인하기

사용자가 볼 수 있는 UI의 모든 state를 시각화해야 함

  • Empty: 폼은 비활성화된 '제출' 버튼을 가지고 있음
  • Typing: 폼은 활성화된 '제출' 버튼을 가지고 있음
  • Submitting: 폼은 완전히 비활성화되고 스피너가 보임
  • Success: 폼 대신에 성공 메시지가 보임
  • Error: 'Typing' state와 동일하지만 오류 메시지가 보임 

 

✔️ 두 번째: 무엇이 state 변화를 트리거하는지 알아내기

두 종류의 인풋 유형

  • 휴먼 인풋
    • 버튼을 누르거나, 필드를 입력하거나, 링크를 이동하는 것 등
  • 컴퓨터 인풋
    • 네트워크 응답이 오거나, 타임아웃이 되거나, 이미지를 로딩하는 등

 

두 가지 모두 UI를 업데이트하기 위해서는 state 변수를 설정해야 함

  • 텍스트 인풋을 변경하면 (휴먼) 텍스트 상자가 비어있는지 여부에 따라 state를 Empty에서 Typing 또는 그 반대로 변경해야 함
  • 제출 버튼을 클릭하면 (휴먼) Submitting state를 변경해야 함
  • 네트워크 응답이 성공적으로 오면 (컴퓨터) Success state를 변경해야 함
  • 네트워크 요청이 실패하면 (컴퓨터) 해당하는 오류 메시지와 함께 Error state를 변경해야 함

 

✔️ 세 번째: 메모리의 state를 useState로 표현하기

useState를 사용하여 컴포넌트의 시각적 state를 표현해야 함

 

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);

^ 인풋의 answer와 가장 최근에 발생한 error를 저장 

 

const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

^ 가능한 모든 시각적 state를 커버하기 위함

 

✔️ 네 번째: 불필요한 state 변수를 제거하기

리팩토링의 목표 : state가 사용자에게 유효한 UI를 보여주지 않는 경우를 방지하는 것 

ex) 오류 메시지가 나타났는데 인풋이 비활성화 돼 있어 유저가 오류를 수정할 수 없는 상황

 

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

^ 위의 일곱 개의 변수에서 확연히 줄어듦

 

✔️ 다섯 번째: state 설정을 위해 이벤트 핸들러를 연결하기

// App.js

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>That's right!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // 네트워크에 접속한다고 가정해봅시다.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

 

 

 


State 구조 선택하기

✔️ State 구조화 원칙

상태를 갖는 구성요소를 작성할 때, 사용할 state 변수의 수와 데이터의 형태를 선택해야 함

 

더 나은 state 설계를 위한 5가지 원칙 

  1. 연관된 state 그룹화하기
    • 두 개 이상의 state 변수를 항상 동시에 업데이트한다면, 단일 state 변수로 병합하는 것을 고려하기
  2. State의 모순 피하기
    • 여러 state 조각이 서로 모순되고 불일치할 수 있는 방식으로 state를 구성하는 것은 실수가 발생할 여지를 만듦
  3. 불필요한 state 피하기
    • 렌더링 중에 컴포넌트의 props나 기존 state 변수에서 일부 정보를 계산할 수 있다면, 컴포넌트의 state에 해당 정보를 넣지 않아야 함
  4. State의 중복 피하기
    • 여러 상태 변수 간 또는 중첩된 객체 내에서 동일한 데이터가 중복될 경우 동기화를 유지하기 어려움
  5. 깊게 중첩된 state 피하기
    • 깊게 계층화된 state는 업데이트하기 쉽지 않음
    • 가능한 state를 평탄화 방식으로 구성하는 것이 좋음 

 

✔️ 연관된 state 그룹화하기

단일 state 변수와 다중 state 변수 사이에서 무엇을 사용할지 불확실한 경우

 

const [x, setX] = useState(0);
const [y, setY] = useState(0);

or

const [position, setPosition] = useState({ x: 0, y: 0 });

 

기술적으로 두 가지 접근 방식 모두 사용 가능

하지만, 두 개의 state 변수가 항상 함께 변경된다면 단일 state 변수로 통합하는 것이 좋음

 

데이터를 객체나 배열로 그룹화하는 또 다른 경우 : 필요한 state 조각 수를 모를 때

ex) 사용자가 커스텀 필드를 추가할 수 있는 양식이 있는 경우에 유용

 

✔️ State의 모순 피하기

// App.js

import { useState } from 'react';

export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  const [isSent, setIsSent] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setIsSending(true);
    await sendMessage(text);
    setIsSending(false);
    setIsSent(true);
  }

  if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }

  return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}

// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

^ 위 코드는 동작하긴 하지만 '불가능한' state를 허용함

ex) setIsSent와 setIsSending을 함께 호출하는 것을 잊어버린 경우, isSending과 isSent가 동시에 true인 상황에 처할 수 있음

 

// App.js

import { useState } from 'react';

export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [status, setStatus] = useState('typing');

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('sending');
    await sendMessage(text);
    setStatus('sent');
  }

  const isSending = status === 'sending';
  const isSent = status === 'sent';

  if (isSent) {
    return <h1>Thanks for feedback!</h1>
  }

  return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button
        disabled={isSending}
        type="submit"
      >
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}

// Pretend to send a message.
function sendMessage(text) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  });
}

^ isSending과 isSent는 동시에 true가 되어서는 안되기 때문에, 이 두 변수를 'typing'(초깃값), 'sending', 'sent' 세 가지 유효한 상태 중 하나를 가질 수 있는 status state 변수로 대체

 

✔️ 불필요한 state 피하기

렌더링 중에 컴포넌트의 props나 기존 state 변수에서 일부 정보를 계산할 수 있다면, 컴포넌트의 state에 해당 정보를 넣지 않아야 함

== 계산되어서 나오는 값은 상태가 아니기 때문에 굳이 useState를 사용해서 저장하지 말고, 필요할 때 즉석에서 계산해서 사용

 

// App.js

import { useState } from 'react';

const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];

export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );

  return (
    <>
      <h2>What's your travel snack?</h2>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.title}
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}.</p>
    </>
  );
}

^ 선택된 항목을 selectedItem state 변수에 객체로 저장 --> 좋지 않음

∵ selectedItem 내용이 items 목록 내의 항목 중 하나이기 때문에 --> 항목 자체에 대한 정보가 두 곳에서 중복되는 것

 

// App.js

import { useState } from 'react';

const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];

export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(0);

  const selectedItem = items.find(item =>
    item.id === selectedId
  );

  function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          title: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }

  return (
    <>
      <h2>What's your travel snack?</h2>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.title}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedId(item.id);
            }}>Choose</button>
          </li>
        ))}
      </ul>
      <p>You picked {selectedItem.title}.</p>
    </>
  );
}

^ 중복은 사라지고 필수적인 state만 유지됨 

 

✔️ 깊게 중첩된 state 피하기 

// places.js

export const initialTravelPlan = {
  id: 0,
  title: '(Root)',
  childPlaces: [{
    id: 1,
    title: 'Earth',
    childPlaces: [{
      id: 2,
      title: 'Africa',
      childPlaces: [{
        id: 3,
        title: 'Botswana',
        childPlaces: []
      }, {
        id: 4,
        title: 'Egypt',
        childPlaces: []
      }, {
        id: 5,
        title: 'Kenya',
        childPlaces: []
      }, {
        id: 6,
        title: 'Madagascar',
        childPlaces: []
      }, {
        id: 7,
        title: 'Morocco',
        childPlaces: []
      }, {
        id: 8,
        title: 'Nigeria',
        childPlaces: []
      }, {
        id: 9,
        title: 'South Africa',
        childPlaces: []
      }]
    }, {
      id: 10,
      title: 'Americas',
      childPlaces: [{
        id: 11,
        title: 'Argentina',
        childPlaces: []
      }, {
        id: 12,
        title: 'Brazil',
        childPlaces: []
      }, {
        id: 13,
        title: 'Barbados',
        childPlaces: []
      }, {
        id: 14,
        title: 'Canada',
        childPlaces: []
      }, {
        id: 15,
        title: 'Jamaica',
        childPlaces: []
      }, {
        id: 16,
        title: 'Mexico',
        childPlaces: []
      }, {
        id: 17,
        title: 'Trinidad and Tobago',
        childPlaces: []
      }, {
        id: 18,
        title: 'Venezuela',
        childPlaces: []
      }]
    }, {
      id: 19,
      title: 'Asia',
      childPlaces: [{
        id: 20,
        title: 'China',
        childPlaces: []
      }, {
        id: 21,
        title: 'India',
        childPlaces: []
      }, {
        id: 22,
        title: 'Singapore',
        childPlaces: []
      }, {
        id: 23,
        title: 'South Korea',
        childPlaces: []
      }, {
        id: 24,
        title: 'Thailand',
        childPlaces: []
      }, {
        id: 25,
        title: 'Vietnam',
        childPlaces: []
      }]
    }, {
      id: 26,
      title: 'Europe',
      childPlaces: [{
        id: 27,
        title: 'Croatia',
        childPlaces: [],
      }, {
        id: 28,
        title: 'France',
        childPlaces: [],
      }, {
        id: 29,
        title: 'Germany',
        childPlaces: [],
      }, {
        id: 30,
        title: 'Italy',
        childPlaces: [],
      }, {
        id: 31,
        title: 'Portugal',
        childPlaces: [],
      }, {
        id: 32,
        title: 'Spain',
        childPlaces: [],
      }, {
        id: 33,
        title: 'Turkey',
        childPlaces: [],
      }]
    }, {
      id: 34,
      title: 'Oceania',
      childPlaces: [{
        id: 35,
        title: 'Australia',
        childPlaces: [],
      }, {
        id: 36,
        title: 'Bora Bora (French Polynesia)',
        childPlaces: [],
      }, {
        id: 37,
        title: 'Easter Island (Chile)',
        childPlaces: [],
      }, {
        id: 38,
        title: 'Fiji',
        childPlaces: [],
      }, {
        id: 39,
        title: 'Hawaii (the USA)',
        childPlaces: [],
      }, {
        id: 40,
        title: 'New Zealand',
        childPlaces: [],
      }, {
        id: 41,
        title: 'Vanuatu',
        childPlaces: [],
      }]
    }]
  }, {
    id: 42,
    title: 'Moon',
    childPlaces: [{
      id: 43,
      title: 'Rheita',
      childPlaces: []
    }, {
      id: 44,
      title: 'Piccolomini',
      childPlaces: []
    }, {
      id: 45,
      title: 'Tycho',
      childPlaces: []
    }]
  }, {
    id: 46,
    title: 'Mars',
    childPlaces: [{
      id: 47,
      title: 'Corn Town',
      childPlaces: []
    }, {
      id: 48,
      title: 'Green Hill',
      childPlaces: []
    }]
  }]
};

 

중첩된 state를 업데이트하는 것은 변경된 부분부터 모든 객체의 복사본을 만드는 것을 의미

깊게 중첩된 장소를 삭제하는 것은 전체 부모 장소 체인을 복사하는 것을 의미

--> 코드가 매우 장황할 수 있음

 

만약 state가 쉽게 업데이트하기에 너무 중첩되어 있다면, 평탄하게 만드는 것(정규화)을 고려

// places.js

export const initialTravelPlan = {
  0: {
    id: 0,
    title: '(Root)',
    childIds: [1, 42, 46],
  },
  1: {
    id: 1,
    title: 'Earth',
    childIds: [2, 10, 19, 26, 34]
  },
  2: {
    id: 2,
    title: 'Africa',
    childIds: [3, 4, 5, 6 , 7, 8, 9]
  },
  3: {
    id: 3,
    title: 'Botswana',
    childIds: []
  },
  4: {
    id: 4,
    title: 'Egypt',
    childIds: []
  },
  5: {
    id: 5,
    title: 'Kenya',
    childIds: []
  },
  6: {
    id: 6,
    title: 'Madagascar',
    childIds: []
  },
  7: {
    id: 7,
    title: 'Morocco',
    childIds: []
  },
  8: {
    id: 8,
    title: 'Nigeria',
    childIds: []
  },
  9: {
    id: 9,
    title: 'South Africa',
    childIds: []
  },
  10: {
    id: 10,
    title: 'Americas',
    childIds: [11, 12, 13, 14, 15, 16, 17, 18],
  },
  11: {
    id: 11,
    title: 'Argentina',
    childIds: []
  },
  12: {
    id: 12,
    title: 'Brazil',
    childIds: []
  },
  13: {
    id: 13,
    title: 'Barbados',
    childIds: []
  },
  14: {
    id: 14,
    title: 'Canada',
    childIds: []
  },
  15: {
    id: 15,
    title: 'Jamaica',
    childIds: []
  },
  16: {
    id: 16,
    title: 'Mexico',
    childIds: []
  },
  17: {
    id: 17,
    title: 'Trinidad and Tobago',
    childIds: []
  },
  18: {
    id: 18,
    title: 'Venezuela',
    childIds: []
  },
  19: {
    id: 19,
    title: 'Asia',
    childIds: [20, 21, 22, 23, 24, 25],
  },
  20: {
    id: 20,
    title: 'China',
    childIds: []
  },
  21: {
    id: 21,
    title: 'India',
    childIds: []
  },
  22: {
    id: 22,
    title: 'Singapore',
    childIds: []
  },
  23: {
    id: 23,
    title: 'South Korea',
    childIds: []
  },
  24: {
    id: 24,
    title: 'Thailand',
    childIds: []
  },
  25: {
    id: 25,
    title: 'Vietnam',
    childIds: []
  },
  26: {
    id: 26,
    title: 'Europe',
    childIds: [27, 28, 29, 30, 31, 32, 33],
  },
  27: {
    id: 27,
    title: 'Croatia',
    childIds: []
  },
  28: {
    id: 28,
    title: 'France',
    childIds: []
  },
  29: {
    id: 29,
    title: 'Germany',
    childIds: []
  },
  30: {
    id: 30,
    title: 'Italy',
    childIds: []
  },
  31: {
    id: 31,
    title: 'Portugal',
    childIds: []
  },
  32: {
    id: 32,
    title: 'Spain',
    childIds: []
  },
  33: {
    id: 33,
    title: 'Turkey',
    childIds: []
  },
  34: {
    id: 34,
    title: 'Oceania',
    childIds: [35, 36, 37, 38, 39, 40, 41],
  },
  35: {
    id: 35,
    title: 'Australia',
    childIds: []
  },
  36: {
    id: 36,
    title: 'Bora Bora (French Polynesia)',
    childIds: []
  },
  37: {
    id: 37,
    title: 'Easter Island (Chile)',
    childIds: []
  },
  38: {
    id: 38,
    title: 'Fiji',
    childIds: []
  },
  39: {
    id: 39,
    title: 'Hawaii (the USA)',
    childIds: []
  },
  40: {
    id: 40,
    title: 'New Zealand',
    childIds: []
  },
  41: {
    id: 41,
    title: 'Vanuatu',
    childIds: []
  },
  42: {
    id: 42,
    title: 'Moon',
    childIds: [43, 44, 45]
  },
  43: {
    id: 43,
    title: 'Rheita',
    childIds: []
  },
  44: {
    id: 44,
    title: 'Piccolomini',
    childIds: []
  },
  45: {
    id: 45,
    title: 'Tycho',
    childIds: []
  },
  46: {
    id: 46,
    title: 'Mars',
    childIds: [47, 48]
  },
  47: {
    id: 47,
    title: 'Corn Town',
    childIds: []
  },
  48: {
    id: 48,
    title: 'Green Hill',
    childIds: []
  }
};

^ 각 plcae가 자식 장소의 배열을 가지는 트리 구조 대신, 자식 장소 ID의 배열을 가지도록 함 

 

 

 


컴포넌트 간 State 공유하기

두 컴포넌트의 state가 항상 함께 변경되기를 원할 때
--> 각 컴포넌트에서 state를 제거하고 가장 가까운 공통 부모 컴포넌트로 옮긴 후 props로 전달해야 함
== State 끌어올리기  

 

✔️ State 끌어올리기 예시

  • Accordion 컴포넌트
    • Panel 컴포넌트
    • Panel 컴포넌트
// App.js

import { useState } from 'react';

function Panel({ title, children }) {
  const [isActive, setIsActive] = useState(false);
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          Show
        </button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About">
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology">
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

 

✔️ Step 1: 자식 컴포넌트에서 state 제거하기

Panel의 isActive에 대한 제어권을 부모 컴포넌트에게 줄 수 있음

--> 부모 컴포넌트는 isActive를 Panel에 prop으로 전달

 

const [isActive, setIsActive] = useState(false);

^ Panel 컴포넌트에서 해당 줄을 제거

 

function Panel({ title, children, isActive }) {

^ Panel의 prop 목록에 isActive를 추가

 

이제 Panel의 부모 컴포넌트는 props 내리꽂기를 통해 isActive를 제어할 수 있음

Panel 컴포넌트는 isActive를 제어할 수 없음

 

✔️ Step 2: 하드 코딩된 데이터를 부모 컴포넌트로 전달하기 

state를 올리려면, 조정하려는 두 자식 컴포넌트의 가장 가까운 공통 부모 컴포넌트에 두어야 함

 

✔️ Step 3: 공통 부모에 state 추가하기

예시에서는, 한 번에 하나의 패널만 활성화되어야 함

이를 위해 공통 부모 컴포넌트인 Accordion은 어떤 패널이 활성화된 패널인지 추적하고 있어야 함

--> state 변수에 boolean 값을 사용하는 대신, 활성화되어 있는 Panel의 인덱스 숫자를 사용할 수 있음

 

const [activeIndex, setActiveIndex] = useState(0);

^ activeIndex가 0이면 첫 번째 패널이 활성화된 것, 1이면 두 번째 패널이 활성화된 것

 

<>
  <Panel
    isActive={activeIndex === 0}
    onShow={() => setActiveIndex(0)}
  >
    ...
  </Panel>
  <Panel
    isActive={activeIndex === 1}
    onShow={() => setActiveIndex(1)}
  >
    ...
  </Panel>
</>

^ 각 Panel에서 show 버튼을 클릭하면 Accordion의 활성화된 인덱스를 변경해야 함

 

// App.js

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          Show
        </button>
      )}
    </section>
  );
}

 

✔️ 각 state의 단일 진리의 원천

상태의 위치 생존 범위 특징
리프 (하단) Local 본인만 알면 됨 (성능 최적화 유리)
중간 (Lifting Up) Shared 형제끼리 공유 (부모로 상태 끌어올리기)
상단 (Root) Global 앱 전체가 알아야 함 (유저 정보, 경로 )

 

각각의 고유한 state에 대해 어떤 컴포넌트가 소유할지 고를 수 있음

 

단일 진리의 원칙 : 모든 state가 한 곳에 존재한다는 의미가 아니라, 그 정보를 가지고 있는 특정 컴포넌트가 있다는 것을 의미

'React > 공식 문서' 카테고리의 다른 글

Context API  (0) 2026.05.27
useReducer  (0) 2026.05.21
불변 업데이트 패턴  (0) 2026.04.28
렌더링과 State 업데이트  (0) 2026.04.08
이벤트 처리와 State  (0) 2026.04.02