리액트에서의 불변성

불변성이 리액트에서 문제가 되는 이유는, 리액트는 렌더링을 할때 얕은 비교를 수행하기 때문이다.

리액트는 기본적으로 state등의 값이 변하는 것을 확인하고, 실제로 변했다면, 다시 렌더링하는 특성을 가지고 있다.

또한 부모 컴포넌트의 값이 변하면, 해당 컴포넌트의 자식 컴포넌트까지도 전부 다시 렌더링한다.(최적화 문제 발생)

이때 값이 변한다의 기준은 리액트 입장에서는 메모리 주소 참조값이 변한다 이다.

let a = [1, 2, 3]
a.push(4)
console.log(a)

예를 들어 이런 상황에서 a의 메모리 주소는 push를 한 다음에도 메모리 주소가 변경되지 않는다.
메모리 주소가 변겨오디지 않았기 때문에 렌더링이 진행되지 않는다.

import React, { useState } from 'react'

function Test() {
  const [a, setA] = useState([1, 2, 3])
  console.log(a)
  return (
    <div>
      <p>{a}</p>
      <button onClick={() => {
        a.push(4)
        setA(a)
      }}>추가하기</button>
    </div>
  )
}

export default Test

이런식으로 버튼을 클릭하면 a 배열에 4 라는 요소를 추가하고 그 값을 setA를 통해 a를 변경하게 된다고 해도 객체의 특성상 메모리 주소는 그대로이기 때문에, setA를 하더라도 리액트가 값이 달라졌다고 인식을 못하기 때문이다.

따라서 push나 pop으로 데이터를 삭제, 추가를 해도 리액트에서는 렌더링이 되지않는다는 소리!

데이터 추가하기

리액트에서 배열에 데이터를 추가할 때는

[...배열, 새로운 데이터]

방식으로 작성하면 된다.

데이터 삭제하기

데이터를 제거할때는 filter를 사용한다

배열.filter(요소 => 남기고 싶은 요소 조건)

데이터 수저아기

특정한 데이터를 수정하고 싶다면, 삼항 연산자와 함께 map을 사용하면 된다.

배열.map(요소 =>수정하고자 하는 데이터가 맞니 ? (맞다면) 수정할 값 : (아니라면) 원래 요소를 뱉으렴

import React, { useState } from 'react'
import { data } from '../constants/data'
function Board() {
    const [posts, setPosts] = useState(data)
    const removePost = (id) => {
        setPosts(posts.filter((post) => post.id !== id))
    } // id와 일치하지 않는 것만 남긴다!
    const updatePost = (newPost, id) => {
        setPosts(posts.map((post) => (post.id === id ? newPost : post)))
    } // id와 일치하는 것만 수정한다!
    return (
        <div>
            {posts.map((post) => (
                <div key={post.id}>
                    <h1>{post.title}</h1>
                    <p>{post.content}</p>
                    <button onClick={() => removePost(post.id)}>제거하기!</button>
                    <button onClick={() => updatePost({ ...post, title: '새로운 타이틀' }, post.id)}>수정하기!</button>
                </div>
            ))}

            <button onClick={() => setPosts([...posts, { id: 4, title: '4번째 끌', content: '4번째 내용' }])}>추가</button>
        </div>
    )
}

리액트의 생명주기

컴포넌트가 표시되고 사라지는 순간까지를 하나의 생명주기라고 표현한다.

mount(컴포넌트가 표시될때) -> update(컴포넌트 내부의 요소가 업데이트 될때) -> unmount(컴포넌트가 사라질때)

useEffect는 그 시점중 하나를 골라서, 해당 시점에 특정한 함수가 실행되도록 하는 Hook 이다.

useEffect는 어떤 시점에서 함수를 실행시킬지에 따라 아래와 같이 사용한다.

  • mount(컴포넌트가 표시될때)

    useEffect(컴포넌트가 표시될때 실행할 함수, [빈 대괄호]);

  • update

    useEffect(오른쪽 리스트에 적은 요소들이 업데이트 되는 시점에 실행할 함수, [업데이트 되는지 지켜볼 변수/State]);

  • unmount(컴포넌트가 사라질때)

    useEffect(() => {return () => 컴포넌트가 사라질 때 실행할 함수})

useEffect 사용하기

  • mount
    컴포넌트가 표시되고 3초뒤에 setisLoading 값을 변경하여 렌더링 되는 항목을 변경하는 컴포넌트를 제작한다.
import React, { useEffect, useState } from 'react'

function Loading() {
    const [isLoading, setIsLoading] = useState(true)

    useEffect(() => {
        setTimeout(() => {
            setIsLoading(false)
        }, 3000)
    }, [])

    return <div>{isLoading ? <>로딩중</> : <>로딩완료</>}</div>
}

  • update
    시간이 지나서 isLoading이 변경될때 텍스트가 추가되는 기능을 추가한다
 useEffect(() => {
        setText([...text, '추가'])
    }, [isLoading])

    return (
        <div>
            {isLoading ? <>로딩중</> : <>로딩완료</>}
            <p>{text}</p>
        </div>
    )

엥 근데 왜 '추가' 가 2개가 나오지? 값은 한번만 변한거 아니야?

처음에 선언할때도 useEffect의 2번째 대괄호의 내용이 변했다고 간주하므로 setText가 실행된다.

타이머를 만들어보자

시작과 중지 버튼을 추가해서 타이머를 멈추게 하거나 다시 시작하게 하는 기능을 추가한 타이머를 만들어보자.

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

function Timer({ s }) {
    const [seconds, setSeconds] = useState(s)
    const [isClicked, setIsClicked] = useState(false)
	// 클릭할 때 결정되는 isClicked의 값에 따라 다르게 동작한다.
    useEffect(() => {
        if (isClicked) {
            const countDown = setTimeout(() => {
                setSeconds(seconds - 1)
            }, 1000)
            return () => clearTimeout(countDown)
        }
    }, [seconds, isClicked])

    return (
        <div>
            <h1>타이머</h1>
            <h1>{seconds}</h1>
            <button onClick={() => setIsClicked(true)}>시작</button>
            <button onClick={() => setIsClicked(false)}>중지</button>
        </div>
    )
}

세계는 지금 몇시지?

moment-timezone을 사용해서 다른 나라의 실시간 시간 정보를 사용한 시계를 만들어보자.

import React, { useState } from 'react'
import moment from 'moment-timezone'
import { useEffect } from 'react'

function Prc() {
    const [times, setTimes] = useState([
        { id: 1, tz: 'Asia/Seoul', time: moment().tz('Asia/Seoul').format('YYYY-MM-DD HH:mm:ss') },
        { id: 2, tz: 'Asia/Taipei', time: moment().tz('Asia/Taipei').format('YYYY-MM-DD HH:mm:ss') },
        { id: 3, tz: 'Europe/London', time: moment().tz('Europe/London').format('YYYY-MM-DD HH:mm:ss') },
    ])

    useEffect(() => {
        const interval = setInterval(() => {
            setTimes(times.map((item) => ({ ...item, time: moment().tz(item.tz).format('YYYY-MM-DD HH:mm:ss') })))
        }, 1000)
        return () => clearInterval(interval)
    }, [])

    return (
        <div>
            {times.map((times) => (
                <div key={times.id}>
                    <h1>{times.tz}의 현재시각</h1>
                    <h1>{times.time}</h1>
                </div>
            ))}
        </div>
    )
}

export default Prc

times에 들어있는 객체 데이터를 활용해서 각 나라의 현재 시각을 setInterval()을 사용해서 1초마다 값을 최신하 하여 실시간으로 시간을 보여주는 시계가 되었다.

profile
개발자 꿈나무

0개의 댓글