차이점
props | state |
---|---|
부모 컴포넌트가 자식 컴포넌트에게 전달하는 값 | 자신(컴포넌트)이 스스로 관리하는 상태 값 |
값을 자신(자식 컴포넌트)이 변경할 수 없음 | 값을 자신이 변경할 수 있음 |
공통점
props를 통해 값을 내려 받거나, 자신이 관리하고 있는 state가 변경되면 컴포넌트 렌더링이 발생한다 |
---|
함수 컴포넌트는 함수이기 때문에 호출이 일어나기 전까진 내부 값이 변경되어도 re-rendering 되지 않는다.
이러한 문제를 해결하기 위해서 함수 컴포넌트 내부적으로 상태 값을 관리하는 일이 필요하다.
상태 값을 관리 한다는 것은, 함수 내부적으로 값의 변동이 있을 때, 이를 인지하고 렌더링이 일어날 수 있다는 것을 의미한다.
React의 여러 Hook 중 useState가 그 역할을 한다.
import React, { useState } from 'react';
// state: 값
// setState: state를 변경 시킬 때 사용되는 함수(컴포넌트의 re-rendering을 발생 시킨다.)
const [state, setState] = useState();
원시타입(Reference Type)
setState()로 state값에 바로 접근.
const [number, setNumber] = useState(0);
const [boolean, setBoolean] = useState(true);
const [string, setString] = useState('');
setNumber(number + 1);
setBoolean(!boolean);
setString(string + 'a');
참조 타입(Primtive Type)
Spread Operator 이용.
React는 state값과 setState 실행 이후 state값을 비교할 때 얕은 비교를 하기 때문에, 객체 자체를 복사하여 새로운 주소 값에 저장.
// 객체
const [info, setInfo] = useState({
operator: '+',
count: 0,
show: true
});
setInfo({
...info,
operator: '*',
count: info.count+1,
show: !info.show
});
// 배열(자바스크립트에선 배열도 객체)
const [array, setArray] = useState([]);
setArray([...array, newItems]);
setArray(array.filter(arr => {}));
💡 비동기로 동작하는 setState()
state 업데이트는 비동기적일 수도 있다.
React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 경우도 있다.
단일 처리시, 객체 형태로 병합한다.
const add = () => setNumber(number + 1);
const multiplyBy2 = () => setNumber(number * 2);
const multiplyBy2AndAddBy1 = () => {
multiplyBy2();
add();
};
// 합병원리: number키에 마지막으로 입력된 'number + 1'이 덮어 씌워짐.
Object.assign({number, number: number * 2, number: number + 1});
setState()를 연속적으로 사용해야 할 경우, 함수 형태로 입력 후 사용.
// 함수 형태로 입력
const add = () => setNumber((number) => number + 1);
const multiplyBy2 = () => setNumber((number) => number * 2);
const multiplyBy2AndAddBy1 = () => {
multiplyBy2();
add();
};
💡 React의 데이터는 아래로 흐른다
단일 진실 공급원(Single source of truth): 모든 데이터 요소를 한 곳에서만 제어 또는 편집하도록 조직하는 관례.
하위 컴포넌트에서 각자 관리되고 있는 state를 상위 컴포넌트로 올리고 상태값을 변경할 수 있는 함수를 props로 보내주는 형태.
state 끌어올리기 예시
// Convert.js
import React from "react";
import AgeInput from "./AgeInput";
export default function Convertor() {
return (
<div>
<AgeInput relation={'s'} />
<AgeInput relation={'m'} />
</div>
);
}
// AgeInput.js
import React, { useState } from "react";
export default function AgeInput(props) {
const [age, setAge] = useState(0);
const relationNames = {
's': `Sister's`,
'm': 'My',
};
const relation = relationNames[props.relation];
function handleChange(e) {
setAge(e.target.value);
}
return (
<fieldset>
<legend>{`Enter ${relation} age`}</legend>
<input value={age} onChange={handleChange} />
<p>{props.relation === 's' ? 'My ' : `Sister's `}Age is {props.relation === 's' ? parseInt(age) - 2 : parseInt(age) + 2}</p>
</fieldset>
);
}
// Convert.js
import React, { useState } from "react";
import AgeInput from "./AgeInput";
export default function Convertor() {
const [state, setState] = useState({
relation: 'm',
age: 0,
});
const handleAgeChange = (obj) => {
setState({...state, relation: obj.relation, age: obj.age});
}
function toSisterAge(myAge) {
return myAge + 2;
}
function toMyAge(sisterAge) {
return sisterAge - 2;
}
function tryConvert(age, convert) {
const input = parseInt(age);
if(Number.isNaN(input)) return '';
const output = convert(input);
return output.toString();
}
const { relation, age } = state;
const myAge = relation === 's' ? tryConvert(age, toMyAge) : age;
const sisterAge = relation === 'm' ? tryConvert(age, toSisterAge) : age;
return (
<fieldset>
<legend>Family age</legend>
<AgeInput relation={'s'} age={sisterAge} onAgeChange={handleAgeChange} />
<AgeInput relation={'m'} age={myAge} onAgeChange={handleAgeChange} />
</fieldset>
);
}
// AgeInput.js
import React from "react";
export default function AgeInput(props) {
const relationNames = {
's': `Sister's`,
'm': 'My',
};
function handleChange(e) {
props.onAgeChange({
age: e.target.value,
relation: props.relation,
});
}
const relation = relationNames[props.relation];
return (
<div>
<label for='input'>{relation} age is : </label>
<input className='input' value={props.age} onChange={handleChange} />
</div>
);
}