도리쓰에러쓰

[React] Lifecycle Hook(예전 문법) vs useEffect(요즘 문법) 본문

코딩애플 (React)/기초수업 (코딩애플) - 2

[React] Lifecycle Hook(예전 문법) vs useEffect(요즘 문법)

강도리 2022. 1. 18. 14:29

저번 게시물에 이어서 작성하겠습니다. (코드 참고)

 

[React] SASS 개념, 설치 및 기본 문법

저번 게시물에 이어서 작성하겠습니다. (코드 참고) [React] styled-components를 이용한 class없는 CSS 스타일링 저번 게시물에 이어서 작성하겠습니다. (코드 참고) [React] React Router 3 :: URL 파라미터로..

dori-coding.tistory.com


1. Lifecycle Hook

Component는 '등장 - 업데이트(재렌더링) - 퇴장' 주기가 있습니다.

Lifecycle Hook은 Component의 주기 중간에 명령을 줄 수 있습니다.

가장 유용한 Hook은 componentDidMount()와 componentWillUnmount() 입니다.

class Detail2 extends React.Component {
    // Detail2 컴포넌트가 Mount 되었을 때 실행할 코드
    componentDidMount() {

    }

    // Detail2 컴포넌트가 Unmount 되었을 때 실행할 코드
    componentWillUnmount() {

    }
}

2. useEffect Hook

useEffect Hook은 Component가 mount 되었을 때도 컴포넌트가 update 될 때도 특정 코드를 실행할 수 있습니다.

요즘 React를 개발할 땐 Lifecycle Hook보다 useEffiect Hook을 더 많이 사용합니다.

 

- 우선 useEffect를 import 합니다.

import React, { useEffect, useState } from 'react';

 

- 그리고 함수로 호출하여 사용하면 됩니다.

function Detail() {
	useEffect(()=>{
    	// 코드 작성
    });
    
    return (
    	// HTML 코드
    )
}

 

- 여러개 사용하고 싶다면 아래와 같이 작성하면 됩니다.

function Detail() {
	useEffect(()=>{
    	// 첫번째로 실행할 코드
    });
    
    useEffect(()=>{
    	// 두번째로 실행할 코드
    });
    
    return (
    	// HTML 코드
    )
}

3. useEffect()를 이용하여 2초 후에 alert 창 사라지게 하기

1) UI를 보이게 혹은 보이지 않게 하는 상태를 state로 저장

let [alert, setAlert] = useState(true);

 

2) if문 등을 이용해 state가 true일 때만 UI를 보여줍니다.

{
    alert === true
    ? (<div className='my-alert-red'>
            <p>재고가 얼마 남지 않았습니다!</p>
        </div>)
    : null
}

 

3) state값을 변경하는 함수 setAlert()를 통해 값을 false로 바꿔주면, 2초 뒤에 alert창이 보이지 않게 됩니다.

useEffect(()=>{
    let timer = setTimeout(() => { setAlert(false) }, 2000);
});

 

- 다음과 같이 alert창이 사라진 것을 확인할 수 있습니다.

🔽


📌 useEffect()를 맨처음 <Detail> load될 때만 실행하게 하고 싶다면?

- 우선 input을 하나 만듭니다.

<input onChange={ (e)=>{ setInputData(e.target.value) } }/>

 

- input에 입력된 값을 저장하는 변수를 만듭니다.

let [inputData, setInputData] = useState('');

 

- input에 무언가를 입력하면 업데이트(재렌더링)이 일어나는데 그럴 때마다 useEffect()도 계속 실행됩니다. 확인해보기 위해 임시로 아까 만들어뒀던 useEffect()에 console.log()를 찍어봅시다.

useEffect(()=>{
    let timer = setTimeout(() => { setAlert(false) }, 2000);
    console.log('test');
});

 

- 개발자 도구에서 확인해보면 input에 값을 입력할 때마다 update가 되어 useEffect()가 실행된다는 것을 확인할 수 있습니다.

 

 

- useEffect()는 업데이트 될 때마다 실행이 되는데 아래 코드와 같이 특정 state가 변경될 때만 실행되는 실행 조건을 지정하면 됩니다. 그럼 특정 state가 변경되기 전까진 update되어도 useEffect()가 실행되지 않습니다.

useEffect(()=>{
    let timer = setTimeout(() => { setAlert(false) }, 2000);
    console.log('test');
}, [alert]);

 

- 실행 조건인 특정 state를 여러개 작성해도 됩니다.

useEffect(()=>{
    let timer = setTimeout(() => { setAlert(false) }, 2000);
    console.log('test');
}, [alert, inputData]);

 

- 아래 코드와 같이 실행 조건이 빈칸이면 <Detail> 등장 시 한번 실행하고 끝납니다.

useEffect(()=>{
    let timer = setTimeout(() => { setAlert(false) }, 2000);
    console.log('test');
}, []);

3-1. setTimeout 주의점

setTimeout을 사용하면 버그가 생길 수 있고, 그 안에 이상한 코드가 들어갈 수도 있어서 꼭 타이머 해제를 해야합니다.

타이머 해제를 위해 위에서 배웠던 'useEffect()의 실행 조건'을 사용하여 작성해봅시다.

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

4. 전체 코드

App.js

/* eslint-disable */
import React, { useState } from 'react'
import { Navbar, Container, Nav, NavDropdown, Button } from 'react-bootstrap';
import './App.css';
import data from './data.js';
import Detail from './Detail.js'

import { Link, Route, Switch } from 'react-router-dom';

function App() {

  let [products, setProducts] = useState(data);

  return (
    <div className="App">
      <Navbar bg="light" expand="lg">
        <Container>
          <Navbar.Brand href="#home">Saint Laurent</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="me-auto">
              <Nav.Link as={Link} to="/">Home</Nav.Link>
              <Nav.Link as={Link} to="/detail">Detail</Nav.Link>
              <NavDropdown title="Dropdown" id="basic-nav-dropdown">
                <NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
                <NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
                <NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
                <NavDropdown.Divider />
                <NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
              </NavDropdown>
            </Nav>
          </Navbar.Collapse>
        </Container>
      </Navbar>

      <Switch>
        <Route exact path="/">
          <div className='Jumbotron'>
            <h1>20% Season Off</h1>
            <p>
              This is a simple hero unit, a simple jumbotron-style component for calling
              extra attention to featured content or information.
            </p>
            <p>
              <Button variant="primary">Learn more</Button>
            </p>
          </div>

          <div className='container'>
            <div className='row'>
              {
                products.map((a, i) => {
                  return <Card products={a} num={i} key={i}/>
                })
              }
            </div>
          </div>
        </Route>
        
        <Route path="/detail/:id">
          <Detail products={ products }/>
        </Route>
        
        <Route path="/:id">
            <div>아무말</div>
        </Route>
      </Switch>
    </div>
  );
}

function Card(props) {
  return(
    <div className='col-md-4'>
      <img className="img" src={ 'images/img'+ (props.num + 1) +'.jpg' } />
      <h4>{ props.products.title }</h4>
      <p>{ props.products.content }</p>
      <p>{ props.products.price }</p>
    </div>
  )
}

export default App;

 

Detail.js

import React, { useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import './Detail.scss';

let Box = styled.div`
    padding : 20px;
`;

let Title = styled.h4`
    font-size : 25px;
    color : ${ props => props.color }
`;

function Detail(props) {

    let { id } = useParams();
    let history = useHistory();
    let findProduct = props.products.find(function(product) {
        return product.id = id;
    });

    let [alert, setAlert] = useState(true);
    let [inputData, setInputData] = useState('');

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

    return(
        <div className='container'>
            <Box>
                <Title className='red'>상세페이지</Title>
            </Box>

            <input onChange={ (e)=>{ setInputData(e.target.value) } }/>

            {
                alert === true
                ? (<div className='my-alert-red'>
                        <p>재고가 얼마 남지 않았습니다!</p>
                    </div>)
                : null
            }
            
            <div className='row'>
                <div className='col-md-6'>
                    <img className='img' src={ process.env.PUBLIC_URL + '/images/img'+ (Number(findProduct.id) + 1)+'.jpg' } />
                </div>
                <div className='col-md-6 mt-4'>
                    <h4 className='pt-5'>{ findProduct.title }</h4>
                    <p>{ findProduct.content }</p>
                    <p>{ findProduct.price }</p>
                    <button className='btn btn-danger'>주문하기</button>
                    <br />
                    <br />
                    <button className='btn btn-danger' onClick={ () => {
                        history.goBack();
                    }}>뒤로가기</button>
                </div>
            </div>
        </div>
    )
}

export default Detail;

 

* 이외의 코드는 이전 게시물에 작성된 코드와 일치합니다.

Comments