[#50] React - useState

2025. 4. 9. 15:47·LG 유플러스 유레카 SW/React

useState

- 컴포넌트에 상태를 추가하는 Hook
- 화면에 보여주는 데이터를 저장하고 변경할 수 있게 해주는 기능

기본 사용법

const [상태, 상태변경함수] = useState(초기값);
const [state, setState] = useState(초기값);
  • state: 현재 상태 값
  • setState: 상태를 업데이트하는 함수
  • 초기값: 처음 렌더링 시 사용할 값

 

📍 특징

1️⃣ 상태 업데이트

상태를 업데이트하면 컴포넌트가 리렌더링 됨

2️⃣ 중요한 규칙

  • Hook은 컴포넌트 최상위 레벨에서만 호출
  • 조건문이나 반복문 안에서 사용 불가
  • 이벤트 핸들러나 생명주기 내에서 호출 가능

3️⃣ 여러 상태 변수

하나의 컴포넌트에 여러 상태 변수 사용 가능

function 상품목록페이지() {
  const [검색어, 검색어변경] = useState('');
  const [카테고리, 카테고리변경] = useState('전체');
  const [정렬기준, 정렬기준변경] = useState('인기순');
  
  return (
    <div>
      <input 
        value={검색어} 
        onChange={(e) => 검색어변경(e.target.value)}
        placeholder="상품 검색"
      />
      
      <select 
        value={카테고리} 
        onChange={(e) => 카테고리변경(e.target.value)}
      >
        <option value="전체">전체</option>
        <option value="의류">의류</option>
        <option value="가전">가전</option>
      </select>
      
      {/* 정렬 옵션 등 */}
      
      {/* 필터링된 상품 목록 표시 */}
    </div>
  );
}

4️⃣ 객체와 배열 상태

객체나 배열을 업데이트할 때는 불변성을 지켜야 함

// 객체 업데이트 (잘못된 방법)
form.name = '김철수'; // ❌

// 객체 업데이트 (올바른 방법)
setForm({...form, name: '김철수'}); // ✅

// 배열 업데이트 (올바른 방법)
setItems([...items, 새항목]); // ✅

5️⃣ 상태 업데이트 특성

  • 비동기적: setState 호출 직후 상태가 즉시 변경되지 않음
  • 일괄 처리: React는 여러 상태 업데이트를 일괄 처리함
  • 이전 상태 사용: 이전 상태를 기반으로 업데이트할 때는 함수 형태를 사용
import React, { useState } from 'react'

const App = () => {
  const [age, setAge] = useState(0)
  const increment = () => {
    // setAge(age + 1)
    setAge(prev => prev + 1) // 기존 값 기억하는 방식으로 쓰는 것이 유용
  }

  return (
    <div>
      <h1>연습</h1>
      <button
        onClick={() => {
          increment()
          increment()
          increment()
        }}
      >
        +3
      </button>
      <button onClick={increment}>+1</button>
      <h2>입력값 : {age}</h2>
    </div>
  )
}

export default App

 

🔥 자주 발생하는 문제와 해결법

1️⃣ 상태 업데이트 후 이전 값 출력

상태 업데이트는 비동기적으로 처리되어 즉시 반영되지 않음

function 클릭핸들러() {
  set개수(개수 + 1);
  console.log(개수); // 여전히 이전 값 출력
}

2️⃣ 객체/배열 변경이 화면에 반영되지 않음

const [할일목록, set할일목록] = useState();

// 잘못된 방법 ❌
const 항목들 = [...할일목록];
항목들[0].완료 = true;
set할일목록(항목들); // 참조가 같아 변경 감지 안됨

// 올바른 방법 ✅
set할일목록(이전목록 =>
  이전목록.map((항목, 인덱스) =>
    인덱스 === 0 ? {...항목, 완료: true} : 항목
  )
);

3️⃣ "Too many re-renders" 오류

// 잘못된 방법
return <button onClick={핸들러()}>클릭</button> // ❌

// 올바른 방법
return <button onClick={핸들러}>클릭</button> // ✅
return <button onClick={() => 핸들러()}>클릭</button> // ✅

 

✨ 유용한 팁

1️⃣ 초기 상태를 계산하는 함수 사용

무거운 계산이 필요한 초기값은 함수로 전달하면 첫 렌더링에만 실행됨

// 매 렌더링마다 실행 (비효율적)
const [items, setItems] = useState(createExpensiveList()); // ❌

// 초기화 시에만 한 번 실행 (효율적)
const [items, setItems] = useState(() => createExpensiveList()); // ✅

2️⃣ key를 사용한 상태 초기화

컴포넌트에 다른 key를 전달하면 React는 컴포넌트를 새로 생성하고 상태를 초기화함

function 앱() {
  const [사용자ID, 사용자ID변경] = useState(1);
  
  return (
    <>
      <button onClick={() => 사용자ID변경(사용자ID + 1)}>
        다음 사용자
      </button>
      <프로필 key={사용자ID} /> {/* key가 변경되면 상태 초기화 */}
    </>
  );
}

 

 

📄 로렘 입숨 (Lorem Ipsum), 🖼️ 로렘 픽숨 (Lorem Picsum)

로렘 입숨 더미 텍스트 Lorem ipsum dolor sit amet...
로렘 픽숨 더미 이미지 https://picsum.photos/

 

 

📌 position: absolute가 되면?

  • 기본 블록 요소(ex: div, p)는 원래 부모 영역을 가득 채움
  • position: absolute를 주면,
    • 문서 흐름에서 빠지고,
    • 자기 크기를 "내용(content)"에 맞춰 잡기 시작

➡️ inline-block처럼 "너비, 높이 = 내용만큼" 가짐

 

 

🎯 CSS transition-timing-function

이름 느낌 설명 주로 쓰는 곳
ease 부드럽게 기본값. 천천히 → 빠르게 → 천천히 대부분 자연스러운 애니메이션
ease-in 점점 빠르게 처음엔 천천히, 끝날수록 빠르게 들어오는 애니메이션 시작 효과
ease-out 점점 느리게 처음엔 빠르게, 끝날수록 느리게 나가는 애니메이션 마무리 효과
ease-in-out 양쪽 부드럽게 처음 천천히 → 중간 빠르게 → 끝 천천히 자연스럽게 시작 + 끝내기
linear 일정하게 속도가 처음부터 끝까지 똑같음 로딩바, 기계적 움직임

 

 

nodemon

Node.js 서버를 자동으로 재시작해주는 개발 도구

node로 서버 돌리면 매번 수동으로 껐다 켜야 하는데, nodemon은 수정 감지 → 자동 재시작 해줘서 개발할 때 편함

  • 설치
npm install -g nodemon
  • 실행
nodemon app.js

 

 

고차함수

다른 함수를 인자로 받거나 함수를 반환하는 함수

더보기
더보기
더보기
더보기

1. map()

배열의 모든 요소를 변환하여 새 배열 반환

// 숫자 배열의 각 요소를 2배로 만들기
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 객체 배열에서 특정 속성만 추출하기
const users = [
  { id: 1, name: '김철수', age: 25 },
  { id: 2, name: '이영희', age: 30 },
  { id: 3, name: '박민수', age: 28 }
];
const names = users.map(user => user.name);
console.log(names); // ['김철수', '이영희', '박민수']

2. filter()

조건에 맞는 요소만 포함하는 새 배열 반환

// 짝수만 필터링하기
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]

// 특정 나이 이상인 사용자만 필터링하기
const users = [
  { name: '김철수', age: 25 },
  { name: '이영희', age: 30 },
  { name: '박민수', age: 18 }
];
const adults = users.filter(user => user.age >= 20);
console.log(adults); // [{ name: '김철수', age: 25 }, { name: '이영희', age: 30 }]

3. reduce()

배열의 요소를 하나의 값으로 줄이기

// 배열 요소의 합계 구하기
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 15

// 객체 배열의 특정 속성 합계 구하기
const cart = [
  { item: '노트북', price: 1200000 },
  { item: '마우스', price: 35000 },
  { item: '키보드', price: 45000 }
];
const totalPrice = cart.reduce((total, product) => total + product.price, 0);
console.log(totalPrice); // 1280000

4. forEach()

배열의 각 요소에 대해 함수 실행

// 배열의 각 요소 출력하기
const fruits = ['사과', '바나나', '오렌지'];
fruits.forEach(fruit => console.log(fruit));
// 사과
// 바나나
// 오렌지

// 배열 요소의 합계 계산하기 (부수 효과 사용)
let total = 0;
[1, 2, 3, 4].forEach(num => {
  total += num;
});
console.log(total); // 10

5. find()

조건을 만족하는 첫 번째 요소 반환

// 특정 ID를 가진 사용자 찾기
const users = [
  { id: 1, name: '김철수' },
  { id: 2, name: '이영희' },
  { id: 3, name: '박민수' }
];
const user = users.find(user => user.id === 2);
console.log(user); // { id: 2, name: '이영희' }

6. some()

하나라도 조건을 만족하면 true 반환

// 배열에 음수가 있는지 확인
const numbers = [1, 2, 3, -1, 4];
const hasNegative = numbers.some(num => num < 0);
console.log(hasNegative); // true

7. every()

모든 요소가 조건을 만족하면 true 반환

// 모든 사용자가 성인인지 확인
const users = [
  { name: '김철수', age: 25 },
  { name: '이영희', age: 30 },
  { name: '박민수', age: 18 }
];
const allAdults = users.every(user => user.age >= 20);
console.log(allAdults); // false

8. flatMap()

map() 후 결과를 1레벨 평탄화

// 문장을 단어 배열로 변환
const sentences = ['Hello world', 'I love JavaScript'];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words); // ['Hello', 'world', 'I', 'love', 'JavaScript']

9. sort()

배열 요소 정렬

const numbers = [3, 1, 4, 1, 5];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 1, 3, 4, 5]

// 객체 배열 정렬
const users = [
  { name: '김철수', age: 25 },
  { name: '이영희', age: 30 },
  { name: '박민수', age: 18 }
];
users.sort((a, b) => a.age - b.age);
console.log(users); // 나이순으로 정렬됨

10. findIndex()

조건을 만족하는 첫 번째 요소의 인덱스 반환

const fruits = ['사과', '바나나', '오렌지', '포도'];
const index = fruits.findIndex(fruit => fruit === '오렌지');
console.log(index); // 2

11. flat()

중첩 배열을 평탄화

const nestedArray = [1, [2, 3], [4, [5, 6]]];
const flattened = nestedArray.flat(2); // 깊이 2까지 평탄화
console.log(flattened); // [1, 2, 3, 4, 5, 6]

12. reduceRight()

오른쪽에서 왼쪽으로 reduce 실행

const array = ['a', 'b', 'c', 'd'];
const result = array.reduceRight((acc, curr) => acc + curr);
console.log(result); // 'dcba'​

 13. at()

양수 또는 음수 인덱스로 요소에 접근 (ES2022)

const array = [5, 12, 8, 130, 44];
console.log(array.at(2)); // 8
console.log(array.at(-1)); // 44 (마지막 요소)

14. Array.from()

유사 배열 객체나 이터러블을 배열로 반환

// 문자열의 각 문자를 배열로 변환
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']

// 매핑 함수 사용 (두 번째 인자)
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]

15. Array.of()

주어진 인자로 새 배열 생성

console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array.of('a', 'b', 'c')); // ['a', 'b', 'c']

16. entries(), keys(), values()

배열의 반복자 메서드

const array = ['a', 'b', 'c'];

// entries() - [인덱스, 값] 쌍의 반복자 반환
for (const [index, element] of array.entries()) {
  console.log(index, element);
}

// keys() - 인덱스의 반복자 반환
for (const index of array.keys()) {
  console.log(index);
}

// values() - 값의 반복자 반환
for (const element of array.values()) {
  console.log(element);
}

 


실습

입력 창에 텍스트 입력 후 버튼 또는 엔터 키를 눌렀을 때 로컬 스토리지에 데이터 저장 + 저장된 리스트 가져오기
  • InputField.jsx
import React, { useState } from 'react'

const InputField = ({ setData }) => {
  const [inputText, setInputText] = useState('')
  const inputItem = e => {
    setInputText(e.target.value)
  }
  const addItem = () => {
    // 입력 검증
    if (inputText.trim().length < 2) {
      alert(inputText.trim() === '' ? '여행지를 입력하세요' : '2자 이상 입력하세요')
      document.querySelector('input').focus()
      return
    }
    // 로컬 스토리지 저장하기
    setData(prev => {
      const newData = [inputText, ...prev]
      localStorage.setItem('trip', JSON.stringify(newData))
      return newData
    })
    setInputText('')
    document.querySelector('input').focus()
  }

  // Enter key 입력
  const handleKeyUp = e => {
    if (e.key === 'Enter') {
      addItem()
    }
  }

  return (
    <div className="inputField mw">
      <input
        type="text"
        placeholder="가고싶은 여행지를 등록하세요"
        value={inputText}
        onChange={inputItem}
        onKeyUp={handleKeyUp}
      />
      <button onClick={addItem}>ADD</button>
    </div>
  )
}

export default InputField
  • app.jsx
import React, { useState } from 'react'
import Header from './components/Header'
import './index.css'
import InputField from './components/InputField'
import PostList from './components/PostList'
import NoList from './components/NoList'

const App = () => {
  // const list = ['송도', '인천', '서울', '부산']
  const list = JSON.parse(localStorage.getItem('trip')) || []
  const [data, setData] = useState(list)

  return (
    <>
      <Header />
      <InputField setData={setData} />
      {data.length === 0 ? <NoList /> : <PostList data={data} setData={setData} />}
    </>
  )
}

export default App
  • PostList.jsx
import React from 'react'
import List from './List'

const PostList = ({ data, setData }) => {
  return (
    <div className="mw">
      <div className="totalCount">
        <strong>Total</strong>
        <span>{data.length}</span>
      </div>
      <ul className="postList">
        {data.map((item, index) => (
          <List key={index} item={item} setData={setData} />
        ))}
      </ul>
    </div>
  )
}

export default PostList
  • List.jsx
import React from 'react'

const List = ({ item, setData }) => {
  const removeItem = () => {
    setData(prev => {
      const newData = prev.filter(p => p !== item)
      localStorage.setItem('trip', JSON.stringify(newData))
      return newData
    })
  }
  return (
    <li>
      <p>{item}</p>
      <i className="fa-regular fa-trash-can" onClick={removeItem}></i>
    </li>
  )
}

export default List

 

728x90
반응형

'LG 유플러스 유레카 SW > React' 카테고리의 다른 글

[#52] React Router  (2) 2025.04.11
[#51] 가위바위보 게임 (feat. React)  (0) 2025.04.10
[#49] React 설정  (1) 2025.04.08
[#46] React 컴포넌트, props, state  (1) 2025.04.02
[#45] React란  (0) 2025.04.01
'LG 유플러스 유레카 SW/React' 카테고리의 다른 글
  • [#52] React Router
  • [#51] 가위바위보 게임 (feat. React)
  • [#49] React 설정
  • [#46] React 컴포넌트, props, state
nueos
nueos
  • nueos
    nueos 공부 기록
    nueos
  • 전체
    오늘
    어제
    • 분류 전체보기 (191)
      • 해커톤 (1)
      • 네이버 BoostCamp (6)
      • LG 유플러스 유레카 SW (3)
        • React (21)
        • TypeScript (2)
        • JavaScript (2)
        • HTML+CSS (5)
        • Spring (7)
        • Java (6)
        • SQL (2)
        • Algorithm (8)
        • CX (6)
        • Git (2)
        • 프로젝트 (2)
        • 스터디 (9)
        • 과제 (8)
        • 특강 (1)
      • React (3)
      • Next (0)
      • Javascript (2)
      • HTML (2)
      • CSS (9)
      • Algorithm (6)
      • Database (0)
      • OS (13)
      • C++ (24)
      • Python (1)
      • jQuery (1)
      • Django (1)
      • Git (1)
      • 개발 지식 (3)
      • 정보 보안 (22)
      • 포렌식 (1)
      • 암호 (2)
      • 기타 (4)
      • 패스트캠퍼스 FE 프로젝트십 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    디지랩챌린지
    Stack
    스택
    기술로바꾸는세상
    Queue
    heap
    힙
    큐
    exhaustive search
    제주지역혁신플랫폼지능형서비스사업단
    완전 탐색
    제주해커톤
    디지털혁신
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
nueos
[#50] React - useState
상단으로

티스토리툴바