본문 바로가기
웹/24-StudyWithPnP

[React+JS/리액트] 웹 스터디 7주차

by 보먀 2024. 6. 5.
728x90
반응형

탭 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 하는게 귀찮음 + 재사용시 제대로 동작하지 않을 수 있음 - 재사용 어려움

728x90
반응형