리액트 컴포넌트에서 다루는 데이터는 두개로 나뉜다.
바로 props와 state 이다.
props는 부모 컴포넌트가 자식 컴포넌트에게 주는 값.
자식 컴포넌트에서는 props를 받아오기만 하고, 받아온 props를 직접 수정 할 수 는 없다.
반면에 state는 컴포넌트 내부에서 선언하며 내부에서 값을 변경 할 수 있다.
src 디렉토리에 My Name 이라는 컴포넌트를 만들어보자.
import React, { Component } from 'react';
class MyName extends Component {
render() {
return (
<div>
안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
</div>
);
}
}
export default MyName;
자신이 받아온 props값은 this. 키워드를 통하여 조회 할 수 있다.
지금 name이라는 props를 보여주도록 설정해놓았다. 자 이 컴포넌트를 사용해보자.
app.js를 다음과 같이 열어보자
import React, { Component } from 'react';
import MyName from './MyName';
class App extends Component {
render() {
return (
<MyName name="리액트" />
);
}
}
export default App;
import를 통하여 컴포넌트를 불러오고, 렌더링 해보았다. 이렇게 컴포넌트를 만들고나면, 일반 태그를 작성하듯이, 작성해주면 된다. 그리고 props값은 name="리액트" 이런식으로 태그의 속성을 설정해주는 것처럼 해야한다.
작성후 브라우저를 확인하면
"안녕하세요! 제 이름은 리액트 입니다."라고 뜬다.
가끔씩은 실수로 props를 빠트려먹을때가 있다. 혹은, 특정 상황에 props를 일부러 비워야 할 때도 있다.그러한 경우에, props의 기본값을 설정해 줄 수 있는데, 그것이 바로 defaultProps 이다.
import React, { Component } from 'react';
class MyName extends Component {
static defaultProps = {
name: '기본이름'
}
render() {
return (
<div>
안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
</div>
);
}
}
export default MyName;
```
이렇게 하면 만약에 <MyName /> 이런식으로 name 값을 생략해버리면 "기본이름"이 나타자게 될 것이다. 참고로 defaultProps는 다음과 같은 형태로도 설정할 수 있다.
```javascript
import React, { Component } from 'react';
class MyName extends Component {
render() {
return (
<div>
안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
</div>
);
}
}
MyName.defaultProps = {
name: '기본이름'
};
export default MyName;
```
우리가 곧 알아볼 함수형 컴포넌트에서 defaultProps를 설정할 땐 위 방식으로 하면 된다.
---`````
## 3.함수형 컴포넌트
이렇게 단순히 props만 받아와서 보여주기만 하는 컴포넌트의 경우엔 더 간편한 문법으로 작성할 수 있는 방법이 있다. 바로 함수형태로 작성하는 것.
한번 아까 만들었던 MyName이라는 컴포넌트를 다시 작성해보자.
```javascript
import React from 'react';
const MyName = ({ name }) => {
return (
<div>
안녕하세요! 제 이름은 {name} 입니다.
</div>
);
};
export default MyName;
훨씬 간단하다. 함수형 컴포넌트와 클래스형 컴포넌트의 주요 차이점은, 우리가 조만간 배우게 될 state와 LifeCycle이 빠져있다는 점이다.
그래서 컴포넌트 초기 마운트가 아주 미세하게 빠르고, 메모리 자원을 덜 사용한다. 미세한 차이이니 컴포넌트를 무수히 많이 렌더링 하게 되는게 아니라면 성능적으로 큰 차이는 없다.
동적인 데이터를 다룰 땐 어떻게 할까? 바로 state를 사용한다. 이번에 또 다른 컴포넌트를 만들어 보자. Counter라는 파일을 생성해서 다음과 같이 입력하자.
import React, { Component } from 'react';
class Counter extends Component {
state = {
number: 0
}
handleIncrease = () => {
this.setState({
number: this.state.number + 1
});
}
handleDecrease = () => {
this.setState({
number: this.state.number - 1
});
}
render() {
return (
<div>
<h1>카운터</h1>
<div>값: {this.state.number}</div>
<button onClick={this.handleIncrease}>+</button>
<button onClick={this.handleDecrease}>-</button>
</div>
);
}
}
export default Counter;
위에서부터 아래로 쭉 살펴보자. 우선 컴포넌트의 state를 정의할 때는 class fields문법을 사용해서 정의한다.
이 코드는 만약에 class fields를 사용하지 않는다면 다음과 같이 사용한다.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
...
}
우리가 class fields를 사용하는건 편의를 위함이다. 확실히 constructor에 넣는 것 보다는 편해보인다.
위 코드의 constructor에서 super(props) 를 호출 한 이유는, 우리가 컴포넌트를 만들게 되면서, Component를 상속했으며, 우리가 이렇게 constructor를 작성하게 되면 기존의 클래스 생성자를 덮어쓰게 된다. 그렇기에 리액트 컴포넌트가 지니고 있던 생성자를 super를 통하여 미리 실행하고, 그 다음에 우리가 할 작업인 (state 설정)을 해주는 것.
만약 class fields 도 사용하고 constructor도 사용하게 된다면, 어떤 부분이 더욱 늦게 설정될까?
class fields가 먼저 실행되고, 그 다음에 constructor에서 설정된 것이 나온다.
handleIncrease = () => {
this.setState({
number: this.state.number + 1
});
}
handleDecrease = () => {
this.setState({
number: this.state.number - 1
});
}
```
컴포넌트에 메소드를 작성해주었다. 컴포넌트에서 메소드는 다음과 같은 형식으로도 작성 할 수 있는데,
`````javascript
handleIncrease() {
this.setState({
number: this.state.number + 1
});
}
handleDecrease() {
this.setState({
number: this.state.number - 1
});
}
이렇게 하면, 나중에 버튼에서 클릭이벤트가 발생했을때, this가 undefined로 나타나서 제대로 처리되지 않게 됩니다. 이는 함수가 버튼의 클릭이벤트로 전달이 되는 과정에서 "this"와의 연결이 끊겨버리기 때문이다. 이를 고쳐주려면 constructor에서
constructor(props) {
super(props);
this.handleIncrease = this.handleIncrease.bind(this);
this.handleDecrease = this.handleDecrease.bind(this);
}
처럼 해주거나, 우리가 이전에 작성한 코드처럼 아예 화살표 함수 형태로 하면 this가 풀리는 것에 대해서 걱정할 필요 없다.
자 이제 각 메소드에 들어있는 this.setState에 대해서 앏아보자.
state에 있는 값을 바꾸기 위해서는, this.setState를 무조건 거쳐야 한다. 리액트에서는, 이 함수가 호출되면 컴포넌트가 리렌더링 되도록 설계되어있다.
이 함수에 대해 조금 더 자세히 알아보자.
setState는 객체로 전달되는 값만 업데이트 해준다.
지금은 state에 number 값 밖에 없지만 만약 다음과 같이 다른 값이 있다고 가정해보자.
state = {
number: 0,
foo: 'bar'
}
그러면, this.setState({ number:1 };을 하게 된다면, foo는 그대로 남고, number 값만 업데이트!
setState는 객체의 깊숙한 곳까지 확인하지 못한다. 예를 들어서, state가 다음과 같이 설정되어 있다고 가정한다면,
state = {
number: 0,
foo: {
bar: 0,
foobar: 1
}
}
아래와 같이 한다고 해서 foobar 값이 업데이트 되지 않는다.
this.setState({
foo: {
foobar: 2
}
})
이렇게 하게 된다면 그냥 기존의 foo객체가 바뀌어버린다.
{
number: 0,
foo: {
foobar: 2
}
}
그 대신에 위와 같은 상황에서는 이렇게 해주어야 한다.
this.setState({
number: 0,
foo: {
...this.state.foo,
foobar: 2
}
});
... 는 자바스크립트의 전개연산자 이다. 기존의 객체안에 있는 내용을 해당 위치에다가 풀어준다는 의미다. 그 다음에, 우리가 설정하고 싶은 값을 또 넣어주면 해당 값을 덮어쓰게된다.
이러한 작업이 꽤나 귀찮으므로 ,나중에는 immutable.js혹은 immer.js를 사용하여 이 작업을 좀 더 간단하게 해볼 것이다.
setState를 사용하여 값을 업데이트하게 될 때, 기존의 값을 참고하여 값을 업데이트하게될 때, 조금 더 나은 문법으로 할 수 있다.
기존에 작성했던 코드는 이러하다.
this.setState({
number: this.state.number + 1
});
큰 문제는 아니지만 굳이 또 this.state를 조회해야하는데, 이렇게 하면 조금 더 멋진 문법으로 작성 할 수 있다.
this.setState(
(state) => ({
number: state.number
})
);
setState에 updater함수를 만들어서 전달해주었다. 여기서 조금 더 나아가면 이렇게 작성할 수 있다.
this.setState(
({ number }) => ({
number: number + 1
})
);
보면 (state)가 ({number}) 가 되었다. 이건 비구조화 할당 이라는 문법이다.
이 문법은 이런식으로도 사용 할 수 있다.
const { number } = this.state;
결국 코드를 조금 덜 작성하고 싶다면 이렇게도 할 수 있다. 맘에드는 코드 아무거나 사용하면 된다.
const { number } = this.state;
this.setState({
number: number + 1
})
handleIncrease = () => {
const { number } = this.state;
this.setState({
number: number + 1
});
}
handleDecrease = () => {
this.setState(
({ number }) => ({
number: number - 1
})
);
}
render 함수에서 이벤트 설정을 한 부분을 확인해보자.
render() {
return (
<div>
<h1>카운터</h1>
<div>값: {this.state.number}</div>
<button onClick={this.handleIncrease}>+</button>
<button onClick={this.handleDecrease}>-</button>
</div>
);
}
버튼 클릭이되면 준비한 함수가 각각 호출되도록 설정해주었다. 기존에 자바스크립트로 비슷한 작업을 해본사람이라면 아래에 있는 코드가 익숙할 것이다.
<button onclick="alert('hello');">Click Me</button>
html 에서는 onclick 속성에 클릭되면 실행 할 자바스크립트 문자열 형태로 넣어준다. 반면 우리가 작성한 코드를 다시 보자.
<button onClick={this.handleIncrease}>+</button>
여기서 정말 주의해야 할 것이 있다. 리액트에서 이벤트 함수를 설정할 때 html과 다음과 같은 사항이 다르다.
이벤트이름을 설정 할 때 camelCase 로 설정해주어야 한다.
onclick 은 onClick, onmousedown 은 onMouseDown, onchange 는 onChange 이런식으로~
이벤트에 전달해주는 값은 함수 여야 한다. 만약에 onClick={this.handleIncrease()} 이런식으로 하게 된다면, 렌더링을 할 때 마다 해당 함수가 호출이된다.
그렇게 되면 정말 큰 일이 발생한다. 렌더링 -> 함수 호출 -> setState -> 렌더링 -> 함수 호출 -> 무한반복.. 이렇게 되어버리는 것!
자 그러면 이 컴포넌트를 App에서 불러와서 렌더링 해보자
import React, { Component } from 'react';
import Counter from './Counter';
class App extends Component {
render() {
return (
<Counter />
);
}
}
export default App;
버튼들을 눌러보면 숫자가 바뀔 것이다.
정리 :
리액트에서 props를 전달하는 방법과 state를 다루는 방법을 알아보았다.