추석 연휴 덕분에 쉬면서 내가 약했던 부분을 그나마 되돌아가서 복습할 수 있는 시간이 생겼다.
오늘은 1차 팀 프로젝트를 진행하면서도 제일 어렵다고 느꼈던 Array.map() 그리고 부모,자식 컴포넌트를 활용한 state,props 그리고 마지막으로 filter메소드를 복습해보는 시간을 가져볼려고한다.
일단 부모-자식 간의 컴포넌트 활용을 하기 위해서는 먼저 React의 Life cycle에 대해서 이해할 필요성이 있다.
React Lifecycle 기본순서
아래는 위의 코드실행 결과가 콘솔에 찍히는 순서이다.
자 이제 몬스터 과제로 다시 돌아가보자,
위 주소를 호출하여 데이터 로딩을 처리해주세요!
- componentDidMount()
- fetch
- setState (monsters 에 저장)
class Monsters extends Component {
state = {
monsters: [],
userInput: "",
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users", {
method: "GET",
})
.then((res) => res.json())
.then((res) => {
this.setState({ monsters: res });
});
}
render() {
return (
<div className="Monsters">
<h1>컴포넌트 재사용 연습!</h1>
<CardList monsters=this.state.monsters />
</div>
);
}
}
export default Monsters;
위에서 공부 한 기본적인 라이프 사이클에 의하면 먼저 state에 있는 빈 monsters 배열을 render 해올 것이다. 그 다음에 componentDidMount에 들어가서 주어진 주소를 fetch 해온 다음 response 를 setState를 통해 monsters의 배열을 채워줄 것이다. 그 다음 CardList라는 자식 컴포넌트에 monsters라는 state를 props로 넘겨줄 것이다.
Card 컴포넌트를 import 한 뒤, props로 내려받은 데이터에
map 함수를 호출해 각각 다른 데이터를 가진 Card 컴포넌트들을 리턴해주세요!
Card 컴포넌트에서 필요로 하는 데이터는 id, name, email 입니다.
class CardList extends Component {
render() {
const { monsters } = this.props;
return (
<div className="card-list">
{monsters.map((monster) => {
return (
<Card
key={monster.id}
id={monster.id}
cardName={monster.name}
cardEmail={monster.email}
/>
);
})}
</div>
);
}
}
export default CardList;
위에서 Monsters.js 부모 컴포넌트로부터 전달 받은 monsters 배열값을 map함수를 이용해서 호출하여 각각 다른 데이터를 가진 Card 컴포넌트를 리턴 할 수 있다. Card 컴포넌트로부터 필요한 데이터는 id, name, email 뿐이므로 map을 돌리면 인자값을 monster로 지정해주고 id={monster.id} 같은 방식을 통해 배열에서 원하는 데이터만 출력될 수 있도록 만들 수 있다. 여기서 map 함수를 이용할 때 index 값을 따로 지정해줘야만 하는데 Card의 data 안에 이미 index값들이 id로 지정되어 있어서 따로 index 값을 정의할 필요가 없다.
Card 컴포넌트 구조
image tag => < src=이미지주소 alt=""><h2>Name</h2> <p>Email</p>
Card 컴포넌트에서 props로 받아야하는 데이터는 id, name, email 입니다.
props에서 해당하는 키값들을 추출하여 위와 같은 구조로 만들어주세요!
이미지주소: https://robohash.org/${숫자}?set=set2&size=180x180
카드마다 다른 이미지를 보여주기 위해
위 주소의 숫자 부분을 props로 내려받은 id로 대체하셔야 합니다.
Name 과 Email 도 마찬가지입니다.
class Card extends Component {
render() {
const { id, cardName, cardEmail } = this.props;
return (
<div className="card-container">
<img src={`https://robohash.org/${id}?set=set2&size=180x180`} alt="" />
<h2>{cardName}</h2>
<p>{cardEmail}</p>
</div>
);
}
}
export default Card;
Card 컴포넌트로 부터 id, name, email을 받기 위해서 props를 활용하여 내려받습니다. 이 때 props의 키값들은 CardList에서 Card 컴포넌트에 지정해줬던 키값 이름과 동일해야만 합니다. 또한 id 의 img를 가져오는 과정에서 배열이 맵을 통해 돌면서 지속적으로 url주소가 바껴야함으로 templete literal 을 활용하여 id값만 유동적으로 바뀔수 있게 설정해줘야 한다.
- SearchBox 컴포넌트에 정의한 handleChange 메소드를 넘겨주고,
호출 시 인자로 들어오는 이벤트객체(e)를 활용해 userInput 으로 setState.
- 필터링 로직 구현 (filter 메소드 활용)
여기서 비교 대상은 monster 객체의 name 값입니다.
소문자로 바꾼 monster.name 값과 userInput값을 비교.
filter 메소드가 반환하는 값을 변수에 저장 후 return 문 안에 CardList에 props로 전달
SearchBox .JS
class SearchBox extends Component {
render() {
return (
<input
className="search"
type="search"
placeholder="Search..."
onChange={this.props.handleChange}
/>
);
}
}
export default SearchBox;
Monsters.JS
class Monsters extends Component {
state = {
monsters: [],
userInput: "",
};
// 데이터 로딩
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users", {
method: "GET",
})
.then((res) => res.json())
.then((res) => {
this.setState({ monsters: res });
});
}
// SearchBox 에 props로 넘겨줄 handleChange 메소드 정의
handleChange = (e) => {
const { value } = e.target;
this.setState({
userInput: value,
});
};
render() {
const filterMonster = this.state.monsters.filter((monster) =>
monster.name.toLowerCase().includes(this.state.userInput.toLowerCase())
);
return (
<div className="Monsters">
<h1>컴포넌트 재사용 연습!</h1>
<SearchBox handleChange={this.handleChange} />
<CardList monsters={filterMonster} />
</div>
);
}
}
export default Monsters;
SearchBox의 value 값을 handleChange를 통해 userInput로 지정해줘서 searchbox에 유저가 글자를 쓸 때마다 정보를 searchbox 컴포넌트에서 props로 받아온다. 받아온 글자 정보를 filter를 통해 monster.name 의 소문자와 userInput 소문자 정보가 동일할 때만 지정해둔 CardList 컴포넌트로 넘어가서 해당 정보를 map 함수를 통해 돌린다. 결국 유저가 쓴 정보와 동일한 값을 갖고 있는 배열만 출력되게 되고 그것이 바로 search 기능이다.