자바스크립트로 만들었던 인스타그램 클론을 React, Sass로 바꾸고 동일한 기능을 구현했다. 그 과정에서 마주한 장벽, 해결법, 그리고 뼈가 되고 살이 되는 멘토의 리뷰를 기록하고 정리한다.
댓글, 아티클 등 반복적인 컨텐츠는 자식 컴포넌트로 관리하면 편리하다. 주로 데이터를 map을 돌리는 방식으로 동적인 컨텐츠를 생성하는데, 이때 반드시 각각의 요소에 유니크한 키를 부여해주어야 한다. index를 키로 사용하는 것은 지양하고, author의 id
, new Date.getTime()
등을 활용하면 좋다.
// Unique Key 적용 예시
addComment = (e) => {
e.preventDefault();
const { comment, list } = this.state;
if (comment) {
this.setState({
list: [
...list,
{
id: "thisisyourhyung",
content: comment,
key: `thisisyourhyung-${new Date().getTime()}`,
},
],
comment: "",
});
}
};
<ul className="comments">
{this.state.list.map((comment) => (
<Comment
key={comment.key}
commentKey={comment.key}
commentId={comment.id}
commentContent={comment.content}
onDelete={this.deleteComment}
/>
))}
</ul>
추가로, key는 props.key
로 사용이 불가능하다. key는 컴포넌트의 구성요소로 전달되지 않기 때문이다. key 값을 읽어와야 한다면, 다른 props에 key를 담아 읽어와야 한다.
참고자료 : [React.JS] List와 Key
<form>
과 onSubmit
이벤트를 실행시켜보세요.onKeyPress={(e) => e.key === "Enter" && this.addComment()}
대신에 input을 form 태그로 감싸고 form 태그에 onSubmit 이벤트를 실행시켜주는 방법을 사용할 수 있다. form 태그 사용시 e.preventDefault() 함수를 실행시켜 state 값을 유지될 수 있게 해주어야 한다!
// AS IS
handleSubmit = (e) => {
if (!this.canBeSubmitted()) {
e.preventDefault();
return;
}
};
canBeSubmitted() {
const { id, password } = this.state;
return id.includes("@") > 0 && password.length >= 5;
}
// TO BE
handleSubmit = (e) => {
const { id, password } = this.state;
const isValid = id.includes("@") > 0 && password.length >= 5;
if (!isValid) {
e.preventDefault();
return;
}
};
이렇게 둘을 나눠서 함수를 작성하기 보다는 하나로 합치는 방법도 있습니다. 저희가 해야하는건 입력된 값에 따라서 matchArr만 제대로 관리해주면 return문 아래에서 알아서 render 되기 때문에 좀 더 심플하게 생각하셔도 될 것 같습니다! - by 갓준식
// AS IS
handleChange = (e) => {
this.setState({ searchValue: e.target.value });
};
checkMatch = () => {
const { searchValue, userArr, matchArr } = this.state;
if (searchValue) {
this.setState({
matchArr: userArr.filter((x) => x.id.indexOf(searchValue) !== -1),
});
} else {
this.setState({
matchArr: [],
});
}
return matchArr;
};
// TO BE
checkMatch = (e) => {
const searchValue = e.target.value;
const { userArr } = this.state;
this.setState({
matchArr:
searchValue !== ""
? userArr.filter((el) => el.id.includes(searchValue))
: [],
});
};
className
이 아닌 src
를 토글하는 방식은 어때요?// AS IS
<img
alt="하트"
className={this.state.heartClick ? "heart-on" : "heart-off"}
src="https://s3.ap-northeast-2.amazonaws.com/cdn.wecode.co.kr/bearu/heart.png"
/>
<img
alt="좋아요된하트"
className={this.state.heartClick ? "heart-off" : "heart-on"}
src="img/liked.png"
/>
// TO BE
<img
onClick={this.toggleLike}
alt="하트"
className="comment-like"
src={
heartClick
? "https://s3.ap-northeast-2.amazonaws.com/cdn.wecode.co.kr/bearu/heart.png"
: "img/liked.png"
}
/>
state
로 관리할 필요가 없어요.state는 바뀌는 UI에 대한 데이터를 효율적으로 관리하기 위한 용도다.
return
문 안에서 바로 함수를 실행시켜도 좋아요.// Example
onBlur={() => this.setState({ focusIn: false })}
onFocus={() => this.setState({ focusIn: true })}
const isValid = comment !== "";
this.setState({ isBtnActive: isValid });
comment가 빈 문자열이라면 falsy value이기 때문에 굳이 isBtnActive 상태를 따로 관리할 필요가 없다.
// AS IS
e.key === "Enter" ? this.commentInput() : null
// TO BE
e.key === "Enter" && this.commentInput()
// Example
render() {
const { img, id, nickname } = this.props;
return (
<li className="Searchresult">
<img
className="img-profile"
src={img}
alt={id + "님의 프로필 사진"}
/>
<div className="profile-text">
<span className="userID point-span">{id}</span>
<span className="sub-span">{nickname}</span>
</div>
</li>
);
}
// AS IS
alt={this.props.id + "님의 프로필 사진"}
// TO BE
alt={`${this.props.id}님의 프로필 사진`}