hook의 state를 알아보기 전에 알아볼 js ES6 문법이 하나 있다.
구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식입니다.
const arr = [1, 2, 3];
const [one, two, three] = arr;
console.log(one, two, three); // 1 2 3
const [a, b] = [10, 100];
console.log(a, b); // 10 100
// 단순하게 a, b 각 인덱스에 해당하는 변수에 배열의 각 인덱스에 해당하는 값들을 넣어줬을 뿐이다.
배열은 인덱스값이 맞아야하며, 객체는 키값이 맞아야한다.
import React, { useState } from 'react';
function Example() {
// "count"라는 새 상태 변수를 선언합니다
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
// 하나의 컴포넌트 내에서 여러 개를 사용할 수 있음
function ExampleWithManyStates() {
// 상태 변수를 여러 개 선언했습니다!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
useState는 현재의 state 값과 이 값을 업데이트하는 함수를 쌍으로 제공한다.
우리는 이 함수를 이벤트 핸들러나 다른 곳에서 호출할 수 있어 값을 변경하기 위해 보통 이벤트 핸들러와 같이 사용한다
useState는 인자로 초기 state 값을 하나 받음
const [val, setVal] = useState("");
값을 업데이트하는 함수인 2번 째 인자로 보통 앞에 set을 붙혀 사용한다.
// 1. 일반 업데이트
setCounter(counter + 1);
// 2. 함수형 업데이트
setCounter((counter) => counter + 1);
1번 방법보다 2번 방법이 더 안전하다. 즉, 현재의 state 값을 이용해서 다음 state 값을 변경하고 싶다면 2번의 방법과 같이 함수를 사용해야 리액트가 현재 값을 정확하게 알고 계산할 수 있다.
state를 함수형으로 사용하자는 의미로 리액트는 배치성 업데이트 방법을 사용하는데, 리액트는 변화를 감지해서 한번만 변경을 하는데 이는 효율성을 고려하기 위함입니다.
당연히 잦은 리렌더링(업데이트)는 성능 저하 이슈가 있을 수 있기에 모든 변화를 한번에 감지해서 한번만 리렌더링을 합니다.
1번의 일반 업데이트 방식은 현재 상태값을 확인하고 업데이트 하는게 아닙니다.
반면 2번의 함수형 업데이트 방식은 현재의 상태값을 확인해서 업데이트를 진행합니다.
// case 1
setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);
// case 2
setCounter((counter) => counter + 1);
setCounter((counter) => counter + 1);
setCounter((counter) => counter + 1);
위 코드에서 첫번째 케이스는 counter가 1만 증가하겠지만 두번째 케이스는 3이 증가하게 됩니다.
현재의 상태를 확인하고 변경하는가에 대한 차이라고 생각할 수 있습니다.
리액트에 관련된 내용이기에 원시 값과 참조 값의 차이는 간단하게 설명하고 참조 값의 상태를 변경하는 방법을 알아봅니다.
state는 데이터가 저장되는곳이라고 생각하면 되는데 이때 변화되는 데이터 즉, 바뀌는 데이터가 담긴다고 생각하면 되며 값의 변경은 보통 이벤트 핸들러와 같이 사용한다.
useState를 사용하면 리렌더링이 자동으로 일어나고 html 요소를 찾을 필요도 없고 이벤트리스너를 더해줄 필요도 ui를 업데이트해줄 필요도 없다.
제목 같은 변경이 없는 데이터의 경우 일반적인 변수를 사용하거나 하드 코딩을 해도 전혀 상관없지만 좀 더 부드러운 웹 페이지를 제작하고 싶다면 그리고 자주 변경이 될 수 있는 데이터를 화면에 뿌리고자 한다면 state를 사용하는 것이 좋다는 말이다.
Props는 부모(상위) 컴포넌트로부터 자식(하위) 컴포넌트에 데이터를 보낼 수 있게 해주는 방법으로 리액트를 사용하는 이유 중 하나인 컴포넌트의 분할 및 재활용성을 가장 잘 활용할 수 있게 도와주는 기능입니다.
이때 하위 컴포넌트는 읽기 전용으로 전달받은 props를 사용할 수 있지만 변경할 수는 없다.
//자식 컴포넌트
const Button = ({text="hello"}) => {
return (
<button>{text}</button>
)
}
//부모 컴포넌트
<Button text="apple" />
<Button text="banana" />
<Button />
간단한 예시를 들었지만 스타일 및 많은 로직도 위와 같은 방법으로 일부 내용만 변경해서 변경이 가능하고 부모는 String, Number, Boolean, Function 등 다양한 타입을 전달할 수 있다.
이때 부모의 인자(text)명을 마음대로 정할 수 있고 받는 자식의 인자명(text)도 동일해야하고 위와 같이 부모가 인자를 넘겨주지 않을 경우 자식이 가지고 있는 기본값을 설정할 수 있다 이는 js 문법으로 가능한 방법이다.
State를 이용해서 Props 변경한다면 부모 컴포넌트는 state를 변경하게 되고 이는 전체적인 Rerender를 발생시킵니다.
//부모 컴포넌트
const App = () => {
const [value, setValue] = useState("hi");
const changeVal = () => {setValue("bye")}
return (
<div>
<Button text={value} changeVal={changeVal}/>
<Button text="banana" />
<Button />
</div>
)
}
// 자식 컴포넌트
const Button = ({text="hello", changeVal}) => {
console.log(text, "was render")
return (
<button onClick={changeVal}>{text}</button>
)
}
위의 코드가 처음 렌더링 될때의 결과는 아래와 같습니다.
이때 부모 컴포넌트의 <Button text={value} changeVal={changeVal}/>
에서 단 한개의 컴포넌트만 변경시켜도 아래와 같이 부모 컴포넌트가 전체적으로 다시 렌더링됩니다.
이는 매우 비효율적인 방법으로 단지 한개의 컴포넌트만 변경되었을 뿐이지만 부모는 state의 변경으로 전체적으로 렌더링이 필수적으로 일어나게되는데 이때 변경이 없는 즉, Rerender가 필요없는 컴포넌트를 기억하고 불필요한 렌더링을 막을 수 있는 방법입니다.
const MemorizedButton = memo(Button)
const App = () => {
const [value, setValue] = useState("hi");
const changeVal = () => {setValue("bye")}
return (
<div>
<MemorizedButton text={value} changeVal={changeVal}/>
<MemorizedButton text="banana" />
<MemorizedButton />
</div>
)
}
위와 같이 부모 컴포넌트에서 import 해온 Button 컴포넌트에 리액트의 memo라는 기능을 사용하면 리액트는 Rerender가 필요없는 컴포넌트를 지켜보고 막을 수 있습니다.
첫 렌더링 시에는 모든 컴포넌트가 렌더링이 되며 state를 변경하면 아래와 같이 Rerender가 필요없는 컴포넌트는 memo가 지켜보고 Rerender를 하지 않습니다.
해당 방법은 TypeScript로 대처 가능합니다.
많은 Props를 전달할 때 실수를 방지하기 위해서 사전에 전달할 Props의 타입을 미리 설정할 수 있습니다.
PropsTypes 사용방법
import PropTypes from "prop-types"
const Button = ({text, changeVal}) => {
console.log(text, "was render -> use react memo")
return (
<button onClick={changeVal}>{text}</button>
)
}
Button.propTypes = {
text: PropTypes.string.isRequired,
}
위와 같이 컴포넌트 별로 전달할 타입을 설정할 수 있으며 String, Number, Array, Obj... 등등 모든 타입과 필수적으로 입력해야 하는 값인지 아닌지도 체크할 수 있습니다.
위처럼 isRequired
를 넣어 필수적으로 필요하지만 넣지 않을 경우 경고를 보여줍니다.