리액트 컴포넌트에서 다루는 데이터는 props와 state가 있다. (class용 설명)
함수형 컴포넌트는 클래스형 컴포넌트와 달리 state, 라이프 사이클을 원래 사용하지 못했다.
const [ 변수, 변수설정함수] = useState(초기값)
을 통한 state 구현const 변수 = useRef(초기값)
을 통한 구현함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게해준다.
setState함수
가 state를 변경함+
버튼을 누르면 숫자가 올라가고, -
버튼을 누르면 숫자가 하락하는 카운터 만들기const [number, setNumber] = useState(0);
setNumber(prevNumber => prevNumber + 1);
import React, { useState } from 'react';
function Counter() {
// useState 사용으로 변수 선언 및 초기화
const [number, setNumber] = useState(0);
const onIncrease = () => {
// setNumber 함수를 통한 state 값 변경
setNumber(prevNumber => prevNumber + 1);
}
const onDecrease = () => {
// setNumber 함수를 통한 state 값 변경
setNumber(prevNumber => prevNumber - 1);
}
// onClick 이벤트에 state 변경 이벤트 바인딩
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
const [number, setNumber] = useState(0);
에서 number가 변하는 값인데 const로 선언할 수 있는 이유는 number
이전 변수와 아무 관련없는 새 변수를 만들기 때문이다.setNumber
함수는 이전 값을 가지고, 전해받은 callback 함수로 통해 연산을 하여 새롭게 number를 만든다.React는 성능을 위해
setState()
호출을 단일 업데이트로 한꺼번에 처리
setState에 state객체를 전달하여 호출하는 경우, 한번에 여러개의 setState
를 호출하면 React는 이것을 하나로 합쳐서 처리한다.
이는 JavaScript에서 객체를 "병합"하거나 구성하는 방식과 관련있다. 다음 예시Object.assign
의 작동처럼 전달 된 객체 중 동일한 키의 객체에 대해선 마지막 객체의 값이 우선된다.
me = {name : "Justice"};
you = {name : "Your name"};
we = Object.assign({}, me, you);
// 마지막 으로 name을 정의한 you의 name이 우선시 된다.
console.log(we); // {name : "Your name"}
state
에 대해서도 한번에 여러 번의 setState
호출시 가장 마지막의 변경 내용을 적용한다. 아래 코드에서 count는 +1+2+3 해서 6이 증가하는 것이 아닌 마지막인 +3 만 증가한다.const onClick = () => {
setCount(count + 1);
setCount(count + 2);
setCount(count + 3);
// count = count+ 3 이 실행됨
}
React에서 state는 불변성을 유지해야 한다.
// 기존 people 배열엔 영향 없이 새로운 배열을 생성
const newPeople = people.filter((person) => {
return person.id !== id;
});
setPeople(newPeople);
리랜더링이 필요없는 변수를 담는 방법
useRef 란?
useRef
는 .current
프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환useRef
는 .current
프로퍼티에 변경 가능한 값을 담고 있는 “상자”와 같습니다.특징
.current
프로퍼티를 통한 변수에 접근사용 방법
const counter = useRef(0);
counter.current += 1;
function App() {
const users = [
{
id: 1,
username: 'subin',
email: 'subin@example.com'
},
{
id: 2,
username: 'user1',
email: 'user1@example.com'
},
{
id: 3,
username: 'user2',
email: 'user2@example.com'
}
];
// useRef 를 활용하여 변수 생성 및 초기화
const nextId = useRef(4);
const onCreate = () => {
// 배열에 새로운 항복 추가하는 로직 생략
// 배열에 넣어줄 id 를 위하여 nextId 증가 > immutable 하게 다시 객체를 만드는 것이 아니라 mutable 하게 변화시켜줌
nextId.current += 1;
};
return <UserList users={users} />;
}
setInterval 이나 setTimeout과 같은 함수는 clear 시켜주지 않으면 메모리를 많이 소모한다.
따라서 함수를 구현하고 컴포넌트가 unmount 될 때나 특정 상황해서 clear 해줄 필요가 있다.
예시 작동 방식
const interval = useRef();
interval.current = setInterval(changeHand, 100);
clearInterval(interval.current);
const component = () => {
const interval = useRef();
useEffect(() => {
interval.current = setInterval(실행함수, 1000);
return () => {
clearInterval(interval.current);
}
}, []);
// 코드 생략
생각을 해보자. 잘 생각해보면 현재 useRef 로 쓰고 있는 것은 지역변수로 사용하면 되는거 아닌가? 라는 의문이 떠오르게 될 것이다. useRef 가 했던 행동을 지역변수로 만들어보자.
// nextId 를 지역변수로 선언
let nextId = 4;
function App() {
const users = [
{
id: 1,
username: 'subin',
email: 'subin@example.com'
},
{
id: 2,
username: 'user1',
email: 'user1@example.com'
},
{
id: 3,
username: 'user2',
email: 'user2@example.com'
}
];
// 만약 여기다가 nextId를 선언하게 되면 function이 실행될 떄마다 4가 되기 때문에 function 밖에다가 선언한다
// let nextId=4;
const onCreate = () => {
// nextId 증가
nextId += 1;
};
return <UserList users={users} />;
}
의문점이 생긴대로 똑같이 동작한다. 하지만 문제가 있다.
이렇게 선언한 변수는 전역변수 취급이 되기 때문에 함수의 언마운트가 일어나더라도 사라지지 않는다. 그렇기 때문에 다음과 같은 문제가 발생할 수 있다.
useState와 같은 작동을 하는 훅이다. 이 훅을 사용하면, 컴포넌트에서 컴포넌트 상태 업데이트 로직을 분리할 수 있다.
컴포넌트 상태 업데이트 로직 분리시 얻을 수 있는 장점
상태 업데이트 로직을 컴포넌트 바깥에서 작성할 수 있다.
즉, 다른 파일에서 작성 후 불러와서 사용할 수도 있게 된다.
useReducer의 필수 요소
useReducer 사용방법
//state 초기값 세팅
const initialState = {
winner: ' ',
}
//export로 모듈화, 변수로 지정, 다른 컴포넌트에서 재사용 가능
export const SET_WINNER = 'SET_WINNER';
//reduce 함수
const reducer = (state, action) => { //state를 어떻게 바뀌는지 코드
switch (action.type) {
case SET_WINNER: { //action 이름
//state.winner = action.winner는 안됨, 새로운 action 객체를 만들어서 바뀐 값만 업데이트
return {
// 불변성을 지키면서 업데이트한 새로운 상태를 반환합니다
...state, //spread 문법 = 기존 state를 새롭게 얕은 복사(참조)
winner: action.winner, //바뀌는 부분
}
}
default:
return state;
}
}
//action dispatch(reduce 함수의 해당 action을 실행)
const 컴포넌트 = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const onClick 함수 = useCallback(() => {
dispatch({ type: 'SET_WINNER', winner: 'O'}) //dispatch({ action 객체 })
//}, []); //하위 컴포넌트에 들어가는 함수는 useCallback() 적용
return (
...
);
}
export default 컴포넌트;