HOWTO · React
React에서 확장/축소 기능의 단계별 구현
이 가이드를 읽고 사용자가 버튼을 클릭하여 콘텐츠를 확장/축소하도록 허용하는 방법을 알아보세요. 실제 사례를 단계별로 분석한 것입니다.
이 페이지의 내용
페이지에 긴 섹션이나 게시물 목록이 있다면 독자가 보고 싶은 내용을 선택할 수 있게 하세요. React에서는 버튼으로 상태를 업데이트하고, 조건부 렌더링에서 그 상태를 사용해 컨테이너나 개별 항목을 표시하거나 숨길 수 있습니다. 핵심은 서로 독립적인 표시 여부를 각각 별도로 관리하는 것입니다.
React에서 펼치기/접기 기능 구현
하나의 섹션에는 불리언 상태값을 사용하고, 항목마다 독립적으로 제어해야 한다면 항목 식별자를 키로 하는 객체를 사용하세요. 상태가 업데이트되면 React는 이에 맞는 JSX를 다시 렌더링합니다. 단일 섹션의 기본 패턴은 const [isOpen, setIsOpen] = useState(true), onClick={() => setIsOpen(open => !open)}을 사용하는 버튼, 그리고 {isOpen && <section>...</section>} 같은 조건부 콘텐츠입니다. 함수형 업데이트는 최신 값을 읽고, && 표현식은 값이 false일 때 아무것도 렌더링하지 않습니다.
다음 일반 JavaScript 모델은 식별자를 서로 독립적인 표시 여부 값에 연결하는 방법을 보여줍니다. 이 예시는 자체적으로 완결되어 있으며 React 인터페이스를 렌더링하지 않습니다.
다음 JavaScript 모델을 실행하세요. 코드와 출력은 변경되지 않으며, 출력의 레이블은 영어로 표시됩니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
const postIds = ["post1", "post2", "post3"];
const visible = Object.fromEntries(postIds.map((id) => [id, true]));
visible.post2 = !visible.post2;
console.log(postIds.map((id) => `${id}: ${visible[id]}`).join(", "));
post1: true, post2: false, post3: true
이제 앞서 살펴본 제어 기능을 하나로 연결한 React 컴포넌트를 보겠습니다. 애플리케이션 모듈에서 react로부터 * as React와 useState를 가져오세요. createElement를 사용하므로 이 컴포넌트는 일반 JavaScript 소스이면서 React 코드로도 유효합니다. 버튼에 표시되는 문구는 영어로 그대로 유지됩니다. 제공된 실행 증거는 일반 JavaScript 모델만 다루며 React 렌더링은 다루지 않으므로, 이 컴포넌트는 설명용 예시입니다. 위의 실행 가능한 JavaScript 모델과는 별개입니다.
function PostList({ posts }) {
const postIds = posts.map((post) => post.id);
const [sectionOpen, setSectionOpen] = useState(true);
const [visible, setVisible] = useState(() =>
Object.fromEntries(postIds.map((id) => [id, true]))
);
function togglePost(id) {
setVisible((current) => ({ ...current, [id]: !current[id] }));
}
function setAllVisible(value) {
setVisible(Object.fromEntries(postIds.map((id) => [id, value])));
}
return React.createElement(
"section",
null,
React.createElement(
"button",
{ onClick: () => setSectionOpen((open) => !open) },
sectionOpen ? "Hide posts" : "Show posts"
),
React.createElement(
"button",
{ onClick: () => setAllVisible(true) },
"Show All"
),
React.createElement(
"button",
{ onClick: () => setAllVisible(false) },
"Hide All"
),
sectionOpen &&
React.createElement(
"div",
null,
posts.map((post) =>
React.createElement(
"article",
{ key: post.id },
React.createElement(
"button",
{ onClick: () => togglePost(post.id) },
visible[post.id] ? "Collapse" : "Expand"
),
visible[post.id] &&
React.createElement("p", null, post.text)
)
)
)
);
}
visible 객체는 각 게시물 식별자를 해당 게시물만의 불리언 값에 연결합니다. 게시물 버튼을 클릭하면 해당 항목만 바뀌며, current를 펼쳐 복사하면 나머지 게시물의 값은 그대로 유지됩니다. sectionOpen은 부모 목록의 표시 여부를 별도로 제어합니다. 부모를 닫으면 렌더링된 자식 콘텐츠가 숨겨지지만 각 게시물의 값은 초기화되지 않으므로, 다시 열면 이전에 표시 중이던 게시물이 나타납니다. 일괄 버튼은 모든 항목의 값을 같은 불리언으로 바꿉니다. 부모가 닫힌 상태에서 Show All을 눌러도 값만 업데이트되며, 부모를 다시 열 때까지 게시물 콘텐츠는 보이지 않습니다.
React에서 전체 컨테이너 펼치기/접기
부모 섹션에는 자체 표시 상태가 있습니다. 버튼으로 이 값을 전환하고, 예를 들어 {sectionOpen && <div>{/* list */}</div>}처럼 부모 안의 목록 전체를 조건부로 렌더링하세요. 부모가 숨겨지면 부모가 보유한 자식별 표시 값이 여전히 true여도 자식은 렌더링되지 않습니다. 부모를 다시 표시하면 표시 값이 여전히 true인 항목이 렌더링됩니다.
상태가 어디에 있는지에 따라 동작이 달라집니다. 부모를 숨겨도 부모에 저장된 항목별 표시 값은 유지되지만, 조건부 렌더링으로 자식 컴포넌트의 마운트가 해제되면 그 컴포넌트 내부에만 있는 상태는 사라집니다. 위 예시에서는 표시 값이 PostList에 있으므로 섹션을 다시 열면 이전과 같은 항목이 나타납니다. 부모를 닫을 때 모든 항목을 초기화하고 싶다면 같은 이벤트 핸들러에서 표시 값도 업데이트하세요. 그렇지 않다면 부모 제어와 항목별 제어를 서로 독립적으로 유지하면 됩니다. CodeSandbox 예제는 원문에 포함된 대화형 데모입니다.
React에서 특정 게시물 펼치기/접기
각 항목의 식별자를 사용해 해당 항목의 표시 값만 조회하고 업데이트하세요. 상태를 업데이트할 때는 기존 객체를 변경하지 말고 새 객체를 만들어야 합니다. 다음 값이 이전 상태에 따라 달라지는 경우 함수형 업데이트가 유용합니다: setVisible(current => ({ ...current, [id]: !current[id] })). 계산된 속성 [id]는 버튼을 클릭한 항목의 값을 선택하고, 펼침 구문은 나머지 항목의 값을 그대로 유지합니다.
목록 안의 버튼은 해당 항목에 대해 setVisible을 호출할 수 있으며, {visible[id] && <Post post={post} />} 같은 조건식으로 그 항목의 콘텐츠를 제어할 수 있습니다. 목록이 바뀔 수 있다면 현재 식별자에서 초기 표시 상태 객체를 만들고, 데이터가 바뀔 때 새 식별자와 삭제된 식별자도 처리하세요. 키를 직접 적어 만든 고정 객체는 그 안에 명시된 항목만 다룹니다.
React에서 모든 게시물 펼치기 또는 접기
일괄 제어 버튼은 모든 항목의 값을 같은 불리언으로 설정합니다. 목록과 표시 상태가 일치하도록 식별자에서 객체를 만드세요:
const setAllVisible = (value) => setVisible(Object.fromEntries(postIds.map(id => [id, value])))
모두 숨기기에는 onClick={() => setAllVisible(false)}를, 모두 표시하기에는 onClick={() => setAllVisible(true)}를 사용하세요. 이 버튼들은 항목별 표시 값을 업데이트합니다. 부모 컨테이너가 숨겨져 있다면 모두 표시를 눌러도 자식 상태만 바뀌며, 부모를 다시 표시할 때까지 콘텐츠는 보이지 않습니다.
결론
단일 섹션에는 불리언 하나를 사용하고, 항목별 독립 제어가 필요하면 식별자와 불리언을 연결한 매핑을 사용하세요. 부모 표시 여부, 항목 표시 여부, 일괄 동작을 상태에서 분명히 구분하면 각 버튼이 의도한 부분만 변경합니다. 스타일은 버튼의 모양을 바꿀 수 있지만, 독자가 볼 수 있는 콘텐츠는 상태와 조건부 렌더링으로 결정됩니다.