React 다듬기 (상)

이준호·2023년 2월 25일
post-thumbnail

1. 프로젝트 생성 & Bootstrap다운

  1. 새로운 프로젝트를 생성해보자 npx create-react-app $작명
  2. npm start npm을 사용해서 개발할것이다. 앱을 실행 시켜보자.
  3. 페이지 디자인을 위한 bootstrap 이라는 라이브러리를 설치해보자.
    https://react-bootstrap.github.io/getting-started/introduction
  4. index.htmlhead태그 안에 link된 bootstrap 위의 정보를 넣어주자.
  5. bootStrap 에서 원하는 UI를 복사 붙여넣기 해주자
  6. 사용할 UI를 import 로 선언해주자. (태그 앞대문자 선언)
import { Navbar,Container,Nav } from 'react-bootstrap';

🤨 : 선언한 UI의 스타일 수정은 ClassName을 사용해 css변경

2. 이미지 넣기 & Public폴더

  1. 대문사진을 하나 넣어보자.div태그를 생성후 classaname 선언
  2. Img 폴더를 생성후 그안에 대문사진 이미지를 넣어주자.
  3. css로 이동 background-image: url('')주소와 css 코드 입력
  4. 상품컬럼 3개를 만들고 이미지주소를 통해 이미지를 붙이자. (링크를 참조)
    https://react-bootstrap.netlify.app/layout/grid/#rb-docs-content
  5. 이미지를 많이 쓸땐 Public에 관리하는것이 좋다 하지만 경로가 바뀌면 오류가 발생할수 있음으로 다음과 같은 방법으로 코드를 짜면된다.
    <img src={process.env.PUBLIC_URL + '/img/logo.png'}
    🤨 :html에서 이미지를 넣을땐 import 로 이름과 경로를 선언해주어야함

3. import export를 이용

상품컬럼에 대한 정보를 서버를 통해 가져왔다 가정하고 진행하겠다.
1. useState를 사용 let [art] = useState(data);
2. data.js 라는 상품컬럼 정보 에대한 파일을 만들어 export해준다.
3. 다시 App.js로 돌아와서 data.js를 import 해준다.
4. data.js 의 구조는 array안의 object구조이기 때문에 선언할떄 유의한다.
5. 즉 첫번째 상품의 제목을 알고싶으면 art[0].title 이런식으로 써야한다.

4. 컴포넌트 만들기

컴포넌트 함수를 만들어서 원래의 코드를 더 간단하게 표현하자.

  1. 기존의 App함수가 아닌 외부에 MidArt라는 컴포넌트 함수를 만들것이다.
  2. 상품컬럼 3개가 동일한 형식이기 때문에 이를 컴포넌트로 대체한다.
  3. 컴포넌트 함수안에 동일한 형식인 상품컬럼 한가지의 코드 내용을 복붙 한다.
  4. 🚨문제 발생 외부의 함수에서 state변수 사용으로 코드가 동작하지 않는다.
  5. props를 사용하여 부모함수에서 state변수에대한 정보를 가져오게 한다.
  6. props선언으로 인해 컴포넌트를 사용하는 부분에 다음과 같이 state 이름과 작명을 해주고<MidArt art={art[0]}></MidArt> 컴포넌트 함수에선 state 사용 부분에 props.art.title 이와같이 props.을 붙여준다.
  7. 6번까지가 상품컬럼[0] 에대한 컴포넌트 생성이고 나머지 두개도 진행해준다.

App함수

4-2. Maps 활용

Map을 사용해 3개의 위 반복코드를 더 간단하게 만들어줄것이다.

  1. map 함수는 반복문이며 map 앞의 변수의 크기만큼 반복한다.
  2. art.map 은 art의 배열수만큼 반복실행한다. 함수로 만들어보자.
  {art.map(() => {
    return<MidArt art={art[0]}></MidArt>;
  })} 
  1. 위와 같은 코드는art[0] 상품컬럼을 art배열의 개수인 3번만큼 반복한다.
  2. 때문에 반복문이 돌때마다 1씩 증가하는 파라미터 i를 사용하여 해결한다.
  {art.map((a,i) => {
    return<MidArt art={art[i]}></MidArt>;
  })} 

5. 라우팅

리액트는 html파일 하나만 사용하여 여러페이지를 보여준다.때문에 사용자가 다른 페이지를 요청하면 내부의 <div>들을 갈아치워서 보여주는데 react-router-dom은 이것을 간편하게 해준다.

  1. react-router-dom 설치 터미널 입력 npm install react-router-dom@6
  2. index.js세팅 import 이후 BrowserRouter로 App을 감싼다.
    import { BrowserRouter } from 'react-router-dom';
root.render(
  <React.StrictMode>
  	<BrowserRouter>
  		<App/>
  	</BrowserRouter>
  </React.StrictMode>
  );
  1. 이제 메인페이지와 어바웃페이지 2개를 만들어보자.
  2. 먼저 상단에서 여러가지 컴포넌트를 import 해 Routes안에 Route를 작성
  3. 그리고 path엔 경로 element엔 보여줄 html을 작성하면된다.
import {Routes,Route,Link} om 'react-router-dom'
function App(){
 return( (생략)
<Routes>
	<Route path="/" element={<div>메인</div> }/>
	<Route path="/detail" element={<div>상세</div> }/>
</Routes>
)
}
  1. 이제 nav를 제외한 기존의 view들을 element메인자리 에 넣어주자.
  2. detail엔 Detail.js 파일에 컴포넌트 함수를 만들어 element에 넣어주자.
  3. 이제 <Link를 이용해서 메인과 상세 페이지를 이동할수있도록 하면 끝
<Link to="/"></Link>
<Link to="/Detail">상세페이지</Link>

😃😃😃
path경로에 '/' 를 경로로 지정하면 초기 url의 경로가 나오며 만약
path경로에'*' 를 경로로 지정하면 앞서 선언한 라우터들을 제외한 나머지 경로에 대한 페이지를 만들수있다 예를들어 404페이지 같은 에러페이지..

<Route path="*" element={<div>없는페이지임</div>} />

6. Navigat와 routes의 기능들

Link태그 보다 페이지 더깔끔하게 페이지 이동이가능한 방법을 알아보고 비슷한 여러페이지의 생성과 분류하는 방법을 알아보자.

  1. 새로운 기능 useNavigate 와 Outlet 을 import 하는것을 잊지말자
    import{ Routes, Route, Link,useNavigate,Outlet} from 'react-router-dom'
  2. Link태그와 같은 기능을 하지만 더 깔끔하게 함수를 사용가능한useNavigate 를 만들어보자. 먼저 변수를 만들어 선언해주고 사용되는 태그의 event함수navigate 선언과 경로를 지정해준다.navigate 안에 -1을 넢으면 뒤로가기 1을 넣으면 앞으로 가는등 여러가지 기능 활용이 가능하다.
    function App(){
     let navigate = useNavigate();
     return( 
      ------------(생략)
    	<button onClick={()=>{navigate('/Detail')}>상세페이지</button>
     	<button onClick={()=>{navigate(-1)}>뒤로가기</button>)	;}
  3. 서브경로를 만들수 있는 nested Routes를 사용해보자.
    회사정보 라는 페이지 안에 회사멤버,회사위치라는 두개의 페이지를 만들자
    <Route path='/about" element{<About/>} >
     <Route path='member' element{<div>멤버들</div>}/>
     <Route path='location' element{<div>위치</div>}/>
    </Route>
  4. 이렇게 생성하면 이제 'about'이란 경로 하위에 'member','location'
    이 생성되고 about 페이지를 공통으로 보여주며 member나 location의 페이지또한 보여줄수있다.이렇듯 Route 안에 Route를 넣는 방식을 nested
    Route 라고 하며 상위 페이지를 고정시키고 하위 박스들만 변경할때 사용된다.
  5. 하지만 여기서 실제로 about/member위치로 가보면 멤버들 이라는 div가 안보이는데 이는 상위 경로에서 하위 route를 보여주는 위치를 지정하지 않았기 때문이다.때문에 about 컴포넌트로 돌아가Outlet이라는 태그를 선언해준다.
    funciton About() {
     return(
     <div>
     <h4>어바웃 페이지</h4>
     <Outlet></Outlet>
     </div> )
    }

7. URL 파라미터를 통한 페이지제작

여러 Routes를 만드는 방법을배우고 Detail 이란 상세 페이지를 만들었으니 Props로 state를 넘겨 하위 텍스트 들을 채워보자.

  1. props를 전송하는 방법은 동일하다 사용하는 컴포넌트의 위치에 아래처럼 작명
<Route path="/detail" element={ <Detail shoes={shoes}/> }/>
  1. 이후 Detail.js 로 돌아가 아래 코드처럼 사용해주면 props를 사용가능하다.
<h4 className="pt-5">{props.shoes[0].title}</h4>
      <p>{props.shoes[0].content}</p>
      <p>{props.shoes[0].price}원</p>
  1. 그럼 페이지를 여러개 만들면 어떻게 해야함? 아래처럼 무식한 방법으로 만듬??
<Route path="/detail/0" element={ <Detail shoes={shoes}/> }/>
<Route path="/detail/1" element={ <Detail shoes={shoes}/> }/>
<Route path="/detail/2" element={ <Detail shoes={shoes}/> }/> 
  1. 저렇게 하지않기 위해 URL파라미터라는 문법을 사용 아래와 같은방법으로 path 에 /:id를 입력하면 말그대로 사용자가 url로 입력한 detail/다음의 parm을 가져와 보여준다는 뜻
<Route path="/detail/:id" element={ <Detail shoes={shoes}/> }/>
  1. 근데 여기서 문제발생 2번 처럼 shoes[0]의 자료만 하드코딩 해왔기때문에
    3번출력하면 결국 3개의 같은 페이지만 생성된다. 어떻게 해결?
  2. useParam 이라는 훅을 사용하면 url파라미터에 입력된 숫자를 가져올수있음
import { useParams } from 'react-router-dom';
(생략)
  let {id} = useParams();
return(
(생략)
<h4 className="pt-5">{props.shoes[id].title}</h4>
  1. 문제!! 근데 만약 상품의 순서가 바뀌면 어떻게 상세페이지를 보여줌???
    1) 먼저 data.js에 상품명,타이틀 등과함께 구분지을수있는 id 값이 존재해야함
    2) 이후 자바스크립트의 find()문법을 사용하여 배열의 오브젝트값인 id 를색출
    3) array자료.find(()=>{ return 조건식 }) 이렇게 쓰면 조건식에 맞는 자료를 찾아서 이 자리에 남겨줌
let { id } = useParams();
  let 찾은상품 = props.shoes.find(function(x){
    return x.id == id		
    // let 찾은상품 = props.art.find((x) => x.id == id);
  });
  return(
  (생략)
  <h4>{찾은상품.title}</h4>
  <p>{찾은상품.content}</p>
  <p>{찾은상품.price}</p>
  1. find()는 array 뒤에 붙일 수 있으며 return 조건식 적으면 됨 그럼 조건식에 맞는 자료 남겨줌
  2. find() 콜백함수에 파라미터 넣으면 array자료에 있던 자료를 뜻함. x라고 작명해봤음
  3. x.id == id 라는 조건식을 써봄. 그럼 array자료.id == url에입력한번호 일 경우 결과를 변수에 담아줌

8. styled-components

styled-components 는 css 사용없이 js파일 내에서 스타일을 변경하기 쉽기해주는 라이브러리이다. 따라서 사용하고 말고는 사용자 마음이며 간단한 개인 프로젝트에 사용하는걸 추천한다.

  1. 터미널 에서 npm install styled-components
  2. 사용할 컴포넌트 에서 import styled from 'styled-components'
  3. 사용법 상단에서 변수와 변수명을 생성후 styled.태그명을 붙여준다.
  4. 이후 ` 백틱을 사용하여 열고닫아 내부에서 css처럼 스타일을 변형시켜준다.
let Box = styled.div`
  padding : 20px;
  color : grey
`;
  1. 이제 사용을 원하는 부분에 변수명을 컴포넌트로 선언해준다. <Box></Box>
  2. Props를 사용하여 비슷한 컴포넌트의 색깔 스타일만 바꿀수도있다.
import styled from 'styled-components';
let YellowBtn = styled.button`
  background : ${ props => props.bg };  // 이부분
  color : black;
  padding : 10px;
`;
function Detail(){
  return (
    <div>
        <YellowBtn bg="orange">오렌지색 버튼임</YellowBtn>
        <YellowBtn bg="blue">파란색 버튼임</YellowBtn>
    </div>
  )
}

Q. 저거 ${ } 이거 무슨 문법임?
A. 자바스크립트 `` 백틱 따옴표 안에 적어도 문자를 만들 수 있는데
백틱으로 만든 문자 중간에 변수같은걸 넣고 싶을 때 ${ 변수명 } 이렇게 쓸 수 있습니다.

장점

  1. CSS 파일 오픈할 필요없이 JS 파일에서 바로 스타일넣을 수 있습니다.
  2. 여기 적은 스타일이 다른 JS 파일로 오염되지 않습니다.
  3. 페이지 로딩시간 단축됩니다.

단점

  1. JS 파일이 매우 복잡해집니다.
  2. 컴포넌트가 styled 인지 아니면 일반 컴포넌트인지 구분도 어렵습니다.
  3. CSS 담당하는 디자이너가 있다면 협업시 styled-components 문법을 모른다면 그 사람이 CSS로 짠걸 styled-components 문법으로 다시 바꾸거나 그런 작업이 필요함

9. 생애주기와 useEffect

컴포넌트는 크게 3가지의 생애주기(LifeCycle) 이 존재한다. 이를 알아야하는 이유는 생애주기 사이사이에서 간섭하여 이벤트를 발생시킬수있다는 뜻이다.

1.생성이 될수있다. (Mount)
2.재렌더링이 될수있다. (Updata)
3.삭제가 될수있다. (Unmount)
크게 3개의 생애주기 사이에서 hook 으로 코드실행을 개입시키는것을
Lifecylce hook이라고 한다 ex) A컴포넌트가 실행될때 Go 라는 hook코드를 실행

  1. react에선 이러한 Lifecyclehook을 사용하기위해 useEffect를 사용한다.
  2. 사용법
    import {useState, useEffect} from 'react';
     function Detail(){
     useEffect(()=>{
       //여기적은 코드는 컴포넌트 로드 & 업데이트 마다 실행됨
       console.log('안녕')
     });
     let [count, setCount] = useState(0)
     return (
       <button onClick={()=>{ setCount(count+1) }}>버튼</button>
     )
    }
  3. useEffect 안에 있는 consolelog 안녕은 랜더 될때마다 실행된다 때문에
    button의 onClick 이벤트로 인한 count state 변화에 의해 안녕이 계속 출력됨
  4. 근데 useEffect외부에 console.log를 하여도 똑같이 동작한다 다만 다른점은 내부에서 동작시 html이 먼저 동작하고 이후 내부코드가 돌아가기 때문에 시간절약이 가능하다 때문에오래걸리는 반복연산, 서버에서 데이터가져오는 작업, 타이머다는거 이런건 useEffect 안에 많이 적는다.

9-1. 생애주기와 useEffect2

useEffect에 대한 이해를 위해 2초동안 보여주는 레이아웃을 만들어보고 실행조건과 렌더링 에대한 이해를 해보자

  1. 먼저 두가지 상태에 따라 바뀌는 UI 제작을 위해 State만들어주자.
  2. State 값이 true 일때만<div>의 내용을 볼수있게 삼항연산자를 사용해주자.
  3. useEffect를 사용하여 내부에 setTimout이라는 함수를 넣어주자
function Detail(){

let [alert, setAlert] = useState(true)
useEffect(()=>{
  setTimeout(()=>{ setAlert(false) }, 2000)
}, [])

return (
{
  alert == true
  ? <div className="alert alert-warning">
      2초이내 구매시 할인
    </div>
  : null
} ) }

여기서 보면 잘 이해가지 않는 부분이있다. 바로 useEffect 의 []부분
useEffect()의 둘째 파라미터로 [ ] 를 넣을 수 있는데 거기에 변수나 state같은 것들을 넣을 수 있다.그렇게 하면 [ ]에 있는 변수나 state 가 변할 때만 useEffect 안의 코드를 다음과 같이 실행한다.
useEffect(()=>{ 실행할코드 }, [count])
만약 [] 값에아무것도 안넣으면 컴포넌트 mount시 (로드시)
1회 실행하고 영영 실행해주지 않는다.useEffect(()=>{ 실행할코드 }, [])

Clean up function

useEffect 동작하기 전에 특정코드를 실행하고 싶으면 return ()=>{} 안에 넣을 수 있다. clean up function 이라고 부른다. 예를들어 setTimeout() 쓸 때마다 브라우저 안에 타이머가 하나 생깁니다.
근데 useEffect 안에 썼기 때문에 컴포넌트가 mount 될 때 마다 실행되고
잘못 코드를 짜면 타이머가 100개 1000개 생길 수도 있다.나중에 그런 버그를 방지하고 싶으면useEffect에서 타이머 만들기 전에 기존 타이머를 싹 제거하라고 코드를 짜면 되는데 그런거 짤 때 return ()=>{} 안에 짜면 된다.

useEffect(()=>{ 
  let a = setTimeout(()=>{ setAlert(false) }, 2000)
  return ()=>{
    clearTimeout(a)
  }
}, [])

useEffect를 사용해서 숫자만 입력가능한 input을 만들어보자

function Detail(){
  let [num, setNum] = useState('')
  useEffect(()=>{
    if (isNaN(num) == true){
      alert('그러지마세요')
    }
  }, [num])
  return (
    <input onChange((e)=>{ setNum(e.target.value) }) />
  )
}

isNaN 은 String 이 들어가면 true 를 int 가들어가면 false를 뱉는 함수이다.
이를 통해 input 의 target.value에 문자가 들어가면 if 문이 동작한다.

10. 서버와의 ajax 통신

서버란? 유저가 데이터를 요청하면 데이터를 보내주는 프로그램이다.서버에 데이터를 요청할 때는 정확한 규격에 맞춰서 요청해야하는데 아래와 같은 사항을 잘 기재해야 한다

  1. 어떤 데이터인지 (URL 형식으로)
  2. 어떤 방법으로 요청할지 (GET or POST)
    출처:코딩애플

GET,POST 요청을 그냥 보내게 되면 브라우저가 새로고침 된다 그렇기 때문에 우리는 Ajax 라는 브라우저 기능을 사용한다. Ajax를 사용하면 새로고침 없이 서버로 정보를 보내거나 가져오는 기능을 만들수있다. Ajax로 GET/POST를 요청하는 방법은 여러개가 있는데 그중 axios같은 외부 라이브러리를 이용하는 방법이 가장편하다.

npm install axios

  1. axios 사용할 위해 상단에서 import 해주자
  2. axios.get(URL) 이러한 방식으로 요청이 가능하다.
  3. 데이터 가져온 결과는 then이후의 지정되는 result.data안에 들어있습니다.
  4. 여러사항으로 실패했을 때 실행할 코드는 .catch() 안에 적는다.
import axios from 'axios'

function App(){
  return (
    <button onClick={()=>{
      axios.get('https://codingapple1.github.io/shop/data2.json')
      .then((result)=>{
        console.log(result.data)
      })
      .catch(()=>{
        console.log('실패함')
      })
    }}>버튼</button>
  )
}

응용 문제

버튼을 누르면 서버에서 상품데이터 3개를 가져와서 메인페이지에 상품카드 3개를 더 생성해보자.

  1. 서버에서 가져온 result.data를 확인하면 배열의 형태로 들어와있다.
  2. 이는 기존의 data.js에 있는 상품카드의 데이터 정보와 같은 형태이다.
  3. 그렇기때문에 기존의 상품카드 state에 새로운 result.data를 추가해준다.
//(생략) 바로 위의 코드와 같음
      .then((result)=>{
  	 	let copy = [...art, ...result.data];
  		setArt(copy);
        console.log(art);
      })
//(생략) 바로 위의 코드와 같음

만약 버튼을 2회 누를때 7,8,9번 상품도 가져오려면?

  1. 버튼의 입력횟수를 카운트 할 state를 하나 생성해야한다.
  2. 삼항연산자를 사용하여 버튼입력횟수 마다 각각 다른 데이터 정보를 가져온다.
  3. 세번이상 버튼을 누르면 알림 을 띄워 메시지를 보낸다.
 btn < 1
	? axios.get('https://codingapple1.github.io/shop/data2.json')
 	 	   .then((result) => {setBtn(btn + 1);
                   // (생략 위의 코드와 같음)
 :
 btn < 2
    ? axios.get('https://codingapple1.github.io/shop/data3.json')
   		   .then((result) => {setBtn(btn + 1);
                   // (생략 위의 코드와 같음)
 : alert('추가할 상품이없습니다).                                                        

10-1. 서버와의 ajax 통신 2

ajax의 다른 요청방법들과 json에 대해 간략하게 알아보자

1. post 요청방법?

axios.post('URL', {name : 'kim'})

이거 실행하면 서버로 { name : 'kim' } 자료가 전송된다.
완료시 특정 코드를 실행하고 싶으면 이것도 역시 .then() 뒤에 붙이면 된다.

2.동시에 AJAX 여러 요청

Promise.all( [axios.get('URL1'), axios.get('URL2')] )

이러면 URL1, URL2로 GET요청을 동시에 해준다.
둘 다 완료시 특정 코드를 실행하고 싶으면 .then() 뒤에 붙이면 된다.

3. 배열을 원래는 못가져온다?

object/array 이런거 못주고받는다.
근데 방금만해도 array 자료 받아온 것 같은데 그건 어떻게 한거냐면
object/array 자료에 따옴표를 쳐놓으면 된다.
"{"name" : "kim"}" 이걸 JSON 이라고 한다.
JSON은 문자 취급을 받기 때문에 서버와 자유롭게 주고받을 수 있다.
그래서 실제로 결과.data 출력해보면 따옴표쳐진 JSON이 나와야하는데
axios 라이브러리는 JSON -> object/array 변환작업을 자동으로 해줘서
출력해보면 object/array가 나온다.

4. 자주하는 실수

  1. ajax요청으로 데이터를 가져와서
  2. state에 저장하라고 코드를 짜놨고
  3. state를 html에 넣어서 보여달라고 <div> {state.어쩌구} </div>
    이렇게 코드 짰는데.잘 될 것 같은데 이 상황에서 state가 텅 비어있다고 에러가 나는 경우가 많다.
    이유는 ajax 요청보다 html 렌더링이 더 빨라서 그럴 수 있다.
    state안에 뭐가 들어있으면 보여달라고 if문 같은걸 추가하거나 그러면 된다.

11. 리액트에서 탭 UI만들기

여러 사이트에서 흔히 볼수있는 탭 UI를 만들어보자 버튼 3개를 미리 만들고 버튼을 누를 때마다 그에맞는 박스를 보여주는 탭 UI가 필요하다

간단한 작업이라도 3Step 을 통해 천천히 진행하자
1. html css로 디자인 미리 완성해놓고
2. UI의 현재 상태를 저장할 state 하나 만들고
3. state에 따라서 UI가 어떻게 보일지 작성하면 된다고 했다.

1)디자인은 react-bootStrap 의 nav 를 검색하여 참고했다

<Nav variant="tabs"  defaultActiveKey="link0">
    <Nav.Item>   //defaultActiveKey 여기는 페이지 로드시 어떤 버튼에 눌린효과를 줄지 결정하는 부분입니다. 
      <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> 

2) UI의 현재 상태를 저장할 state 하나 만들기
0,1,2 3가지의 상태가 필요하기 때문에 초기값은 0으로 선언

function Detail(){
  let [탭, 탭변경] = useState(0)
  (생략)
}

3) state에 따라서 UI가 어떻게 보일지 작성
삼항연산자,컴포넌트,배열 어떤 방식으로 하든지 자유다. 여러 방식대로 한번 짜보자
3-1) function외부에 컴포넌트로 만들기
props.tab 으로 상속 받지않아도{탭} 으로 바로 state를 가져올수있다.
그리고 외부에 컴포넌트로 사용하면return을 사용해야한다.

function Detail(){
  let [, 탭변경] = useState(0)
  return (
    <TabContent/>
  )
}
function TabContent({}){
  if (=== 0){
   return <div>내용0</div>
  }
  if (=== 1){
   return <div>내용1</div>
  }
  if (=== 2){
   return <div>내용2</div>
  }
}

3-2) array 를 사용해서 if 문없이 만들어보자
아래와 같은 형식은 [A배열] [B배열]=상수 형태임으로 상수 배열 B를 통해
A배열의 위치를 특정해 가져올수있다.

function TabContent(props){
  return [ <div>내용0</div>, <div>내용1</div>, <div>내용2</div> ]
          [props.]
}

12. 컴포넌트 전환 애니메이션 주는 법 (transition)

컴포넌트를 사용할때 애니메이션을 통해 좀더 매끄러운 표현이 가능하다 이는 누군가 만들어놓은 라이브러리를 이용하여도 괜찮지만 그래도 간단한 애니메이션은 css 를 통하여 대체 할수있다.

애니메이션을 만들기위한 step
애니메이션 만들고 싶으면
1. 애니메이션 동작 전 스타일을 담을 className 만들기
2. 애니메이션 동작 후 스타일을 담을 className 만들기
3. transition 속성도 추가
4. 원할 때 2번 탈부착
탭의 내용이 서서히 등장하는 fade in 애니메이션을 만들어보자

1) 애니메이션 동작 전 2. 애니메이션 동작 후 className 만들기

.start {
  opacity : 0
}
.end {
  opacity : 1;
}

3) transition 추가
transition은 "해당 속성이 변할 때 서서히 변경해주셈~" 이라는 뜻이다. 그럼 이제 원하는 <div> 요소에 start 넣어두고 end 를 탈부착할 때 마다 fade in이 된다.

.start {
  opacity : 0
}
.end {
  opacity : 1;
  transition : opacity 0.5s;
}

4) 원할 때 end 부착
동작원리를 알아보면 start가 실행되고 end가 나중에 뒤에 붙어 순간적으로
className='start end'가 되었을때 opacity 투명도가 fade in되는 것이다.
이제 "버튼을 누를 때 className에 end를 부착해주세요" 라고 코드짜면 애니메이션 동작한다.useEffect 를통해 활용할수 있을거 같다.
useEffect 쓰면 특정 state 아니면 props가 변할 때 마다 코드실행이 가능하다,
그래서 "탭이라는 state가 변할 때 end를 저기 부착해주세요" 라고 코드를 짜면 좋을듯

function TabContent({}){

  let [fade, setFade] = useState('')

  useEffect(()=>{
    setFade('end')
  }, [])

  return (
    <div className={'start ' + fade}>
      { [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][] }
    </div>
  )
}

5) 첫번째 동작에러
위의 코드가 동작하지 않는 이유는 start -> start end의 과정처럼 떼었다 붙여져야 하는데 바로 붙은 상태가 되버리기 때문이다. 이를 위해 우리는 setFade('') 를 작성
해주어야한다 이때 이전에 배운 cleanUp코드를 활용하면 아주좋다.

 return () => {
            setFade('');

6) 두번째 동작에러
clean up 으로 end 를 떼어줘도 동작하지 않는다? 이는 react 의
automatic batch 라는 기능때문인데 쉽게 말해 state의 변경 함수가 연달아 여러개 처리 될때 부하를 막기위해 계속 렌더링 하는것이 아닌 모두 처리된 이후의 마지막 state 상태만 렌더링 하는 기능이다. 이때문에 우리는 state에 약간의 딜레이를 부여해 렌더링이 겹치는걸 방지해야 한다. 저번에 사용했던 TimeOut 함수를 써보자.

function TabContent({}){

 let [fade, setFade] = useState('')

 useEffect(()=>{
   let a = setTImeout(()=>{ setFade('end') }, 100)
 return ()=>{
   setFade('')
   clearTimeout(a);
 }
 }, [])

 return (
   <div className={'start ' + fade}>
     { [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][] }
   </div>
 )
}

13. Redux 1 : Redux Toolkit 설치 (장바구니 페이지 만들기)

장바구니 페이지를 만들어보고 Props 를통해 state를 이동하는것이 아닌 Redux라는 라이브러리를 사용해 state 의 사용을 편하게 해보자.

  1. 새로운 페이지를 만들기위해 먼저 Routenav에 추가해주자.
    <Route path="/cart" element={ <Cart/> } />
  2. Route에 담기에 페이지가 크기 때문에 컴포넌트로 분류하고 import 해주자.
  3. 편하게 제작하기위해 bootStrap에서 Table에 대한 양식을 가져와주자.
  4. <thead>태그로 가로를 th 태그로 컬럼을 나누고 td태그로 속성을 구분
    <Table>
     <thead>
       <tr>
         <th>#</th>
         <th>상품명</th>
         <th>수량</th>
         <th>변경하기</th>
       </tr>
     </thead>
     <tbody>
       <tr>
         <td>1</td>
         <td>안녕</td>
         <td>안녕</td>
         <td>안녕</td>
       </tr>
     </tbody>
    </Table> 
  5. 장바구니 기능을 사용하려면 상단의 App.js에서 State를 가져와야하는데 이를 편하게 하기위해 Redux를 배워볼것이다.

Redux란?

Redux는 props 없이 state를 공유할 수 있게 도와주는 라이브러리이다.
js 파일 하나에 여러 state들을 보관할수있어 모든 컴포넌트가 직접 꺼내쓸수있다.
npm install @reduxjs/toolkit react-redux redux 를 설치해주자.
근데 설치하기 전에 package.json 파일을 열어 react``react-dom 항목의 버전을 확인하자. 두개가 18.1.x 이상이면 사용가능하다,.

Redux 세팅

import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
  reducer: { }
}) 

아무데나 store.js 파일을 만들어서 위 코드를 넣어주자 아까 말했던 state들을 보관하는 파일이다..

import { Provider } from "react-redux";
import store from './store.js'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
); 

index.js 파일가서 Provider 라는 컴포넌트와 아까 작성한 파일을 import 해옵니다. 그리고 밑에 이걸로 을 감싸면 된다.이제 과 그 모든 자식컴포넌트들은
store.js에 있던 state를 맘대로 꺼내쓸 수 있다.

14. Redux 2 : store에 state 보관하고 쓰는 법

본격적으로 Redux를 사용하기전에 우리는 왜 State를 사용한 props 들을 왜 쓴지 부터 알아야한다. 20~50개의 컴포넌트를 사용하는 중대형 프로젝트의 경우 Redux사용을 통해 복잡한 코드의 양을 줄일수있지만

몇개안되는 컴포넌트의 사용과 한 파일구조내에서 이동하는 State들을 전부 Redux로 표현한다면 오히려 코드양이 많아지기때문에 적절하게 타협을 보면서 두가지 방법을 사용해야한다.

Redux store에 State를 보관해보자.

  1. store.js 를열어 createSlice()를 먼저 import 해준다.
  2. createSlice 에 { name : 'state이름', initialState : 'state값' }
  3. 2번과 같이 선언해주면 state 하나가 생성가능하게된다.
  4. configureStore() 라는 변수도 import 해준다.
  5. configureStore() 안에 {작명 : createSlice만든거.reducer}선언
import { configureStore, createSlice } from '@reduxjs/toolkit'

let user = createSlice({
name : 'user',
initialState : 'kim'
})

export default configureStore({
reducer: {
  user : user.reducer
}
}) 

Redux store에 있던 state를 가져다 써보자

아무 컴포넌트에서 useSelector((state) => { return state } ) 쓰면 store에 있던 모든 state가 그 자리에 남는다.

  (Cart.js)
  
import { useSelector } from "react-redux"

function Cart(){
  let a = useSelector((state) => { return state } )
  console.log(a)

  return (생략)
}

응용문제

아래와 같은 오브젝트형 데이터를 Store에 담으려면 똑같이 name을 설정하고
initialState선언에 오브젝트 그대로 넣어주면 state로 선언이 가능하고
컴포넌트에서 사용시 {a.name[0]?.id} 이러한 형식으로 배열을 불러서 사용

[
{id : 0, name : 'White and Black', count : 2},
{id : 2, name : 'Grey Yordan', count : 1}
] 

15. Redux 3 : store의 state 변경하는 법

1.store.js에 state변경해주는 함수를 만들고
2.export 한다.
3. 필요할때 import 해서 사용하면 되는데 dispatch()로 함수를 감싸서 사용해야한다.

1. store.js 안에 state를 수정해주는 함수를 생성한다.

let user = createSlice({
name : 'user',
initialState : 'kim',
reducers : {
  changeName(state){
    return 'john ' + state
  }
}
}) 

slice 안에 reducers :{} 를 열고 그안에 함수를 생성하면 된다.
함수 작명은 원하는대로 가능하며 파라미터 하나를 작명해 기존state를 사용할수있다 return 우측에 새로운 state를 입력하면
그 값으로 기존 state를 갈아치워준다.

2. 다른곳에서 쓰기좋게 export 해준다.

export let { changeName } = user.actions
이런 코드를 store.js 밑에 추가하면 된다. slice이름.actions
라고 적으면 state 변경함수가 전부 그자리에 출력된다.
그걸 변수에 저장했다가 export 하라는 코드이다.

3. import 해서 사용한다. 근데 dispatch()로 감싸서

(Cart.js)
import { useDispatch, useSelector } from "react-redux"
import { changeName } from "./../store.js"
(생략) 
<button onClick={()=>{
dispatch(changeName())
}}>버튼임</button> 

Cart.js 에서 버튼을 만들어 ChangeName 함수를 실행하는 코드이다.
store.js 에서 원하는 state변경함수를 가져오고
useDispatch라는 것도 라이브러리에서 가져온다 그리고 dispatch(state변경함수()) 이런식으로 감싸서 실행하면 state가 변경된다.

왜이런식으로 할까??


컴포넌트 100개에서 직접 'kim' 이라는 state 변경하다가 갑자기 'kim'이 123이 되어버리는 버그가 발생할수있다.범인 찾으려고 컴포넌트 100개를 다 뒤져야하는데 만약 state 수정함수를 store.js에 미리 만들어두고
컴포넌트는 그거 실행해달라고 부탁만 하는 식으로 코드를 짜놓으면 'kim'이 123이 되어버리는 버그가 발생했을 때 범인찾기가 수월할것이다.
범인은 무조건 store.js에 있으니까. (수정함수를 잘 만들어놨다면)

16. Redux 4 : state가 object/array일 경우 변경하는 법

store에 저장된 state가 array,object 자료인 경우 state 변경을 편리하게 할수있다

redux state가 array/object 인 경우 변경하려면?

  1. {name:'kim',age:20} 이렇게 생긴 자료를 state 로 만들어보자
  2. kim이라는 내용을 park으로 변경하는 함수를 만들고싶으면 state변경함수는 아래처럼 만든다.
    let user = createSlice({
    name : 'user',
    initialState : {name : 'kim', age : 20},
    reducers : {
      changeName(state){
        return {name : 'park', age : 20}
      }
    }
    }) 
    이러한 코드를 사용하면 changeName()함수 사용시 당연히변경된다
    그러면 아래처럼 state를 직접 수정하게 하면 어떻게 될까
    let user = createSlce({
    (생략)
    reducers : {
      changeName(state){
        state.name = 'park'
      }
    }
    }) 
    이러한 코드를 사용해도 immer.js라이브러리 를 통해 잘 동작한다.
    결론은 array/object 자료의 경우 state변경은 직접 수정해도 되니까
    직접 수정하자 때문에 state를 만들때 문자나 숫자하나만 필요해도 redux에선 일부러 object 아니면 array에 담는 경우가 있는데 이는 수정이 편리해서이다.

응용 문제 1

사이트에 버튼을 만들어 클릭시 age 항목이 +1 되게 만들어보자

let user = createSlice({
name : 'user',
initialState : {name : 'kim', age : 20},
reducers : {
  increase(state){
    state.age += 1
  }
}
})

이렇게 increase라는 함수 만들어 export 하고
필요한 곳에서 import 해서 dispatch(increase()) 하면 +1 된다.

응용문제2

그러면 변경함수가 여러개 필요하면 여러 함수를 전부 redux에 넣음??
동작은 같으나 횟수나 인자가 다를경우 우리는 파라미터 문법을 사용할수있다.
아래처럼 코드를 사용하면 action이라는 파라미터를 주어 함수를 사용하는 컴포넌트에서 값을 설정해 원하는 만큼 함수의 실행을 도울수있다.
이때 꼭 파라미터 뒤에는 .payload를 사용해주자

let user = createSlice({
name : 'user',
initialState : {name : 'kim', age : 20},
reducers : {
  increase(state, a){
    state.age += a.payload
  }
}
}) 

파라미터를 사용해주면 increase(10)하면 10을 더하고
increase(100하면 100을 더할것이다.

17. Redux 5 : 장바구니 기능 만들기 & 응용문제

여러 응용문제를 통해 장바구니 기능을 활성화 시켜보자

응용문제1

장바구니 오브젝트에서 수량+1 기능을 만들어보자

let cart = createSlice({
name : 'cart',
initialState : [
  {id : 0, name : 'White and Black', count : 2},
  {id : 2, name : 'Grey Yordan', count : 1}
],
reducers : {
  addCount(state, action){
    state[action.payload].count++
  }
}
}) 

addCount 라는 함수를 만들어 파라미터로 action을 주어 addCount(0)하면 0번째 상품이 +1 addCount(1)하면 1번째 상품이 +1 된다. export해서
필요할때 사용하면 될거같고 장바구니 컴포넌트 에선 대충 이렇게 쓰면 될거같다.

<button onClick={()=>{ dispatch(addCount(i)) }}>+</button>

어짜피 map을 통해 반복 되니까 파라미터 i를 써서 열마다 새로운 i값을 받을수있다. 0번째 버튼을 누르면 addCount(0) 실행 이런식으로

응용문제 1(심화)

그런데 만약 테이블의 정렬 순서가 바뀌면??
저러한 방식으로 코드를 사용하면 오브젝트안의 데이터 순서가 바뀔경우
버그가 발생할것이다. 그렇기때문에 우리는 데이터마다 가진
고유의 id 값을 확인하여 count++ 를 해줄것이다.
먼저 장바구니 컴포넌트의 코드를 조금 수정해주자.

<button
 onClick={()=>{ dispatch(addCount(state.cart[i].id)) }}>
+</button>

이런식으로 코드를 짜면 dispatch안의 addCount 내용은 state.cart[i]에 대한 id 값 즉 사용자가 선택한 테이블의 id 값을 가지고있을것이고 그 값은 action.payload 로 전송됨.

이제 addCount 함수를 수정해보자

"payload와 같은 id를 가진 상품을 찾아서 +1 해달라~" 이 말을
js로 풀어서 써넣어야한다. state 오브젝트 안에서 payload 와 같은 id 를 찾아서 그 값을 가진 테이블의 count 1증가해달라 해야한다 그럼 코드를 보자

let cart = createSlice({
name : 'cart',
initialState : [
  {id : 0, name : 'White and Black', count : 2},
  {id : 2, name : 'Grey Yordan', count : 1}
],
reducers : {
  addCount(state, action){
    let 번호 = state.findIndex((a)=>{ return a.id === action.payload })
    state[번호].count++
  }
}
}) 

array 자료에서 원하는 항목을 찾으려면 반복문, find(), findIndex() 사용!!!!

  • findIndex()는 array 뒤에 붙일 수 있다.
  • 안에 콜백함수넣고 return 뒤에 조건식도 넣으면 된다.
  • a라는 파라미터는 array 안에 있던 하나하나의 자료다.
  • array에 있던 자료를 다 꺼내서 조건식에 대입해보는데
    조건식이 참이면 그게 몇번째 자료인지 알려준다.

그래서 위의 코드는 a.idpayload가 같으면 그게 몇번째 자료인지
변수에 저장하라는 소리다.

응용문제 2

주문버튼누르면 state에 새로운 상품추가

상세페이지의 주문하기 버튼을 누르면 장바구니 state에 항목이
하나 추가되는 기능을 만들어보자 이것도 state 변경함수 만들고
export하고 import해서 사용한다.

let cart = createSlice({
name : 'cart',
initialState : [
  {id : 0, name : 'White and Black', count : 2},
  {id : 2, name : 'Grey Yordan', count : 1}
],
reducers : {
  addCount()
(생략)
  addItem(state, action){
    state.push(action.payload)
  }
}
}) 

addItem()이라는 함수를 만들어 state 오브젝트에 새로운 배열을
action.payload 에 가져와 push 하는 과정을 진행해보자.

(Detail.js)

<div className="col-md-6">
<h4 className="pt-5">{찾은상품.title}</h4>
<p>{찾은상품.content}</p>
<p>{찾은상품.price}원</p>
<button className="btn btn-danger" onClick={()=>{
  dispatch(addItem( {id : 1, name : 'Red Knit', count : 1} ))
}}>주문하기</button>
</div>
</div> 

상세페이지(Detail.js)에서도 useDispatch를 import 해주고
만든 addItem()함수도 import 해주자. 그리고 위와같이
id,name,count등을 넣어주면 새로운 데이터가 배열안에 들어간다.
우리는 찾은 상품의 id,title등을 알고있으니 저런식으로 하드코딩이아닌

addItem({id: finded.id, name: finded.title, count: 1,})

이와 같은 방법으로 state 를 연결해주면 더 좋을거같다.

응용문제 3

표의 행마다 삭제버튼 만들고 그거 누르면 상품이 삭제되게 만들려면?

문제를 해결하려면 일단 배열에서 특정 값을 제거하는 함수를 알아야한다
검색으로 popsplice라는 함수를 알게되었다 이중 응용1번 처럼
장바구니의 값을 정렬할때 위치가 바뀔수도 있으니 고유 번호인 id값을
기준으로 진행하게 할려면 아래와 같이 코드를 사용할수 있을것이다.

DeleteItem(state, action) {
          let paynum = state.findIndex((a) => {
              return a.id === action.payload;
          });
          state.splice(paynum, 1);
      },

state.spicle(paynum,1)은 이제 배열의 paynum에 해당하는
객체부터 ~1만큼 제거하겠다는 뜻이다 즉 paynum에 해당하는 객체만 삭제

<button
onClick={() => {dispatch(DeleteItem(state.cartdata[i].id));}}

사용하는 cart.js 부분에도 응용 1과 같이 cartdata.id값을 보내주면 끝.

응용문제 4

주문하기 버튼 누를 때 이미 상품이 state안에 있으면 추가가 아니라 기존 항목 수량증가만?

기존의addItem에서 if 문을 사용해 장바구니에 이미 존재하는 id 는
count++하고 없는 id 만 add 해주는 과정이 필요할꺼같다.

addItem(state, action) {   
// id 를 확인해 이미 장바구니에 있는 id면 count+ 아니면 새로 push
         let paynum = state.findIndex((a) => a.id === action.payload.id);
         if (paynum >= 0) {
             state[paynum].count++;
             console.log(paynum);
         } else {
             state.push(action.payload);
             console.log(paynum);
         }
     },

여기서 중요하게 봐야할 부분은 paynum >=0 부분이다 paynum
어떤 내용이 뜨는지 정확히 보기위해 console.log로 확인해보니
장바구니에 있는 값이면 그 id값의 배열 번호가 뜨고 만약 없으면
-1음수가 나왔다 그러므로 if 문을 사용하여 0보다 크면 count++
작으면 새로 push하는 코드를 작성했다.

onClick={() => { dispatch(
	addItem({
		id: finded.id,
		name: finded.title,
		count: 1,
			})
		);

18. localStorage로 만드는 최근 본 상품 기능1

새로고침하면 모든 state 데이터는 리셋된다. 왜냐면 새로고침하면
브라우저는 html css js 파일들을 첨부터 다시 읽기 때문이다.
이게 싫다면 state 데이터를 서버로 보내서 DB에 저장하거나 하면 된다.
내가 서버나 DB 지식이 없다면 localStorage를 이용해도 된다.
유저의 브라우저에 몰래 정보를 저장하고 싶을 때 쓰는 공간이다.

크롬개발자 도구에서 Application 탭 들어가면 볼수있으며 사이트마다 5MB정도의 문자 데이터를 저장할 수 있다.
object 자료랑 비슷하게 key/value 형태로 저장하며
유저가 브라우저 청소를 하지 않는 이상 영구적으로 남아있는다.
밑에 있는 Session Storage도 똑같은데 브라우저 끄면 삭제되는 휘발성.

사용법

js 파일 아무데서나 다음 문법을 쓰면 localStorage에
데이터 입출력 할 수 있다. 차례로 추가, 읽기, 삭제 문법이다.

localStorage.setItem('데이터이름', '데이터');
localStorage.getItem('데이터이름');
localStorage.removeItem('데이터이름')

object 는 못함?

localStorage에 array/object 자료를 저장하려면 문자만 저장할 수 있는 공간이라 array/object를 바로 저장할 수는 없다. 강제로 저장
해보면 문자로 바꿔서 저장해주는데 그럼 array/object 자료가 깨진다.
그래서 편법이 하나 있는데 array/object -> JSON 이렇게 변환해서
저장하면 된다.JSON은 문자취급을 받아서 그렇다. JSON은 그냥 따옴표친 array/object 자료다.

localStorage.setItem('obj', JSON.stringify({name:'kim'}) );

이런식으로 코드를 만들면 obj라는 key를 가지고 name:kim이라는
object 값의 value를 가진 값을 저장할수있다.

var a = localStorage.getItem('obj');
var b = JSON.parse(a)

당연히 꺼낼때도 그냥 꺼내면 JSON값을 그대로 가져오기 때문에
JSON.parse를 사용해서 array/object값으로 다시 변경해줘야한다.

응용문제 1

localStorage 를 사용해서 main페이지에서 최근 열람한 상품들을 간단하게
보여주는 ui를 만들어보자 그러기 위해선 id 값들을 가져와야한다.
1.먼저 최상단의 App.js 에서 시작시 localStorage에 배열을 넣어 초기화하자.

useEffect(()=>{
 localStorage.setItem('watched', JSON.stringify( [] ))
},[]) 

2.Detail.js에서 get 을 통해 watched배열을 가져와보자.
3. 가져온 배열에서 현재있는 상품 페이지의 id를 넣는다.
4. set으로 다시 배열을 localstorage 에 저장하자.

(Detail.js)
useEffect(()=>{
let 꺼낸거 = localStorage.getItem('watched')
꺼낸거 = JSON.parse(꺼낸거)
꺼낸거.push(찾은상품.id)
localStorage.setItem('watched', JSON.stringify(꺼낸거))
}, [])
  1. 작동은 잘하지만 이미본 상품도 계속 추가되는 버그가 있다 고쳐보자
  2. 상품 id에 이미 같은 값이 있으면 추가하지말라고 if문을 사용할수있지만
    set이란 문법을 통해 중복을 제거하고 다시array로 감싸는 방법을 사용해보자 아래의 두 문장을 중간에 넣어주면 잘 동작 할거다.
    (생략)
    꺼낸거 = new Set(꺼낸거)
    꺼낸거 = Array.from(꺼낸거)
    (생략)
    7.근데 사이트 새로고침시 배열이 전부 사라지게 되는데 이는
    우리가 처음에 선언한 app.jswatched에 대한 useEffect
    때문이다.if문을 통해 배열이 null값이 아니라면 실행하지 않겠다고 선언하자.

19. 실시간 데이터가 중요하면 react-query

ajax 요청하다보면 아래와 같은 기능들이 필요해진다.

  • 몇초마다 자동으로 데이터 다시 가져오게 하려면?
  • 요청실패시 몇초 간격으로 재시도?
  • 다음 페이지 미리가져오기?
  • ajax 성공/실패시 각각 다른 html을 보여주려면?

이러한 기능들을 react-quert를 사용하면 쉽게 넣을수있다.

react-query 세팅

  1. 설치: npm install @tanstack/react-query
  2. 세팅: index.js 로 가서 아래의 코드 Import
    import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
  3. 원래의 index.js 코드에 2번을 import 하고 QueryClient태그로 감싸준다.
     <QueryClientProvider client={queryClient}> 
     <Provider store={store}>
       <BrowserRouter>
         <App />
       </BrowserRouter>
     </Provider>
    </QueryClientProvider>
  4. https://codingapple.com/course-status/ 기능

20. 성능 개선 1 개발자도구 & lazy import

크롬 확장프로그램 : React Developer Tools

props를 보냈는데 출력이 안된다거나 이미지를 넣었는데 안보이는
버그같은게 생기면 개발자도구를 켜서 Elements 탭 살펴보면 되는데
여기선 짠 코드가 실제 html css로 변환되어서 보인다.
그게 싫고 컴포넌트로 미리보고 싶으면 리액트 개발자도구를 설치해서 켜보면 됨.

위의 확장 프로그램을 사용하여 컴포넌트를 미리 확인 할수있으며
왼쪽상단 아이콘눌러서 컴포넌트 찍어보면 거기 있는
state, props 들을 조회가 가능하다.
Profiler 탭에서는 녹화버튼을 누르고 앱에서의 동작 버튼클릭 페이지이동
들을 해본후 녹화를 종료하면 방금 렌더링된 모든 컴포넌트의 렌더링 시간을
측정해준다 이를통해 딜레이 걸리는 문제들을 확인이 가능하다.
Redux전용 developer tools도 있으니 나중에 사용해보자.

lazy import

react 를 다짜고 나서 배포를 할려고 build하는 순간 우리가 짠
모든 코드의 내용이 하나의 js 에 묶여서 배포된다. 이렇게되면
js의 용량이 크기때문에 사용자가 처음 접속시 시간이 오래걸리것이며
이를 자원을 크게 잡아 먹는다고 말할수있다. 그렇기 때문에 우리는
lazy import라는 것을 사용해 메인에서 바로 처리하지 않는 컴포넌트를 사용하기 전까지 자원을 받지 않게 할것이다.
예를들어 아래의 코드는 App.js의 시작시 사용되지않음으로

(App.js)
import Detail from './routes/Detail.js'
import Cart from './routes/Cart.js'

다음과 같이 바꿔줄것이다.

(App.js)
import {lazy, Suspense, useEffect, useState} from 'react'
const Detail = lazy( () => import('./routes/Detail.js') )
const Cart = lazy( () => import('./routes/Cart.js') )

물론 lazy import도 사용이 가능한데 이는 선언한 컴포넌트들이
필요하면 그때 load 해주세요 라는 의미를 담고있다.
lazy를 사용하면 당연히 Detail 컴포넌트 로드까지 지연시간이 발생한다.
1. Suspense 라는거 import 해오고
2. Detail 컴포넌트를 감싸면 Detail 컴포넌트가 로딩중일 때 대신 보여줄 html 작성도 가능하다. 귀찮으면 <Suspense>이걸로 <Routes> 전부 감싸도 된다.

<Suspense fallback={ <div>로딩중임</div> }>
<Detail shoes={shoes} />
</Suspense>

##21. 성능 개선2 재렌더링 막는 memo, useMemo

컴포넌트가 재렌더링되면 거기 안에 있는 자식컴포넌트는 항상 함께 재렌더링된다.리액트는 그렇게 대충 무식하게 동작하는데 평소엔 별 문제가 없겠지만 자식컴포넌트가 렌더링시간이 1초나 걸리는 무거운 컴포넌트면 곤란해진다.
부모컴포넌트에 있는 버튼 누를 때 마다 1초 버벅이는 불상사가 발생하기때문
그럴 땐 자식을 memo로 감싸놓으면 된다.

function Child(){
console.log('재렌더링됨')
return <div>자식임</div>
}

function Cart(){ 
let [count, setCount] = useState(0)

return (
 <Child />
 <button onClick={()=>{ setCount(count+1) }}> + </button>
)
}

테스트용 자식 컴포넌트 를 하나 만들어보았다. 이제 만약 cart컴포넌트가
재렌더링 될때 child컴포넌트 또한 재렌더링 될것이다. 만약 child가 이처럼 가벼우면 상관없는데 1초 정도 걸리는 동작이 들어가면 버튼 하나
누를때 마다 동작이 더뎌질것이기 떄문에 memo를 통해 꼭 필요할때만
재렌더링 되게 바꿔줄수있다.
위의 코드에서 child 함수부분만 살짝바꿔줘 보자

   let Child = memo( function(){
  console.log('재렌더링됨')
  return <div>자식임</div>
})

이제 cart가 재렌더링 될때 같이 재렌더링 되는게아닌 child자체의
state가 변경 될때만 console이 찍힐것이다. 즉 button
누를 때만 작동된다는 뜻이다.import 도 까먹지 말고 해주자
import {memo, useState} from 'react'
그렇다고 memo를 전부 사용하면 문제가 발생되는데 이는 memo의 동작
안에 기존의 state와 이후 state의 변경점을 확인하는 연산이 있기 때문에
많은 사용은 오히려 부하를 주기 때문이다.

##22. 성능 개선 3 useTransition

렌더링시간이 매우 오래걸리는 컴포넌트가 있다고 했을때
버튼클릭, 타이핑할 때 마다 그 컴포넌트를 렌더링해야한다면
이상하게 버튼클릭, 타이핑 반응속도도 느려진다 개선방법을 알아보자.
당연히 그 컴포넌트 안의 html 갯수를 줄이면 대부분 해결되는데
근데 그런게 안되면 useTransition 기능을 쓰면 된다.

import {useState} from 'react'

let a = new Array(10000).fill(0)

function App(){
let [name, setName] = useState('')

return (
 <div>
   <input onChange={ (e)=>{ setName(e.target.value) }}/>
   {
     a.map(()=>{
       return <div>{name}</div>
     })
   }
 </div>
)
}

위처럼 사용자가 input에 입력한 정보를 map을 통한 10000개의
값을 가진 배열에 집어넣어 <div>안에 보여주는 무식한 코드를
만들어보자 유저가 <input>에 타이핑하면 그 글자를 <div>
1만개안에 집어넣어줘야하는데 <div> 1만개 렌더링해주느라
<input>도 많은 지연시간이 발생한다.

import {useState, useTransition} from 'react'
let a = new Array(10000).fill(0)

function App(){
let [name, setName] = useState('')
let [isPending, startTransition] = useTransition()

return (
 <div>
   <input onChange={ (e)=>{ 
     startTransition(()=>{
       setName(e.target.value) 
     })
   }}/>

   {
     a.map(()=>{
       return <div>{name}</div>
     })
   }
 </div>
)

useTransition() 쓰면 그 자리에 [변수, 함수]가 남는다.
우측에 있는 startTransition() 함수로 state변경함수 같은걸
묶으면 그걸 다른 코드들보다 나중에 처리해준다.
그래서 <input> 타이핑같이 즉각 반응해야하는걸 우선 처리해줄 수 있다.
물론 근본적인 성능개선이라기보단 특정코드의 실행시점을 뒤로 옮겨주는 것일 뿐이다.

isPending은 어디다 쓰냐면

{
isPending ? "로딩중기다리셈" :
a.map(()=>{
 return <div>{name}</div>
})
} 

startTransition() 으로 감싼 코드가 처리중일 때 true로 변하는 변수다.그래서 이런 식으로 코드짜는 것도 가능 위의 코드는 useTransition으로 감싼게 처리완료되면 <div>{name}</div> 이게 보이겠군요.

profile
IT 학습과정중 학습내용을 복기하기 위해 사용하는 블로그 입니다.

0개의 댓글