바닐라 JS로 제작했었던 게시판을 리액트로 리팩토링해보기로 했다.
구현 목표는 아래와 같다.
🧱 구현 목표
- 바닐라 JS로 구현했었던 모든 기능에 더해 추가 기능 구현하기
- 게시글 목록 보기
- 게시글 작성
- 게시글 삭제
- 게시글 수정
- 데이터를 직접 만든 서버와 연결해 받아오기
- fetch로 데이터 받아오기
- axios로 데이터 받아오기
파일 구조는 다음과 같이 설정했다.
한페이지라서 App.js 내부에 게시글 목록인 DiscussionList 컴포넌트가 들어가고, 그 내부에는 map으로 반복되어 뿌려질 Discussion 컴포넌트가 들어가게 되어있다. 게시글 입력폼과 state, 이벤트들은 최상위 컴포넌트인 App에 모두 작성하여 필요한 부분은 props로 내려주었다.
❤️ 게시글 목록 불러오기 (조회하기)
App.js
게시글의 목록(데이터)은 등록, 수정, 삭제등으로 변경되는 값이기때문에 useState를 사용했다. 초기값으로는 빈 배열을 주었다.
getData 함수는 fetch API를 이용해 로컬호스트로 열어둔 서버의 엔드포인트에서 데이터를 받아와 json()을 적용해 response 객체에서 promise 객체를 꺼낸다.
useEffect 훅을 사용해 컴포넌트가 처음 렌더링될때만(deps가 빈배열이다) getData를 실행하게 하고 then으로 꺼낸 데이터 배열을 setData로 넣어준다. 추후 등록/수정/삭제 기능이 구현될때에는 데이터에 변경사항이 생길때마다 업데이트 되어야 하기때문에 deps 배열에 필요한 값을 넣어줄것이다.
그리고 DiscussionList 컴포넌트가 map 메서드를 통해 Discussion 컴포넌트를 반복적으로 렌더링되게 하려면 데이터가 필요하기 때문에 props로 data를 내려준다.
DiscussionList.js
props로 받아온 data를 구조분해할당으로 꺼내고, ul 내부에 data.map 을 사용해 Discussion 컴포넌트가 반복적으로 렌더링되게 한다. 각각의 내용이 li마다 들어가야하니 여기에도 props로 data를 전달해주고 map 메서드를 사용할때는 고유한 값인 key값을 사용해야하니 각 data의 id값을 사용한다.
Discussion.js
각 게시글이 될 Discussion 컴포넌트이다. 게시글이 등록된 날짜가 ISO 형식으로 되어있기 때문에 parseDate 함수를 사용해 현지 날짜에 맞는 시간으로 보이도록 했다.
isAnswered는 게시글에 답변이 있다면 특정한 클래스를 붙일 수 있도록 삼항연산자를 사용했다. 해당 span에는 google material icons를 사용했다. index.html에 링크를 미리 삽입해두었다.
avatarUrl도 삼항연산자를 사용했다. 지정된 이미지가 없다면 기본 이미지를 지정해주기 위해서였다.
기본 이미지는 무료 api인 randomuser.me를 사용했다.
데이터의 답변 내용은 문자열 내에 html태그가 포함되어있다. 리액트에서 html 태그를 표현하려면(문자 그대로 나타나지 않게 하려면) dangerouslySetInnerHTML = {{ __html: 넣어줄 내용 }} 을 태그에 추가해야한다고 한다.
dangerouslySetInnerHTML = {{ __html: 넣어줄 내용 }}
일반적으로 JSX에서 자바스크립트를 사용할때처럼 중괄호에 값을 넣으면 아래처럼 html 태그까지 같이 출력된다.
그래서 dangerouslySetInnerHTML을 사용하게 되면 아래처럼 깔끔하게 텍스트만 출력된다.
🧡 게시글 작성하기
🚫 게시글 작성하기를 구현하다보니 과제용 서버를 계속 연동해서 사용할게 아니라 새로 만들 필요성을 느끼게 되었다...🥲
게시글 작성하기 기능 구현부터는 새로 구축한 서버와 함께 내용을 정리하도록 하겠다.
서버
서버는 이 페이지에서 정리한 구조를 기본으로 하고 필요한 코드를 추가했다.
post로 /discussions 에 요청을 하면 addDiscussion이 실행된다. 요청 body에서 새로 입력된 데이터(newData)를 구조분해할당으로 변수에 담고, 데이터의 가장 앞에 추가한다. 이로서 데이터를 최신순으로 보이게 한다. 리턴값은 201 상태코드와 새로 생성되는 데이터를 응답으로 보낸다.
클라이언트
사용하는 state
id의 값은 변수로 지정해주기 위해서 useRef를 사용했다.
input과 state의 핸들러
form submit 이벤트가 발생할때 (onSubmit) 실행되는 이벤트 => addDiscussion
addDiscussion 함수가 실행될때 새로 입력된 내용들을 newData 객체에 담아서 post 요청을 보내는 postData함수를 호출한다.
fetch를 사용해 서버에 새로 등록할 객체를 json 형식으로 변환하고 전송한다.
데이터를 불러올때, id가 가장 최신에 등록된 id와 동일해서는 안됨으로 useEffect에 nextId 값을 설정해주는 코드를 넣었다. 가장 최신글의 id보다 1 크게 설정했다.
그런데 문제가 생겼다. 데이터가 새로 추가될때 다시 리렌더링 시키기 위해서 useEffect의 deps에 data를 배열 요소로 넣어주었는데 갑자기 get 요청이 무한으로 반복되기 시작했다.
useEffect의 deps가 빈 배열이면 컴포넌트가 처음 렌더링될때 한번만 콜백함수가 실행되고, deps 내에 값이 있으면 그 값이 변경될때에도 콜백함수가 실행이 된다. 나는 data가 post 요청이 될때만 변화될거라고 생각했는데, 다시 생각해보니 아닌것같다.
form submit이 실행되면 post 요청은 가고 서버에 데이터가 추가되지만, state인 data에 변경사항이 생기는 것은 아니다. 그러면 왜 계속 콜백함수가 실행되어 get 요청을 하게 되는걸까? 이건 다른분이 useEffect와 관련해 정리해두신 글을 보고 이해했다.
https://darrengwon.tistory.com/275
React Hooks : useEffect에서 데이터 fetching하기
How to fetch data with React Hooks? - RWieruch A tutorial on how to fetch data in React with Hooks from third-party APIs. You will use state and effect hooks for the data request from a real API ... www.robinwieruch.de 위 게시물을 근거하여 작성
darrengwon.tistory.com
state의 값을 설정하는 useEffect의 경우, state를 설정하는것 또한 변화에 해당하기때문에 무한 루프가 발생한다는 것이다.
그래서 나도 방법을 다시 생각해보았다.
useEffect의 콜백함수, 즉 getData는 언제 실행되어야 하는가?
1. 컴포넌트가 처음 렌더링 될때. 맨 처음 서버에서 게시글 목록을 받아서 보여줘야하기 때문이다.
2. 게시글 추가 / 수정 / 삭제가 발생했을때. 리렌더링을 통해 변화된 데이터를 다시 목록에 보여줘야한다.
그렇다면 data가 아니라 다른 방법으로 변화가 일어난다는 것을 나타내야한다.
그래서 일단은 이렇게 해결해보았다.
// ....
const [isChanged, setIsChanged] = useState(false); // 데이터 변화상태
const getData = () => {
return fetch(url)
.then((res) => res.json())
.then((json) => {
setData(json);
nextId.current = json[0].id + 1;
setIsChanged(false);
});
};
// ....
const changeData = () => {
setIsChanged(true);
};
useEffect(() => {
getData();
}, [isChanged]);
return (
// ...
<div className="form__submit">
<input type="submit" value="질문하기" onClick={changeData} />
</div>
// ...
);
데이터가 변화했음을 나타내는 isChanged state를 만들고, boolean 값을 준다. 초기값은 false로 두고 데이터가 변화할때 (게시글 등록/수정/삭제) true로 변하게 하고 getData로 데이터를 불러오고 나면 다시 false 로 수정한다. 그리고 이 값을 useEffect의 deps 배열에 넣어준다. 그러면 getData가 isChanged가 변화할때마다 계속 리렌더링 된다.
수정후에는 이렇게 게시글을 등록했을때 리렌더링이 잘 된다.
'study > React' 카테고리의 다른 글
[React] 바닐라 JS 게시판 리팩토링 (3) - axios (0) | 2023.02.18 |
---|---|
[React] 바닐라 JS 게시판 리팩토링 (2) - Delete, Update (0) | 2023.02.16 |
[React] 미니 트위터 클론코딩(CRUD) (0) | 2023.01.31 |
[React] Fragment (0) | 2023.01.27 |
[React] defalutValue와 value (0) | 2023.01.27 |