눈 깜짝할 새에 더위가 한 풀 꺾이고, 1차 프로젝트도 끝이 나고, 추석연휴도 찾아왔다.
벌써 위코드에서 동기들과 함께 생활을 한 지 한 달이 훨씬 지났다는 소리다 와우!
남은 일주일의 쉬는시간(?)동안 공부 방향을 세 갈래로 나누어 생각해봤는데, 그 중 첫째가 리액트 라이프사이클과 리덕스!
라이프사이클을 정리하기 전에, 프로젝트 때 코드도 다시 돌아볼 겸 state와 props에 대해 다시 간략히 정리해보려고 한다.
React, UI데이터를 관리하는 라이브러리
아래 링크에는 3주차 이틀차 되던 날 내가 리액트에 대해 정리했던 내용에 담겨있다. https://gollumnima.github.io/posts/wecode3_2_TIL_react/ 저 당시엔 state와 props에 대한 이해도가 없는 채로 그냥 알아야 적용하니깐.. 주입식으로 정리했던 것 같다. 물론 지금도 완전히 다 이해한 건 아니지만 프로젝트를 하면서 어떤 느낌인지 감이 왔다!
여튼… 지난번 포스팅에선 "리액트는 라이브러리다"
라고 했는데
정확히 말하자면, "UI 데이터를 관리하는 라이브러리"
다.
이 데이터는 또 두 갈래로 나눠지는데,
- 컴포넌트 내부의 상태값 (state)
- 부모 컴포넌트에서 물려받는 속성값 (props)
리액트는 이러한 UI데이터가 변경이 될때마다 화면을 다시 그리는 속성을 가지고 있다. html의 DOM 구조를 생각해보자면, 우리는 데이터가 변경될 때마다 dom요소를 직접 설정해야만 했다.
예시를 보자!
- html+js에서의 이름 클릭시 트윗 삭제기능
function filter() {
let be_empty = document.getElementsByClassName('contents_wrap')[0]
if (event.target.innerText === 'Doori Kim') {
be_empty.remove();
const sum_doorits = document.getElementsByClassName('sum_doorits')[0]
const total_doorits = doorits_box.children.length
sum_doorits.innerHTML = total_doorits; // 헐 대박..
}
}
저 당시엔 주석으로 헐 대박을 썼지만.. 리액트 코드 보다가 이거 보니 넘나 복잡한 것!
이제 리액트 코드를 보자
removeTweet = () => {
this.setState({
tweetBox: this.state.tweetBox
});
};
<div className="doorits_box">
<ul>
{tweetBox.map((el, idx) => (
<li key={idx}>
<button className="name_btn" onClick={this.removeTweet}>
{el.name}
</button>
<span className="post_time">{el.time.toString()}</span>
<p className="post_txt">{el.message}</p>
</li>
))}
</ul>
</div>
훨씬 더 직관적이다! removeTweet이라는 함수를 만들어서 tweetBox의 state에 변화를 주고, onClick이벤트에 만든 함수를 적용시키면 끝!
어때요? 참 쉽죠?
기억할 것은 리액트는 랜더함수를 통해 화면을 자동으로 갱신시킨다는 점!
State
클래스형 컴포넌트 내부에서 상태를 관리해줄 때 state를 쓴다. (요즘은 함수형 컴포넌트에서도 state를 쓸 수 있게 훅이라는 게 새로 나오긴 했다 ㅎ) constructor 안에서 초기 상태를 this.state 객체 안에 변화할 값들을 넣어주고 초기값을 세팅해준다.
우리가 state를 이용하지 않고, 임의로 데이터값을 바꾼다고 해도 가능은 하다! 하지만 리액트는 바보라서 데이터가 변경되었다는 사실을 모르기 때문에 화면은 그대로 일것이다 ㅠㅠ 그래서 우리는 setState를 써줘야 하는 것!
우리팀 프로젝트의 어려웠던 부분 중 하나인 뉴스 카테고리 선택하는 뉴스홈 화면 코드를 보자.
import React from "react";
import NewsHomeCategory from "./NewsHomeCategory";
import { Link } from "react-router-dom";
import Navbar from "../../Components/Home/Navbar";
import HomeNewsItem from "../../Components/Home/HomeNewsItem";
import "./NewsHome.scss";
class NewsHome extends React.Component {
constructor() {
super();
this.state = {
category: [],
newsList: [],
active_category: 1,
selectedIndex: 0,
indexCount: 0
};
}
componentDidMount() {
fetch("http://13.209.12.87:8000/news/tags", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then(response => {
return response.json();
})
.then(response => {
let categoryList = response.map(el => {
return el.tag;
});
//fetch로 데이터를 받은 후 카테고리 정보를 받아오게끔 setState 함수 설정
this.setState({
category: categoryList
});
});
this.requestNewsList(this.state.active_category);
}
requestNewsList(categoryItem_number) {
fetch(
`http://13.209.12.87:8000/news?offset=1&tag_num=${categoryItem_number}`,
{
method: "GET",
headers: {
"Content-Type": "application/json"
}
}
)
.then(response => {
return response.json();
})
// 해당하는 카테고리의 배열만 뜨게 설정하기
.then(response => {
this.setState({
newsList: response[0].news_data.map(el => {
if (el.tag_id === categoryItem_number) {
return el;
} else {
return "";
}
}),
active_category: categoryItem_number
});
});
}
onChangeCategory = e => {
let categoryItem_number = Number(e.currentTarget.id);
// 백에서 보내준 데이터가 1부터 시작해서 -1을 해줘야 제대로 작동한다
this.setState({
selectedIndex: categoryItem_number - 1
});
this.requestNewsList(categoryItem_number);
};
render() {
return (
<>
<div className="NewsWrap">
<div className="NewsHome-tag">
<div className="NewsHome page_wrapper">
<div className="tag_round">
{this.state.category.map((el, key) => {
let selectedStatus =
key === this.state.selectedIndex ? true : false;
return (
<NewsHomeCategory
key={key}
name={el}
value={key + 1}
onChangeCategory={this.onChangeCategory}
selected={selectedStatus}
/>
);
})}
</div>
<div className="design">
{this.state.newsList.map((item, index) => {
if (item !== "") {
return (
<Link to={`/news/${item.id}`}>
<HomeNewsItem
key={index}
category={item.tag}
title={item.title}
summary={item.content}
thumbnail={item.image_url}
/>
</Link>
);
}
})}
</div>
</div>
</div>
</div>
</>
);
}
}
export default NewsHome;
엄청 복잡한 로직이었다! 일단 클릭한 카테고리에 따라 보이는 화면이 다르게 구현 하는것도 힘들었고, 뉴스와 레시피쪽 데이터 형식이 같진 않아서 fetch함수를 써서 데이터를 가져오고 가공하는데 더 어려움을 겪었던 것 같다.
NewsHome이라는 컴포넌트는 또 NewsHomeCategory라는 컴포넌트와 이어지는데.. 여기서 우리는 props에 대해서 살펴볼 것이다.
props
부모컴포넌트로 부터 물려 받는 속성값을 props라고 한다. 이는 불변객체(immutable)기 때문에 값을 변경하려고 하면 에러가 난다!
그렇다면 NewsHome과 이어지는 NewsHomeCategory의 코드를 살펴보자
살펴보기 전! 내가 처음에 시도했던 방법을 먼저 살펴보자 (원래 일식 카테고리 누르면 노재팬 뜨게 하려고 했는데 팀원들의 만류로 패쓰~) 여튼 카테고리를 눌렀을 때 해당하는 리스트는 뜨지만, 색깔이 바뀌는 효과를 주고 싶었던 나는…
onClinkHandler = e => {
this.props.onChangeCategory(e);
this.setState({
color: "orange",
backgroundColor: "3px soild orange"
})
};
render() {
return (
<div className="check_box">
<div
className="RecipeButton"
style={{ borderBottom: this.state.borderBottom }}
>
<p
id={this.props.value}
onClick={this.onClinkHandler}
style={{ color: this.state.color }}
>
{this.props.name}
</p>
</div>
</div>
);
}
state에 color와 backgroundColor를 넣어놓고 Click 이벤트가 일어날 때 style이 바뀌도록 인라인으로 css효과를 작성했다. 그치만… 결과는!!! 누르는 것마다 색깔이 바뀌게 되어서 대실패~
우리팀 에이스 광훈님의 도움을 받아 다시 작성해본 코드…
import React from "react";
import "./NewsHome.scss";
class NewsHomeCategory extends React.Component {
onClickHandler = e => {
this.props.onChangeCategory(e);
};
render() {
let borderColor = this.props.selected
? "3px solid orange"
: "2px solid #bdbdbd";
let charColor = this.props.selected ? "orange" : "black";
return (
<div className="check_box">
<div
className="NewsButton"
style={{
borderBottom: `${borderColor}`
}}
>
<p
id={this.props.value}
onClick={this.onClickHandler}
style={{
color: `${charColor}`
}}
>
{this.props.name}
</p>
</div>
</div>
);
}
}
export default NewsHomeCategory;
props가 선택 되었는지 안 되었는지를 알기 위해선 NewsHome 컴포넌트 내부에 selected가 true인지 false인지 판별하는 함수를 넣어줬다. 요렇게!
<div className="tag_round">
{this.state.category.map((el, key) => {
let selectedStatus = key === this.state.selectedIndex ? true : false;
return (
<NewsHomeCategory
key={key}
name={el}
value={key + 1}
onChangeCategory={this.onChangeCategory}
selected={selectedStatus}
/>
);
})}
</div>
selectedStatus가 true인 애들만 orange 색으로 바뀌게 하는 코드가 뙇! 이제 증말로 선택한 카테고리만 색깔이 바뀐다 오예!
넘나 잘 먹히는 것…☆ 나 혼자만의 힘으로 한건 아니지만 원하는 결과가 화면에 나오니 이렇게 기쁠수가 없었다 ㅋㅋ
3주 전의 나는 이론으로만 대충 이런거다! 하고 state와 props에 대해 포스팅 했었는데 프로젝트에 적용을 해보니까 쪼꼼 알 것 같다.
결론, 무조건 콘솔 찍어봐라!
this.props가 무엇인지, this.state.newsList에 어떤 데이터가 들어오는지, 진짜로 데이터가 들어오긴 하는건지! 일단 다 찍어봐야 알 수 있다!
Reference
- 실전 리액트 프로그래밍(이재승 저)
- 1차 프로젝트 wooridoori.com 코드