componentDidMount()
: 컴포넌트가 생성되고 첫 렌더링이 완료된 후 호출됩니다
componentDidUpdate()
: 컴포넌트가 업데이트된 후 호출됩니다
componentWillUnmount()
: 컴포넌트가 제거되기 직전에 호출됩니다
생명주기 메서드에 대한 이해를 돕기 위해
먼저 리액트로 간단한 카운터 기능을 만들어보겠습니다
<div id="root"></div>
<script type="text/babel">
// Counter 컴포넌트를 만들어서 상태 관리하기
class Counter extends React.Component {
constructor(props) {
super(props);
// 컴포넌트가 생성될 때(생성자함수) 상태를 가지고 시작
this.state = {
number: 0,
}
}
// 첫 렌더링이 완료된 후 10이라는 상태를 가지게 됩니다
componentDidMount() {
// setState가 render 함수를 재호출합니다
this.setState({ number: 0 })
}
render() {
return (
<div>
<h2>{this.state.number}</h2>
<button>+</button>
<button>-</button>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Counter />
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
</script>
사용자가 아무런 조작을 하지 않았음에도 위 코드는 화면 렌더링이 2번 실행된 것입니다
별 반 차이가 없어보이지만 생성자 함수가 state 데이터를 가지고 시작하는 것보다
초기 렌더링을 끝낸 뒤 생성주기 메서드를 사용해서 데이터를 불러오는 쪽을 더 추천합니다
사용자에게 먼저 html 틀을 보여주고, 데이터는 따로 받아오는 편이 더 나은 사용성을 제공하기 때문입니다
componentDidMount() {
// setState가 render 함수를 재호출합니다
setTimeout(() => {
this.setState({ number: 10 })
}, 3000)
}
render() {
return (
<div>
<h2>{this.state.number === 0 ? "로딩중" : this.state.number}</h2>
<button>+</button>
<button>-</button>
</div>
)
}
}
↑ 앞으로는 이러한 형태의 코드를 자주 보게 될 것...
아래는 완성코드입니다
<body>
<div id="root"></div>
<script type="text/babel">
// Counter 컴포넌트를 만들어서 상태 관리하기
class Counter extends React.Component {
constructor(props) {
super(props);
// 컴포넌트가 생성될 때(생성자함수) 상태를 가지고 시작
this.state = {
number: 0,
isLoading: true,
}
this.increment = this.increment.bind(this)
}
// 첫 렌더링이 완료된 후 10이라는 상태를 가지게 됩니다
componentDidMount() {
// setState가 render 함수를 재호출합니다
setTimeout(() => {
this.setState({ number: 0, isLoading: false })
}, 1000)
}
// 상태가 바뀌면 호출될 메서드
componentDidUpdate() {
console.log(this.state.number)
}
increment() {
this.setState({
number: this.state.number + 1,
})
}
decrement = () => {
this.setState({
number: this.state.number - 1,
})
}
render() {
if (this.state.isLoading) return <div>로딩중</div>
return (
<div>
<h2>{this.state.isLoading? "로딩중" : this.state.number}</h2>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Counter />
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
</script>
</body>
렌더 메서드 실행 → ComponentDidMount
로 데이터를 불러오면 렌더 메서드 재실행 →
클릭 이벤트 실행 → ComponentDidUpdate
로 상태 변경 → 렌더 메서드 재실행
*이후 생명주기 메서드 안에는 axios와 같은 비동기 요청 코드가 담길 예정입니다
리액트는 기본적으로 싱글 페이지 어플리케이션의 구현을 위해 제작된 라이브러리입니다
이를 위해 하나의 html 파일에서, 각 컴포넌트의 상태(데이터)가 바뀐 부분만을 찾아 화면을 다시 그리는 방식으로 구조가 짜여있습니다
그런데 <form>
태그의 submit과 같은 기능은 데이터를 전송할 때 새로고침 현상을 발생시키고
또 자바스크립트의 코드는 페이지의 새로고침이 발생하면 모든 이벤트의 실행 결과가 초기화된다는 문제가 있습니다
아래 예제 코드를 통해 해당 이슈에 대해 파악해보겠습니다
<body>
<div id="root"></div>
<script type="text/babel">
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
value: "",
word: "",
}
}
// onChange는 엘리먼트의 상태가 바뀔 때마다 발동된다는 것을 이용합니다
handleChange = (e) => {
this.setState({ value: e.target.value })
}
handleSubmit = (e) => {
// SPA 구현을 위해 form 태그의 새로고침을 막아두고 시작해야 합니다, 자주 사용하게 될 패턴
e.preventDefault()
this.setState({
word: this.state.value,
value: "",
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" onChange={this.handleChange} value={this.state.value} />
<button type="submit">전송</button>
<div>{this.state.word}</div>
</form>
)
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
board: [
{ id: 1, subject: 'web7722' },
{ id: 1, subject: 'web7733' },
{ id: 1, subject: 'web7744' },
{ id: 1, subject: 'web7755' },
]
}
}
getList(board) {
return board.map((val, key)=><li key={key}>{val.subject}</li>)
}
render() {
return <ul>{this.getList(this.state.board)}</ul>
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Counter />
<Form />
<List />
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
</script>
</body>
map()
매서드만큼은 사용법을 제대로 파악하고 있어야 합니다<form>
태그의 input value를 상태로 관리하고,위 화면에서 Props : web7722는 어떤 과정을 거쳐서 전달되었을까요?
<body>
<div id="root"></div>
<script type="text/babel">
class C extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
Props: {this.props.text}
</div>
}
}
class B extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<C text={this.props.id} />
</div>
}
}
class A extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<B id={this.props.name} />
</div>
}
}
class Props extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<A name="web7722" />
</div>
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Counter />
<Form />
<List />
<Props />
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
</script>
</body>
<A name="web7722" />
→ <B id={this.props.name} />
<B id={this.props.name} />
→ <C text={this.props.id} />
<C text={this.props.id} />
→ <div>Props: {this.props.text}</div>
컴포넌트명과 변수명을 바꿔가며 자주자주 연습합시다
*컴포넌트의 innerHTML은 children이라는 속성명으로 고정되어 전달됩니다
Component
는 엘리먼트의 모음이며 하나의 상태만을 가질 수 있습니다. 상태에 따라 엘리먼트가 바뀔 수 있습니다
Props
는 데이터(값)의 전달이며, 방향은 자식 컴포넌트를 향해 한방향으로만 이어집니다
State(상태)
는 엘리먼트에 표현할 데이터를 모아주는 변수를 뜻합니다
State 끌어올리기
를 사용하려면 부모 컴포넌트와 자식 컴포넌트를 구분할 수 있어야 합니다,
그리고 함수가 값이라는 사실도 알아야 합니다
(부모 컴포넌트에서 본인의 상태를 바꾸는 함수를 미리 구현 → 자식 컴포넌트는 전달받은 함수를 실행)
생명주기
는 상태 변화에 따라 실행할 추상 메서드를 뜻합니다
(this.setState()
를 사용할 경우에 componentDidUpdate()
와 render()
가 실행되는 상황)
이벤트
는 이벤트를 등록하는 방법에 대해서만큼은 정확히 인지해야합니다,
this 바인딩에 대해 공부하는 것도 자바스크립트 작동원리 이해에 큰 도움이 됩니다
조건부 렌더링
~ JSX 문법({}
)과 삼항연산자를 통한 컴포넌트 호출
List 렌더링
~ 같은 엘리먼트를 여러번 사용할 때, 배열[]
에 담아서 사용해도 출력된다는 것을 알아둡시다
<ul>
{[<li>hello</li>,<li>world</li>]}
</ul>
map()
메서드를 사용해서 배열 내용을 변경할 수 있으면 OK입니다
const arr = [{a: 10},{b: 20}]
// map 함수로 그리기
[<li>a: 10</li>, <li>b: 10</li>]