상태란 계속해서 동적으로 변화하는 특정 상태(=값)
상태(=값)에 따라 각각 다른 동작을 함
React에서는 '컴포넌트Component'라는 구성단위로 이루어진다.
컴포넌트는 구성요소라는 뜻인데, 프로그래밍에 있어 재사용이 가능한 각각의 독립된 모듈을 뜻한다.
(화면을 구성하는 요소들을 각각 만들고 그것을 하나로 합쳐 하나의 페이지가 되는 형식)
이 컴포넌트 또한 작게 쪼개서 작은 부분을 합쳐 하나의 컴포넌트로 만들 수 있다. 하나의 큰 컴포넌트 안에서 여러개의 작은 tree 구조처럼 될 수 있습니다. 큰 요소 안에 작은 요소가 있으니 부모 자식의 관계가 형성될 수있는 것이다.
컴포넌트는 자신의 출력에 다른 컴포넌트를 참조할 수 있습니다. 이는 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있음을 의미합니다.
카운터를 가지고 예시를 들어보겠습니다. : >
src파일 안에 counter.js파일을 만들어 준 후 컴포넌트(=레고의 블록들)을 만든다.
//App.js
import Counter from "./counter";
const App = () => {
return (
<div>
<Counter/>
</div>
);
}
export default App;
//counter.js
import React,{useState} from "react";
const Counter=()=>{
//0에서 출발을 하고
//+버튼을 누르면 1씩 증가함
//-버튼을 누르면 1씩 감소함
//count 상태
const [count,setCount] = useState(0)
const onIncrease = () => {
setCount(count+1);
};
const onDecrease = () => {
setCount(count-1);
};
return(
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
)
}
export default Counter;
import React,{useState} from "react";
리엑트의 기능을 사용하려면 import를 사용해야하고, 그 중 useState메서드 기능을 이용하겠다는 뜻 (콤마로 구문하고 중괄호를 쓴다.)
h2태그에 원래 0을 넣었었는데, 버튼을 누를때마다 바뀌는값이므로 {count}라는 변수를 넣어줌
useState라는 리엑트의 매서드는 배열을 반환하고 배열의 비구조화 할당을 통해서 0번째 인덱스(=파라미터)인 count, 1번째 인덱스(=파라미터)인 setCount 라는 상수로 받아옴
이때, 0번째 인덱스(=파라미터) count는 상태의 값으로 사용이 되고, 1번째인덱스(=파라미터) setCount는 count라는 상태를 변화시키는 상태변화함수로써 사용이 됨.
useState메서드를 호출하면서 넘겨준 인자인 0은 count라는 상태를 만드는데에 초기값으로 사용이 된다.
즉, 0에서 출발 = useState(0)
자바스크립트에서 온클릭함수를이런식으로 작성을 했는데
리엑트에서는 온클릭함수를 onClick={함수명}이렇게 쓴다.(카멜케이스와 중괄호를 쓴다는점!)
화면에 표시된 값이 실시간으로 바뀐다는 것은,
count의 상태가 바뀔때마다 app.js컴포넌트가 counter컴포넌트를 호출하고 반환받은 html을 화면에 표시하는 것이기 때문에 counter라는 컴포넌트(=함수)가 return을 다시한다(=화면에 새로그림) 라고 할 수 있다.
이런것을 리랜더링이라고 하는데 이것을 통해 알 수 있는것은 컴포넌트는 자신이가진 state가 변화하면 화면을 다시그려 리랜더링을 한다(=함수가 다시 호출된다.)
리엑트에서 어떤 컴포넌트가 가진 state가 바뀌면 그 컴포넌트가 리랜더링 된다.
그렇기때문에 우리는 실시간으로 버튼을 눌러서 값을 바꿀 수 있는 counter도 만들 수 있다
지금은 state하나만 만들었지만, 카운터 컴포넌트가 state를 두개를 가져도 상관이 없다. 하지만 state이름(count)와 state상태변화함수(setCount)는 결국 상수를 선언하는 것(const)이기 때문에 이름이 겹치면 안된다
위에 만들었던 App컴퍼런트(상위)에서 counter컴퍼런트(하위)에게 counter의 초기값을 0이 아니라 App컴퍼런트가 전달하는 값으로 사용해달라고 하면?
이때 prop이라는 개념을 이용하면 된다
import React from "react";
import Counter from "./counter";
const App = () => {
return (
<div>
<Counter initialValue={5}/>
</div>
);
}
export default App;
자식컴퍼런트(counter)에게 initialValue라는 이름을 붙여서 값을 전달할 수 있다.
import React,{useState} from "react";
const Counter=(props)=>{
console.log(props);
const [count,setCount] = useState(0)
const onIncrease = () => {
setCount(count+1);
};
const onDecrease = () => {
setCount(count-1);
};
return(
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
);
};
export default Counter;
값을 prop을 통해 전달을 했는데, 전달만한다고 초기값으로 5가 나타나는것이아니다.
counter컴포넌트로가서 부모에게 내려받은 props를 const Counter=(props)=>{}
매개변수를 통해서 받아서 사용할 수 있다.
제대로 받았는지 console.log(props);
콘솔에 출력을 해보면 {initialValue: 5}
객체안에 담겨서 값이 나오는 것을 확인 할 수 있다.
import React from "react";
import Counter from "./counter";
const App = () => {
return (
<div>
<Counter a={1} b={2} c={3} initialValue={5}/>
</div>
);
}
export default App;
객체들을 위와같이 추가를해서 보내도 되는지 콘솔로 확인을 해보면
{a: 1, b: 2, c: 3, initialValue: 5}
props를 몇개를 보내도 객체안에 담아서 나오는 값을 확인 할 수 있다.
코드를 아래와같이 깔끔하게 다시 짤 수 있다
import React from "react";
import Counter from "./counter";
const App = () => {
const counterProps ={
a : 1,
b : 2,
c : 3,
d : 4,
initialValue : 5
}
return (
<div>
<Counter {...counterProps}/>
</div>
);
}
export default App;
자식컴포넌트에게 전달해야 할 props들을 객체로 만들어서 객체를 펼쳐서 전달하는 스프레드연산자{...이름명}
를 통해 전달을 해도 똑같이 자식컴포넌트가 받아서 사용가능하다.
import React,{useState} from "react";
const Counter=(props)=>{
const [count,setCount] = useState(props.initialValue)
const onIncrease = () => {
setCount(count+1);
};
const onDecrease = () => {
setCount(count-1);
};
return(
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
);
};
export default Counter;
props를 counter컴포넌트에서 꺼내쓰려면
props를 매개변수로 받고, 점표기법을통해서 사용할 수 있다(props.initialValue)
또, 객체를 스프레드연산자로 전달을 했고 받는쪽에서는 똑같이 객체로 받는다고 했으니까 비구조화할당을 통해서 받을수도 있다(props객체에서 initialValue값만 꺼내 쓴 것)
const Counter=({initialValue})=>{ const [count,setCount] = useState(initialValue)}
만약, initialValue값을 쓰려고하는데 부모컴포넌트에서 값을 없애버렸다면 undefined오류가 일어난다.
이를 해결하려면 counter컴포넌트에 Counter.defaultProps={
defaultProps : 0
};
defaultProps기능을 사용하면 전달받지 못했던 props의 기본값을 설정해서 에러를 방지할 수 있다.
//OddEvenResult.js
const OddEvenResult = ({count}) => {
return <>{count%2===0? "짝수" : "홀수"}</>
};
export default OddEvenResult;
//Counter.js
import React,{useState} from "react";
import OddEvenResult from "./OddEvenResult";
const Counter=(props)=>{
const [count,setCount] = useState(props.initialValue)
const onIncrease = () => {
setCount(count+1);
};
const onDecrease = () => {
setCount(count-1);
};
return(
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
<OddEvenResult count={count}/>
</div>
);
};
export default Counter;
OddEvenResult.js에 state와 prop을 이용해서 동적인 데이터를 계속 바꿔가면서 props를 전달할 수 있다.
OddEvenResult컴포넌트가 counter의 state가 바뀔때마다 계속 다른값을(짝수/홀수)를 렌더링하는 것을 볼 수있는데, 이렇게 리액트의 컴포넌트는 부모가내려주는 props가 변경이되면 다시렌더를 하게된다.그래서 리랜더가 일어난다.
만약에 {count} props를 받지않고, 혼자 렌더링을하는 자식요소라고 할 때에도
//OddEvenResult.js
const OddEvenResult = () => {
console.log("render");
return <>{10%2===0? "짝수" : "홀수"}</>
};
export default OddEvenResult;
부모요소가 변경이되면(=counter컴포넌트의 state가 바뀌게되면) 자식요소도(=OddEvenResult컴포넌트도) 계속 리랜더가 되는것을 확인할 수 있다.
props로는 뭐든지 다 전달할 수 있기때문에 컴포넌트자체도 다른컴포넌트의 props로 전달할 수 있다.
//Container.js
const Container=({children})=>{
return (
<div style={{margin: 20, padding: 20, border: "1px solid gray"}}>
{children}
</div>
);
};
export default Container;
//App.js
import React from "react";
import Container from "./container";
import Counter from "./counter";
const App = () => {
return (
<Container>
<div>
<Counter/>
</div>
</Container>
);
}
export default App;
<Container>
컴포넌트 사이에 JSX요소(html)을 자식으로 배치하게되면 Container컴포넌트에 {children}이라는 prop으로 전달되게된다.
그래서 {children}에 JSX요소들(<div><Counter/></div>
)이 전달이 되었고 값처럼 활용을 할 수 있다.