탭 UI 만들기

버튼 3개와 박스 3개를 미리 만들어놓고 버튼을 누를 때 마다 그에 맞는 박스를 보여주는 탭 UI 를 만들어보려고 한다.
다시 상기시켜보는 동적 UI 만드는 방법
1. html, css 디자인 미리 완성시켜 놓고
2. UI 의 현재 상태를 저장할 state 만들기
3. state 에 따라서 UI 가 어떻게 보일지 작성
- html, css 로 디자인 미리 완성하기
리액트 부트스트랩 사이트에서 아래의 코드를 복사해서 가져오고, Nav 를 import 하기
여기서 defaultActiveKey 는 페이지 로드시 어떤 버튼에 눌린 효과를 줄지 결정하는 부분으로 원하는 버튼을 넣으면 된다.
import { Nav } from 'react-bootstrap';
<Nav variant="tabs" defaultActiveKey="link0">
<Nav.Item>
<Nav.Link eventKey="link0">버튼0</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="link1">버튼1</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="link2">버튼2</Nav.Link>
</Nav.Item>
</Nav>
<div>내용0</div>
<div>내용1</div>
<div>내용2</div>
- UI 의 상태를 저장할 state 하나 만들기
탭의 값에 따라 보이는 내용이 달라지도록 만듦.
-> 0 번 내용이 보이거나 / 1 번에 내용이 보이거나 / 2 번 내용이 보이거나
function Detail(){
let [탭, 탭변경] = useState(0)
(생략)
}
- state 에 따라서 UI 가 어떻게 보일지 작성
// 삼항연산자 사용하기
function Detail(){
let [탭, 탭변경] = useState(0)
return (
{ 탭 == 0 ? <div>내용0</div> : null }
{ 탭 == 1 ? <div>내용1</div> : null }
{ 탭 == 1 ? <div>내용2</div> : null }
)
}
// 컴포넌트 사용하기
// 참고로 컴포넌트는 리턴문이 없으면 제기능을 하지 못하니 잊지말고 리턴문을 잘 써주도록 하자!
function Detail(){
let [탭, 탭변경] = useState(0)
return (
<TabContent 탭={탭}/>
)
}
function TabContent(props){
if (props.탭 === 0){
return <div>내용0</div>
}
if (props.탭 === 1){
return <div>내용1</div>
}
if (props.탭 === 2){
return <div>내용2</div>
}
}
// 더 간결하게
function Detail(){
let [탭, 탭변경] = useState(0)
return (
<TabContent 탭={탭}/>
)
}
function TabContent(props){
return [ <div>내용0</div>, <div>내용1</div>, <div>내용2</div> ][props.탭]
}
※ 참고 - props 쉽게 쓰기
자식컴포넌트에서 props 라고 파라미터를 작명하지 않고 필요한 이름으로 작명하기
function TabContent({탭}){
return [ <div>내용0</div>, <div>내용1</div>, <div>내용2</div> ][탭]
}
컴포넌트 전환 애니메이션 주기: transition
애니메이션 만들기 step
1. 애니메이션 동작 전 스타일을 담을 className 만들기
2. 애니메이션 동작 후 스타일을 담을 className 만들기
3. transition 속성도 추가
4. 원할 때 2번 탈부착
- 애니메이션 동작 전/후 스타일 담을 className 만들기
.start 는 동작 전, .end 는 동작 후
.start {
opacity : 0
}
.end {
opacity : 1;
}
- transition 추가
transition: opacity 0.5s 는 opacity 가 변경될 . 때 0.5초에 걸쳐서 변경해달라는 의미이다.
.start {
opacity : 0
}
.end {
opacity : 1;
transition: opacity 0.5s;
}
- 원할 때 end 탈부착
useEffect 를 이용해서 탭이라는 state 가 변할 때 end 를 부착하기 위해 아래와 같이 코드를 짰다. 하지만, 아래의 코드를 돌려 확인해보면 <div> 태그 안 내용의 투명도 변화가 보이지 않는다.
function TabContent({탭}){
let [fade, setFade] = useState('')
useEffect(()=>{
setFade('end')
}, [탭])
return (
<div className={'start ' + fade}>
{ [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][탭] }
</div>
)
}
end 가 없었다가 새로 생겨야 코드가 정상동작을 하기 때문에 useEffect 안 clean up function 을 이용해서 먼저 떼었다가 붙이는 코드로 다시 짜보았지만 여전히 제대로 동작하지 않는 것을 확인할 수 있었다.
function TabContent({탭}){
let [fade, setFade] = useState('')
useEffect(()=>{
setFade('end')
return ()=>{
setFade('')
}
}, [탭])
return (
<div className={'start ' + fade}>
{ [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][탭] }
</div>
)
}
이런 문제는 리액트 18버전 이상부터 생긴 automatic batch 이라는 state 를 여러 개 묶어서 처리하는 기능 때문이다.
state 변경함수들이 연달아 여러개 처리되어야 한다면, state 변경함수를 처리할 때마다 재렌더링 하는 것이 아니라 state 변경함수를 다 처리하고 마지막에 한 번만 재렌더링 된다.
- (X)
state1 변경함수 처리 -> 재렌더링
state2 변경함수 처리 -> 재렌더링
state3 변경함수 처리 -> 재렌더링
- (O)
state1 변경함수 처리
state2 변경함수 처리
state3 변경함수 처리
재렌더링
그래서 떼고 다시 붙여주는 데 약간의 시간을 둬서 automatic batching 을 막아줘야 한다.
function TabContent({탭}){
let [fade, setFade] = useState('')
useEffect(()=>{
setTImeout(()=>{ setFade('end') }, 100)
return ()=>{
setFade('')
}
}, [탭])
return (
<div className={'start ' + fade}>
{ [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][탭] }
</div>
)
}
숙제: Detail 컴포넌트 로드시 투명도가 0 에서 1 로 서서히 증가하는 애니메이션을 주려면?
function Detail(props){
let [fade, setFade] = useState('')
useEffect(()=> {
let a = setTimeout(()=> { setFade('detail_end') }, 200)
return ()=> {
clearTimeout(a)
setFade('')
}
}, [])
return (
<div className={'container start ' + fade}>
(하단 html 생략)
)
}
중첩해서 사용한 컴포넌트가 많을 때 편리한 Context API
리액트는 Single Page Application 으로 컴포넌트 간 state 공유가 어렵다. state 공유는 부모 컴포넌트 -> 자식컴포넌트 방향으로만 가능한데, 아래 그림처럼 <App> 에 있던 shoes state 를 <TabContent> 에서 사용하려면 <App> -> <Detail> -> <TabContent> 이렇게 2 단계를 거쳐서 전송해야 하기 때문에 불편함이 있다.

불편함을 해소하기 위해 Context API 를 사용해보자. Context API 를 사용하면 props 전송없이 state 공유가 가능하다.
Context API 사용하기
셋팅 1. createContext() 함수를 가져와서 만들어주기 (쉽게 말하면 state 보관함)
셋팅 2. <Context> 로 원하는 곳을 감싸기
셋팅 3. 공유를 원하는 state 를 value 안에 다 적기 value={{state1, state2 ...}}
사용 1. 만들어둔 Context 를 import 해오기
사용 2. useContext() 안에 넣기
// App.js
export let Context1 = React.createContext();
function App(){
let [재고, 재고변경] = useState([10,11,12]);
return (
<Context1.Provider value={ {재고, shoes} }>
<Detail shoes={shoes}/>
</Context1.Provider>
)
}
// Detail.js
import {useState, useEffect, useContext} from 'react';
import {Context1} from './../App.js';
function Detail(){
let {재고} = useContext(Context1)
return (
<div>{재고}</div>
)
}
Context API 단점
1. state 변경시 쓸데없는 컴포넌트까지 전부 재렌더링됨 - 성능이슈
2. useContext() 를 쓰고 있는 컴포넌트는 나중에 다른 파일에서 재사용할 때 Context 를 import 하는게 귀찮음 + 재사용시 제대로 동작하지 않을 수 있음 - 재사용 어려움
'웹 > 24-StudyWithPnP' 카테고리의 다른 글
[React+JS/리액트] 웹 스터디 9주차 (0) | 2024.07.04 |
---|---|
[React+JS/리액트] 웹 스터디 8주차 (0) | 2024.06.12 |
[React+JS/리액트] 웹 스터디 6주차 (2) | 2024.06.03 |
[React+JS/리액트] 웹 스터디 5주차 (0) | 2024.05.23 |
[React+JS/리액트] 웹 스터디 4주차 (0) | 2024.05.16 |