React로 minishop 만들기(2)

seul_velog·2022년 5월 14일
0

✍️ useEffect 정리


1. useEffect에 대해

const Datail = ({ shoes }) => {
  let [count, setCount] = useState(0);
  useEffect(() => {
      // 1)
    console.log('안녕'); // 2)
  });
  // 3)
...


return (
  ...
  {count}
  <button
    onClick={() => {
      setCount(count + 1);
    }}> 버튼 </button>
  ...

  • 1) 이 안에 적은 코드는 해당 컴포넌트가 처음 마운트 되었을 때와 업데이트 되었을 때(재렌더링) 실행된다.
  • 두 번 출력되는 이유 : useEffect에 있는 코드는 두 번 정도 실행 될 수 있다. (디버깅을위해) 실제 배포시에는 한 번만 실행된다.
    index.js 파일에서 <React.StrictMode> 를 제거하면 한 번만 뜨게 할 수도 있다.

❓🤔 2) 에서 해당 코드를 useEffect 바깥 3) 에서 실행해도 같은 결과가 나온다.
그럼 언제 useEffect 를 쓰면 좋을까?

  • useEffect 안에 적은 코드는 렌더링이 되고 난 후 실행이된다는 점을 기억하자! 즉 html을 다 그려준 뒤 해당 코드가 실행된다.
  • 만약 3) 위치에 복잡한 연산이 필요한 코드가 작성되어 있다면 html 렌더링 작업은 딜레이가 생길 수 있지만, useEffect 안에 작성한다면 html이 먼저 로딩이 된 후 실행되므로 더 효율적으로 동작할 수 있다.😀

📌 따라서 아래와 같은 경우 useEffect 안에 코드를 작성하면 효율적으로 렌더링이 가능해진다.

  • 어려운 연산이 필요한 작업
  • 서버에서 데이터를 가져오는 작업 (오래걸리고 html렌더링보다 덜 중요함)
  • 타이머 작업

✍️ Side Effect 란?

  • 함수의 핵심기능(html렌더링 등)과 상관없는 부가기능
    Side Effect에서 따온 이름으로써 useEffect 로 이름이 지어졌다고 한다.🧐

📌 과제 1) Detail 페이지 방문 후 2초 후에 alert 박스가 사라지게 만들기

const Datail = ({ shoes }) => {
  let [alert, setAlert] = useState(true); // 1)
  useEffect(() => {
    console.log('useEffect 됐다!');
    setTimeout(() => {
      setAlert(false);
    }, 2000);
  });
  
  
return (
  <div className='container'>
    {alert === true ? // 2)
      (<div className='alert alert-werning'>2초이내 구매시 할인</div>) : null}
    ...
  • state가 true 라면 alert 박스가 보이도록, false라면 안보이도록 구현해보자.

  • 1) useState 로 초기 상태값을 true로 설정한다.
    → UI 상태 저장할 state 만들기

  • 2) 삼항연산자를 통해서 조건을 설정한다.
    → state에 따라서 UI가 어떻게 보일지 작성하기

  • html이 먼저 렌더링된 후, useEffect 안의 setTimeout 을 통해서 2초 뒤 state를 false로 변경한다.

📌 solution

  • 기존 코드는 컴포넌트가 mount, update시 모두 실행되었다.
  • useEffect 실행조건을 넣을 수 있는 곳에 [] 빈 배열을 설정하는 것이 더 정확한 답이다. (mount에만 실행)
    즉 컴포넌트 mount시 한 번만 실행하고 싶으므로, 이렇게 작성하는 것이 효율적인 정답이 될 수 있다.
  • deps 에 alert를 넣으면 alert라는 state가 변할 때만 실행된다. 즉, mount될 때와 alert state가 변할 때 실행된다.



📌 과제 2) 인풋 값에 숫자가아닌 문자를 입력했을 때 경고창 보여주기

let [message, setMessage] = useState(true);

 const changeHandler = (e) => {
    const value = Number(e.target.value); // 1)
    if (isNaN(value)) { // 2)
      setMessage(false);
    } else {
      setMessage(true);
    }
  };


return (
  ...
  
  {message === true ? null : (
  <div
    className='alert alert-werning'
    style={{ backgroundColor: 'orange' }}
    >  숫자만 입력하세요 </div> 
   )}
  <input type='text' onChange={changeHandler} />
  • JSX로 UI구조를 작성하고 state를 설정한다.
  • onChange를 통해서 인풋 값을 입력받아서 처리하는 방법으로 작성했다.
  • 1) 기본적으로 value는 string이므로 숫자로 변환한다.
  • 2) 입력받은 value 값이 숫자가 아닌지 판별해서 아니라면 상태를 false로 변경한다.

✍️ 굳이 useEffect 훅으로 구현을 안해도 되지만, 훅을 활용하려면 어떻게 사용해야할까? 🤔




2. setTimeout

useEffect(() => {
  console.log('useEffect 됐다!');
  let a = setTimeout(() => { // 1)
    setAlert(false);
  }, 2000);
  return () => {
    console.log('useEffect가 동작되기 전에 실행되는 코드');
    clearTimeout(a); // 2)
  };
}, [count]);
  • 1) setTimeout 함수를 변수에 할당해서 사용하면 2) 에서 처럼 clearTimeout 함수를 통해서 타이머를 제거해줄 수 있다.




3. Ajax

  • 리액트에서는 보통 서버와 ajax를 이용해서 통신한다.
  • 서버에 데이터를 요청할 때 어떤방법(GET/POST)으로 어떤자료(URL)를 원하는지 잘 전달해야 한다.
  • 데이터를 가져올 때는 GET, 보낼 때는 POST 요청을 한다. URL은 서버에서 받아와서 사용한다.
  • 서버개발시엔 누가 어떤 URL로 요청하면 어떤 데이터를 보내줄지에 대해 코드를 짜게 된다.
  • 서버와 데이터를 주고받을 때는 문자형태로 주고받는다.

서버에 GET 요청하기

GET 요청 방법

  • 브라우저 url 이용하기 (이 주소창 자체가 서버에 GET요청하는 곳이다.) GET / POST 시 새로고침된다.
  • ajax로 자바스크립트 코드로도 GET 요청을 할 수 있다. 새로고침 없이도 GET / POST 요청이 가능하다.

1) ajax 사용하기

ajax 옵션 세가지

  • XMLHttpRequest (예전 자바스크립트 방식)
  • fetch() (최신 자바스크립트 방식)
  • axios (외부라이브러리)

2) axios (외부라이브러리) 사용하기

라이브러리 설치 및 셋팅

$ npm install axios
import axios from 'axios';
  • ajax를 이용한 GET요청은 axios.get('url')
  • 요청결과는 axios.get('url').then
  • ajax 요청이 실패할 경우 catch 문 작성
  • 원래는 데이터를 JSON으로 받아오지만 axios가 array 형태로 자동으로 바꿔준다.

ex.)

<button
  onClick={() => {
    // axios 라이브러리 사용
    axios
      .get('https://codingapple1.github.io/shop/data2.json')
      .then((result) => {
        console.log(result.data); // 1)
      })
      .catch(() => {
        console.log('실패했을 때 처리할 코드');
      });
  }}
>
  데이터 받아오기
</button>


// fetch 사용예
fetch('https://codingapple1.github.io/shop/data2.json')
  .then((res) => res.json()) // array/object 변환과정 필요
  .then((data) => {
  console.log(data); // 2)
});
  • 1)과 2)의 콘솔 출력값은 동일하다.

3) 동시에 ajax 요청을 여러개 하려면

Promise.all [ axios.get('/url1'), axios.get('/url2') ]
.then(()=>{}) // 전부 성공할경우 실행



📌 과제) 서버에 GET 요청해서 받아온 데이터 보여주기

<button
  onClick={() => {
    axios
      .get('https://codingapple1.github.io/shop/data2.json')
      .then((result) => {
        const copyShoes = [...shoes, ...result.data];
        setShoes(copyShoes);
      })
      .catch(() => {
        console.log('실패했을 때 처리할 코드');
      });
  }}
>
  데이터 받아오기
</button>

✍️ 첫 시도에는 map() 함수와 push() 를 사용했는데 이 방법보다는 복사해온 데이터에 바로 합칠 수 있는 concat 이나 혹은 spread 문법을 이용하는 것이 좋을 것 같다. 😀



서버에 POST 요청하기

서버로 데이터전송하는 POST 요청

axios.post('/uri주소', {name:'seul'})




4. 삼항연산자 대신 if문 사용하기

Tab UI만들기

// state 저장
 let [tab, setTab] = useState(0);

// TabComponent를 보여줄 UI
 <TabContent tab={tab} />


// state를 동작 시킬 스위치
<Nav.Link
  eventKey='link0'
  onClick={() => {
    setTab(0);
  }}
  >
  

// TabContent 컴포넌트
const TabContent = ({ tab }) => {
  if (tab === 0) {
    return <div>내용0</div>;
  } else if (tab === 1) {
    return <div>내용1</div>;
  } else if (tab === 2) {
    return <div>내용2</div>;
  }
};
  • 먼저 UI를 작성한다. 그 다음 state를 설정해주고 state에 따라 어떤 UI를 보여줄지 작성한다.
  • 마지막으로 state를 변동할 스위치를 작성한다.
  • if문을 사용하기 위해 JSX문법 바깥에서 새로운 컴포넌트를 만들고 if문에 따라 return 값(렌더링될 코드)을 설정해준다.
  • 코드를 더 깔끔하게 작성하기 위해 아래와 같이 리팩토링할 수 있다.
const TabContent = ({ tab }) => {
  return [<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][tab];
};

✍️ 배열안에 보여줄 내용들을 전부 넣고 [tab] 의 상태값에 따라서 배열 인덱스에 맞는 요소를 return 한다.
아마 리턴해야할 내용이 길다면 가독성이 더 떨어지지 않을까? 하지만 이 예제에서는 이렇게 리팩토링하는 편이 더 간편한 것 같다. 😀




5. 전환애니메이션 효과

  1. 애니메이션 동작 전 className 만들기
  2. 애니메이션 동작 후 className 만들기
  3. className에 trancition 속성 추가
  4. 원할 때 2번 className 부착하기
/* css */
.start {
  opacity: 0;
}

.end {
  opacity: 1;
  transition: opacity 0.5s;
}
// TabContent component
const TabContent = ({ tab }) => {
  let [fade, setFade] = useState(''); // 1)

  useEffect(() => { // 3)
    setTimeout(() => {
      setFade('end'); // 3-2)
    }, 100);

    return () => {
      setFade(''); // 3-1)
    };
  }, [tab]);

  return (
    <div className={`start ${fade}`}> // 2)
      {[<div>내용0</div>, <div>내용1</div>, <div>내용2</div>][tab]}
    </div>
  );
};
  • 1) 애니메이션 동작 전후 css를 만들고 state에 변수를 설정한다.
  • 2) className에 JSX 문법에 맞게 해당 변수를 적용한다.
  • 3) 이때 setTimeout를 통해서 비동기적으로 처리하지 않으면 적용되지 않았다.

❓왜 그럴까? 🤔
최신 리액트의 automatic batching 기능 때문이라고 한다.
state 변경함수를 쓸 때마다 재렌더링을 하는 것이 아니라, state변경이 다 되고나서 재렌더링을 한번 시켜주기 때문!
따라서 3-1) return(cleanup)문 에서 먼저 실행 후 3)-2 setFade 가 작동하도록 코드를 짜 두었어도 두 개를 합쳐서 한 번 렌더링을 실행하기 때문이다.

  • 아래와 같이 useEffect내 cleanup기능을 사용해서 setTimeout을 활용할 수도 있다.

    const TabContent = ({ tab }) => {
      let [fade, setFade] = useState('');
    
      useEffect(() => {
        let fadeTimeout = setTimeout(() => { // 변수에 담고
          setFade('end');
        }, 100);
    
        return () => {
          clearTimeout(fadeTimeout); // 지워준다
          setFade('');
        };
      }, [tab]);




6. Context API

  • props 전송없이 state 공유가 가능하다. 즉, 자식컴포넌트는 props 없이 state를 사용할 수 있게된다.
  • 성능이슈(state 변경시 불필요한 부분까지 재렌더링)와 컴포넌트 재활용이 어렵다(다른 페이지에서 컴포넌트를 가져다 쓸 경우 문제가 발생될 수 있음)는 두 가지 단점 때문에 자주 사용되지는 않는다.🤔
    ✍️ 따라서 외부 라이브러리 Redux 를 사용!
profile
기억보단 기록을 ✨

0개의 댓글