컴포넌트 위주로 간단히 구현한 결과물
깃헙 링크
추가로 구현한 디테일
(1) 전송 버튼을 누르면 userID는 그대로, 내용 입력칸은 초기화된다.
(2) Tweets에서 트위터 기존 글 + 새로 생성되는 글에 대한 총 데이터를 다뤘다. 그리고 화면에 렌더링하기 위해 그의 자식 컴포넌트인 tweet 으로 내려가는 데이터를 Tweets의 형제 컴포넌트도 받아와 filter을 거쳐 특정 user의 게시글만 보이게 했다.
-> 여기서 많은 삽질이 일어난다.. 그건 제일 아래에 나온다...
- 핵심적으로 구현해야 할 기능은 유저 Doyu 🤍의 Twittler에서의 트윗 전송 기능 및 생성된 트윗 추가이다.
- 이 기능 구현을 위해서 어떤 컴포넌트에 어떤 state가 필요하고, 이 state를 어떻게 변화시킬 것인지 고민해 볼 필요가 있다.
File Structure
파일 구조는 다음과 같다. 파일 및 폴더부터 체계적으로 정리하자!
/
├── /React Twittler State Props
│ ├── README.md
│ ├── /public # create-react-app이 만들어낸 파일, yarn/npm start로 실행할 시에 쓰임
│ └── /src # React 컴포넌트가 들어가는 폴더
│ ├── static # dummyData가 들어가는 폴더
│ │ └── dummyData.js
│ ├── Pages # 페이지를 표시하는 컴포넌트가 들어가는 폴더
│ │ ├── About.css
│ │ ├── About.js
│ │ ├── Mypage.css
│ │ ├── Mypage.js
│ │ ├── Tweets.css
│ │ └── Tweets.js
│ ├── Components # 단일 컴포넌트가 들어가는 폴더
│ │ ├── Tweet.css
│ │ └── Tweet.js
│ ├── App.css
│ ├── App.js
│ ├── Footer.js
│ ├── index.js
│ └── Sidebar.js
├ package.json
└ .gitignore
./static/dummyData.js
아직까지는 연습용 미리 만들어 둔 데이터를 사용해본다. src/Pages/MyPage.js, src/Pages/Tweets.js에서는 파일 최상단에 dummyTweets가 import 된다. 아래 트윗 객체로 구성된 배열을 static/dummyData.js에서 확인 가능하다.
{
id: 1,
username: 'kimcoding',
picture: `https://randomuser.me/api/portraits/women/1.jpg`,
content: '모든 국민은 인간으로서의 ... ',
createdAt: '2022-02-24T16:17:47.000Z',
updatedAt: '2022-02-24T16:17:47.000Z'
}
상세 컴포넌트 구현하기
컴포넌트 그리기
- App 에서 왼쪽은 Sidebar 컴포넌트, 오른쪽은 페이지 전환을 위한 공간이다.
- Sidebar에는 3개의 버튼이 있다. (home, mypage, about)
- 전환될 페이지는 총 3개이며 각각 footer가 달려있다.
(위 그림에서 '페이지가 전환되는 부분'에 구현된다. '페이지가 전환되는 부분'의 하위요소가 아니다. 편의상 그려놓음) - home과 mypage 페이지에는 새롭게 tweet 게시글이 생성되면 글 목록이 업데이트 되어야 한다.
* 필요한 부분만 정리하여 부분 포스팅
Tweets 컴포넌트 (Tweet.js) 구현 체크사항 - (Tweet 컴포넌트의 부모 컴포넌트)
기능 : 글 쓰는 양식에 글을 쓰고 '전송' 버튼을 누르면 기존 데이터에 신규 데이터가 추가된다.
1. 변경되는 값은 입력폼에 입력되는 '유저 아이디'와 '글 내용' + '전체 데이터'이다.
state를 선언 및 할당해준다.
// 🟣 TODO : useState를 react로 부터 import
import React, { useState } from "react";
import Footer from '../Footer';
import Tweet from '../Components/Tweet';
import './Tweets.css';
import dummyTweets from '../static/dummyData';
const Tweets = () => {
const [username, setUsername] = useState("Doyu 🤍"); // 유저 아이디 (Doyu가 디폴트 값)
const [msg, setMsg] = useState(""); // 내용 (초기값은 없다)
const [tweets, setTweets] = useState(dummyTweets); // 전체 데이터 (초기 값에 기존 데이터들 넣어줌)
2.이벤트가 발생하면 state 값이 변경되도록 함수를 연결한다.
입력칸
<div className="tweetForm__input">
<input // userID 입력하는 칸
type="text"
placeholder="your username here.."
className="tweetForm__input--username"
onChange = {handleChangeUser} // handleChangeUser 함수 연결
value={username} // 🟣 value에 state 할당 (state 값이 바로 해당 칸에 표시)
></input>
<textarea // 트윗 내용 입력하는 칸
placeholder="무슨 생각을 하고 계신가요?"
className="tweetForm__input--message"
onChange = {handleChangeMsg} // handleChangeMsg 함수 연결
value={msg} // 🟣 value에 state 할당 (state 값이 바로 해당 칸에 표시)
></textarea>
</div>
const handleChangeUser = (event) => {
setUsername(event.target.value); // Tweet input 엘리먼트에 입력 시 작동하는 함수
}; // state 값을 입력폼에 입력한 내용으로 변경
const handleChangeMsg = (event) => {
setMsg(event.target.value); // Tweet textarea 엘리먼트에 입력 시 작동하는 함수
}; // state 값을 입력폼에 입력한 내용으로 변경
전송 버튼
<button
className= "tweetForm__submitButton"
onClick={handleButtonClick}>전송</button> // 버튼을 누르면 handleButtonClick 함수 호출
spread syntax를 이용해 새로운 객체 1개와 기존의 객체 배열을 풀어 쓴 형태를 전달 인자로 넣어준다.
const newTweet = [tweet, ...tweets]
setTweets(newTweet)
//또는
setTweets([tweet, ...tweets])
const handleButtonClick = (event) => { // Tweet button 엘리먼트 클릭시 작동하는 함수
const tweet = {
id: tweets.length + 1,
username: username, // 유저ID state 값
picture: 'https://~~~~~~', // 이미지는 내가 원하는 유저 프로필 사진을 넣음
content: msg, // 내용 state 값
createdAt: new Date().toISOString();
};
setTweets([tweet, ...tweets]); // 🟣 spread syntax를 활용해 배열 형태의 state 값 변경
setMsg('') // 🟣 '전송' 버튼을 누르면 내용의 초기값이 ''가 된다.
setUsername('Doyu 🤍') // 🟣 '전송' 버튼을 누르면 ID의 초기값이 'Doyu 🤍'가 된다.
dummyTweets.unshift(tweet)
};
3. state 값 tweets(객체 형태의 개별 글의 정보가 모두 나열된 배열 데이터) 을 자식인 Tweet 컴포넌트에게 내려 보낸다.
- 여기서 tweets은 전체 트윗글의 정보를 담은 배열 형태다. : [{최신 글1}, {최신 글2}, {.....}, .....]
- 자식 컴포넌트에게 내려보내려면 map 함수를 이용해 하나씩 보내야 한다.
- map의 전달 인자 tweet은 {최신 글1}의 형태로 대략 {id: 6, userID: 도유, 내용: '날씨 좋다!'}의 객체 형태다.
<ul className="tweets">
{tweets.map((tweet) =>
<Tweet key={tweet.id} tweet ={tweet} />
)}
</ul>
Tweet 컴포넌트 (Tweet.js) 구현 체크사항 - (Tweets 컴포넌트의 자식 컴포넌트)
기능 :
부모 Tweets 컴포넌트에 포함되어 있는 Tweet 컴포넌트는 부모로부터 전체 데이터를 받아와서 유저 아이디, 생성 날짜, 내용이 포함된 전체 글 목록을 화면에 렌더링 해준다.
dummyTweets가 아닌 다른 데이터가 props로 전달되어도 트윗 정보를 정확하게 표시해야 한다.
import React from 'react';
import './Tweet.css';
const Tweet = ({ tweet }) => {
{/* 🟣 props, 여기서는 tweet를 받아온다. tweet은 객체의 형태로 전달된다. */}
const parsedDate = new Date(tweet.createdAt).toLocaleDateString('ko-kr');
return (
<li className="tweet" id={tweet.id}>
<div className="tweet__profile">
<img src={tweet.picture} />
.
.
.
깨달음의 메모
spread syntax와 map 함수.. 그리고 prop
- map의 전달 인자 tweet은 {최신 글1}의 형태로 대략 {id: 6, userID: 도유, 내용: '날씨 좋다!'}의 객체 형태다.
// 참고한 예시코드
function Say() {
const props = {name: "walli"};
return <Hello {...props} />;
}
const Hello = (props) => <div>{props.name}</div>
- 위의 코드를 참고하여 아래와 같이 구현했는데 에러가 났다.
- 해결 : 위와 같이 쓰려면 자식의 전달 인자가 tweet이어야 한다.
{tweets.map((tweet) => // 예) 배열 tweets의 0번째 요소 tweet은 객체
<Tweet key={tweet.id} {...tweet} /> // 🟣 객체 리터럴로 {} 안에 {ID: 6, username..} 등 바로 입력
)}
// 생략
const Tweet = ( tweet ) => { // 🟣 {tweet}이 아니라 tweet
return (
<img src={tweet.picture} />
ECMAScript 2018에서 객체 리터럴이 추가되었다.
const obj = { a: "A", b: "B" };
console.log({ c: "C", ...obj }); // {c: "C", a: "A", b: "B"}
- 다른 방법 : {} 안에 객체를 전달한다. 자식의 전달 인자가 {tweet} 이다.
{tweets.map((tweet) => // 예) 배열 tweets의 0번째 요소 tweet은 객체
<Tweet key={tweet.id} tweet ={tweet} /> // 🟣 {{최신 글의 정보}} 이런 식
)}
const Tweet = ({ tweet }) => { // 🟣 그대로 {tweet} 형태로 자식한테 전달해야 한다.
return (
<img src={tweet.picture} />
어찌 됐든 객체 하나는 그냥 넘겨줄 수 있구나~ 를 깨달았다.
state hook 구현 과정
(1) dummyTweets.unshift(tweet); 를 이용해서 state hook을 쓰지 않고 시도해 보았다. (실패)
* 결과 :
- 되긴 된다. 하지만 렌더링이 바로 되지 않기 때문에 state hook을 쓴 '내용 입력' 함수를 쓸 때에서야 글이 추가가 된다.
- 다시 말해 unshift를 쓰면 dummyTweets에 배열 요소 하나가 추가가 되지만, 렌더링이 바로 되지 않기 때문에 변화가 없다.
- 내용을 입력하고 '전송' 버튼을 눌렀는데도 동작하지 않고 다음 게시글을 생성하기 위해 input창에 입력을 할 때(state hook 연결되어 있음) 그때서야 렌더링이 되어 글이 추가가 된다.
(2) state hook을 걸고 unshift를 사용해 보았다. (실패)
tweets.unshift(tweet)
setTweets(tweets)
* 결과 :
- state hook 의 기본 원칙은 state를 직접적으로 변경하지 않기이다. 하지만 state인 tweets를 직접 건드리고 있다!
- 또한 렌더링 조건은 주소값의 변경 여부로 결정된다. 하지만 이 경우 주소값이 바뀌지 않아 렌더링이 되지 않는다.
* 해결 방법 (권장 X):
- slice()를 이용해 아예 새로운 배열로 복사를 하고 unshift를 해서 새 tweet을 넣고 그렇게 변화된 새 배열을 setTweets 함수 인자로 전달하면 된다. 근데 굳이...?
(3) spread syntax를 이용해 새로운 객체 1개와 기존의 객체 배열을 풀어 쓴 형태를 전달 인자로 넣어준다. (성공!)
const newTweet = [tweet, ...tweets]
setTweets(newTweet)
//또는
setTweets([tweet, ...tweets])
삽질의 여정
형제 컴포넌트 사이 데이터 공유하기
문제 상황:
- Tweets 와 Mypage는 형제 컴포넌트다.
- Tweets에서 사용된 state hook을 이용해 갱신된 트위터 글의 전체 목록 데이터가 Mypage 에도 필요했다.
- 따라서 이와 관련된 고민을 한 포스팅이다.
// Tweets 컴포넌트에서 state 값인 tweets (트위터 글 목록 전체 데이터)를 자식 Tweet에게 내보내고 있다.
// Mypage 컴포넌트도 이 데이터가 필요하다!!!!
<ul className="tweets">
{tweets.map((tweet) =>
<Tweet key={tweet.id} tweet ={tweet} />
)}
</ul>
해결 시도 방법 (1) : 데이터를 아예 공통된 부모 컴포넌트로 옮긴다.
- 원칙상 내가 원하는 데이터는 공통의 부모 컴포넌트인 이곳(App.js)에 위치해있어야만 한다.
- 하지만, 보이는 것처럼 routering으로 링크가 연결되어 있으며 각 컴포넌트의 위치를 구축해놓은 상태이다.
- 만약, 원하는 데이터를 이곳에 옮기게 된다면 트위터의 입력폼과 전송버튼, state hook까지 데리고 와야 한다.
- <Route path="/" element={이곳!!!} /> 에 실험 삼아 Tweets 컴포넌트의 내용을 다 옮겨와 보았지만 작동하지 않는다.
- 인터넷을 찾아봐도 이런 식의 응용은 없었다.
해결 시도 방법 (2) : Context API, 리덕스를 사용하여 전역적으로 값을 관리한다.
참고했던 자료들
- https://andwinter.tistory.com/327
- https://velog.io/@lemuel0525/React-context-API%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-state-%EC%A0%84%EC%86%A1
-> 이 부분에 매달려 2시간 정도 할애했던 것 같다.
- 이것 저것 3~4가지 방법을 시도해보았으나 결론적으로 해당 방법은 이미 제작되어서 상당 부분 완성이 된 컴포넌트 간의 관계와 함수, 데이터 구조 상 쉽게 말하면 우겨넣어지는 느낌이었다.
- bottom-up 방식에서 이미 컴포넌트와 데이터 흐름을 구상했을 때, 이미 routes, state hook 등으로 긴밀하게 구축된 관계를 엎고 굳이 특정 데이터를 전역으로 뺄 이유는 그닥 없어 보였다. (차라리 새로 만드는 게 나음)
- 나에게는 효율적 시간관리도 중요했기 때문에 (변명인가...?) 다음 과제에 전역 데이터 관리를 활용해보기로 했다.
해결 시도 방법 (3) : 다소 허무한 방법으로 해결했다.
- 어찌됐든 Mypage에서 필요한 것은 새로 갱신된 데이터 배열이다.
- 그럼 state hook에서 트위터 전체 게시물 데이터 갱신을 위해 선언한 tweets(아래 그림 4번째 줄)의 초기값으로 써먹었던 dummyTweets 배열 데이터를 활용하면 되는 것 아닌가...?
- 아래 그림 마지막에서 2번째 줄처럼 그냥... 신규 데이트를 unshift를 이용해 제일 앞으로 넣어 주었다.
- 끝.... 하하
- + 다른 장점은 다른 컴포넌트에 이동했다가 다시 본 페이지에 오면 리렌더링 되어 신규로 생성된 트윗 글이 없어지는 현상이 있었는데 이 방법을 쓰고 나서는 그대로 있었다. ㅎㅎ 이득이다.
시간 가는 지도 모르고 코딩하다 보니 벌써 새벽 1시였다. 금요일이라서 가능했던 취침 시간 ㅎㅎ
넘 재밌쟈나!!!!
'FE > React' 카테고리의 다른 글
[React] 데이터 흐름 & State 끌어올리기 (Lifting State Up) (0) | 2023.04.01 |
---|---|
[React] Goodbye create-react-app! (0) | 2023.03.31 |
[React] React 데이터 흐름 (0) | 2023.03.24 |
[React] React의 이벤트 처리(Event handling) : state 다양한 응용 (0) | 2023.03.24 |
[React] React에서 데이터를 다루는 Props & State (0) | 2023.03.24 |