리액트를 사용하여 인터페이스를 설계할 때 사용자가 볼 수있는 요소는 여러가지 컴포넌트로 구성되어 있다. 컴포넌트를 선언하는 방식은 함수 컴포넌트
와 클래스형 컴포넌트
두 가지가 있다.
import React, { Component } from 'react';
class App extends Component {
render(){
const name = 'react';
return <div className='react'>{name}</div>;
}
}
함수 컴포넌트와 차이점은 state 기능
및 라이프사이클 기능
을 사용할 수 있으며 임의 메서드
를 정의할 수 있다. 함수 컴포넌트는 위의 기능을 사용하지 못하는 단점이 있었지만 리액트 v16.8 이후 Hooks
라는 기능이 도입되면서 해결이 되었다.
클래스형 컴포넌트에서는 render()
가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 한다.
작성한 컴포넌트는 export default '컴포넌트 이름'
을 사용해 다른 파일에서 사용할 수 있게 내보내기가 가능하며 import '컴포넌트 이름' from '파일이 위치한 경로'
를 아용해 불러와 모듈을 사용하는 것이 가능하다.
properties를 줄인 표현. 컴포넌트 속성을 설정할 때 사용하는 요소이다.
props를 렌더링 할 때는 { }
기호로 감싸 주면 되고 지정할 때는 사용하는 곳에서 <'컴포넌트이름' '전달 할 props 이름'= '전달 할 props 값' />
으로 지정하면 된다.
`MyComponent.js`
const MyComponents = props => {
return <div>{props.name}</div>; // props 렌더링
};
export default MyComponent;
`App.js`
import MyComponents from './MyComponent';
const App = () => {
return <MyComponents name='react' />; // props 값 지정
}
export default App;
기본 값을 지정하고 싶을 때 defaultProps
를 이용해 기본 값을 지정할 수도 있다.
`MyComponent.js`
MyComponent.defaultProps ={ // props를 전달하지 않았을때 '기본 값'이 출력된다.
name: '기본 값'
};
`App.js`
const App = () => {
return <MyComponents />; // props를 전달하지 않았기 때문에 defaultProps에 설정 된 기본값이 출력 된다.
}
태그 사이의 내용을 보여주고 싶을 때 children
을 사용하면 된다.
`MyComponent.js`
const MyComponents = props => {
return <div>{props.name}, {props.children}</div>;
// 태그 사이의 내용이 children값을 사용해 보여준다.
};
`App.js`
const App = () => {
return <MyComponents>children</MyComponent>; //태그 사이에 값을 넣으면 children 으로 값이 전달 된다.
}
ES6의 비구조화 할당 문법
을 사용하면 내부 값을 바로 추출 할 수가 있어 props의 값을 더 짧은 코드로 사용할 수 있다.
`MyComponent.js`
const MyComponents = props => {
const {name , children} = props;
// const MyComponents = ({name, children}) - 이렇게 사용해도 동일하게 나온다.
return <div>{name}, {children}</div>;
};
컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 propTypes
를 사용한다. 지정하는 방법은 MongoDB의 스키마를 정의하는 것과 유사하다. 조건에 맞지 않아도 출력은 되지만 콘솔창에 오류 메시지를 출력한다.
`MyComponent.js`
import PropTypes from 'prop-types';
(...)
MyComponent.propTypes = {
name: PropTypes.string, // name 값은 반드시 문자열로 전달.
age: PropTypes.number.isRequired // age 값은 숫자이며 필수로 전달 되어야 하는 값이다.
(...)
defaultProps
와 propTypes
는 클래스 내부에서 지정하는 방법도 있다.
`MyComponent.js`
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class MyComponent extends Component {
static defaultProps = {
name: '이름'
};
static propTypes = {
name: PropTypes.string,
age: PropTypes.number.isRequired
};
render(){
const { name, age, children } = this.props;
return (...);
}
}
export default MyComponent;
state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props는 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 읽기 전용으로만 사용할 수 있다. 값을 바꾸려면 부모 컴포넌트에서 바꾸어 다시 전달해 주어야 하며 전달 받은 값을 직접 바꿀 수 없다.
리액트에는 클래스형 컴포넌트가 지니고 있는 state, 함수 컴포넌트에서 useState함수를 통해 사용하는 state, 이렇게 두 가지 종류의 state가 있다.
클래스형 컴포넌트에 state를 설정할 때는 constructor
메서드를 작성하여 설정하는데 이때 반드시 suepr(props)
를 호출해 주어야 한다. 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 Component
클래스가 지닌 생성자 함수를 호출해 준다.
constructor(props){
super(props);
this.state = {
number: 0 // state의 초기값 설정
};
}
render() {
const { number } = this.state; // this.state로 state 조회
return (
<div>
<h1>{number}</h1>
<button
onClick={() => { // onClick를 통해 클릭 이벤트를 지정
this.setState({number: number+1}); // setState를 사용하여 state에 새로운 값을 지정.
}
>+1</button>
</div>
state의 초기값을 지정하기 위해 constructor 메서드를 선언해주었지만 선언하지 않고도 초기값을 설정할 수 있다.
`기존 방식`
constructor(props){
super(props);
this.state = {
number: 0, // state의 초기값 설정
fixedNumber: 0
};
}
`constructor에서 꺼내기`
state = {
number: 0,
fixedNumber: 0
};
this.setState를 사용하여 state값을 업데이트할 때는 상태가 비동기적으로 업데이트 된다. 다음과 같이 this.setState를 두 번 호출하게 되어도 버튼을 클릭하면 숫자가 2가 아닌 1씩 더해진다.
onClick={() => {
this.setState({number: number+1});
this.setState({number: this.state.number +1});
}}
이에 대한 해결책은 객체 대신에 함수를 인자로 넣어 주는 방법이 있다. 아래와 같이 함수로 인자를 넣어주게 되면 값이 한 번에 2씩 증가 하는것을 볼 수가 있다.
`사용 방법`
this.setState((prevState, props) => {
// prevState는 기존 상태, 업데이트 과정에 props가 필요 없다면 생략 가능
return {
// 업데이트 할 내용
}
})
`실제 코드 적용`
<button onClick={() => {
this.setState(prevState => {
return {
number: prevState.number + 1
}
});
this.setState(prevState => ({
number: prevState.number + 1
}));
}}
// 두 개의 코드는 동일한 기능을 한다
>
+1
</button>
그리고 업데이트 한 뒤 특정 작업을 하고 싶을 때는 setState의 두 번째 파라미터로 콜백(callback)
함수를 등록하여 작업을 처리할 수 있다. 아래와 같이 작성 후 코드를 실행하면 console 화면에 입력한 동작이 실행되는 것을 볼 수가 있다.
<button onClick={() => {
this.setState({number: number+1}, () => {
console.log('setState 호출');
console.log(this.state);
}
);
}}
>
+1
</button>
16.8 버전 이후부터는 useState
라는 함수를 사용하여 함수 컴포넌트에서도 state를 사용할 수 있게 되었는데 이 과정에서 Hooks
이라는 것을 사용하게 된다.
Hooks를 사용하기 전, 배열 비구조화 할당
이 있는데 객체 비구조화 할당과 비슷하고 훨씬 깔끔하게 작성할 수 있다.
`기본 사용`
const array =[1,2];
const one = array[0];
const two = array[1];
`배열 비구조화 할당 사용`
const array = [1, 2];
const [one, two] = array;
// 둘 다 one = 1, two = 2 라는 값이 담긴다
클래스형 컴포넌트에서 state의 초기값은 객체 형태이지만 useState에서는 초기값의 형태는 자유롭다. useState 함수를 호출하면 배열이 반환되는데 첫 번째 원소는 현재 상태, 두 번째 원소는 상태를 바꾸어 주는 함수이다. 그리고 배열 비구조화 할당을 통해 이름을 자유롭게 정해 줄 수 있으며 한 컴포넌트에서 여러 번 사용해도 상관이 없다.
import React, { useState } from 'react'
const Say = () => {
const [message, setMessage] = useState('');
const onClickEnter = () => setMessage('안녕하세요!');
const onClickLeave = () => setMessage('안녕히 가세요!');
const [color, setColor] = useState('black');
return (
<div>
<button onClick={onClickEnter}>입장</button>
<button onClick={onClickLeave}>퇴장</button>
<h1 style={{ color }}>{message}</h1>
<button style={{ color: 'red' }} onClick={() => setColor('red')}>
빨간색
</button>
<button style={{ color: 'green' }} onClick={() => setColor('green')}>
초록색
</button>
<button style={{ color: 'blue' }} onClick={() => setColor('blue')}>
파란색
</button>
</div>
);
};
export default Say;
state의 값을 바꾸어야 할 때는 setState
혹은 useState를 통해 전달받은 세터 함수
를 사용해야 한다. 다음 코드는 잘못된 코드이다.
`클래스형 컴포넌트`
this.state.number = this.state.number+1;
this.state.array = this.array.push(2);
this.state.object.value = 5;
`함수형 컴포넌트`
const [object, setObject] = useState({ a: 1, b: 2 });
object.b = 2;
배열이나 객체를 업데이트 할 때는 사본을 만들고 그 사본에 값을 업데이트한 후, 그 사본의 상대를 setState 혹은 세터 함수를 통해 업데이트 하면 된다. 객체에 대한 사본을 만들 때는 spread 연산자(...)
를 사용하여 처리하고, 배열에 대한 사본을 만들 때는 배열의 내장함수들을 활용하면 된다.
`객체 다루기`
const object = { a: 1, b: 2, c: 3 };
const nextObject = { ...object, b: 4 };
// 사본을 만들어서 b의 값만 덮어 쓰기
`배열 다루기`
const array = [
{id: 1, value: true},
{id: 2, value: true},
{id: 3, value: false}
];
let nextArray = array.concat({ id: 4 }); // 새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item => (item.id === 1 ? {...item, value:false} : item));
// id가 1인 항목의 value를 false로 설정