일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- EnhancedFor
- 한국건설관리시스템
- 대덕인재개발원
- cursor문
- 참조형변수
- 컬렉션프레임워크
- 인터페이스
- 추상메서드
- exception
- 제네릭
- GRANT VIEW
- 환경설정
- 오라클
- 어윈 사용법
- 메소드오버로딩
- 예외처리
- 예외미루기
- 컬렉션 타입
- Java
- abstract
- 객체 비교
- 집합_SET
- NestedFor
- oracle
- 다형성
- 정수형타입
- 자바
- 자동차수리시스템
- 생성자오버로드
- 사용자예외클래스생성
- Today
- Total
거니의 velog
(12) 프론트엔드 프로젝트 : 글쓰기 기능 구현하기 3 본문
3. 리덕스로 글쓰기 상태 관리하기
* 글쓰기 관련 상태를 리덕스로 관리해 줄 차례이다. write 리덕스 모듈을 작성해 주자.
[modules/write.js]
import { createAction, handleActions } from 'redux-actions';
const INITIALIZE = 'write/INITIALIZE'; // 모든 내용 초기화
const CHANGE_FIELD = 'write/CHANGE_FIELD'; // 특정 key 값 바꾸기
export const initialize = createAction(INITIALIZE);
export const changeField = createAction(CHANGE_FIELD, ({ key, value }) => ({
key,
value,
}));
const initialState = {
title: '',
body: '',
tags: [],
};
const write = handleActions(
{
[INITIALIZE]: (state) => initialState, // initialState를 넣으면 초기상태로 바뀜
[CHANGE_FIELD]: (state, { payload: { key, value } }) => ({
...state,
[key]: value, // 특정 key 값을 업데이트
}),
},
initialState,
);
export default write;
* 리듀서를 다 만들었으면 루트 리듀서에 포함시키자.
[modules/index.js]
import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';
import auth, { authSaga } from './auth';
import loading from './loading';
import user, { userSaga } from './user';
import write from './write';
const rootReducer = combineReducers({
auth,
loading,
user,
write,
});
export function* rootSaga() {
yield all([authSaga(), userSaga()]);
}
export default rootReducer;
* 이제 Editor, TagBox, WriteActionButtons 컴포넌트 각각에 대해 컨테이너 컴포넌트를 만들어 줄 것이다. 현재 상황을 보면, 구현해야 할 기능이 그렇게 많지 않고 로직도 간단하기 때문에 컨테이너 컴포넌트를 하나만 만들고 그 안에서 글 작성에 관련한 모든 컴포넌트의 상태 관리를 해 주어도 괜찮을 것 같다. 하지만 이러한 방식은 자칫하면 코드가 방대해져 나중에 유지 보수가 어려워질 수 있기 때문에 각 컴포넌트의 역할에 따라 컨테이너 컴포넌트를 따로 만드는 것을 권장한다.
(1) EditorContainer 만들기
[containers/write/EditorContainer.js]
import React, { useEffect, useCallback } from 'react';
import Editor from '../../components/write/Editor';
import { useSelector, useDispatch } from 'react-redux';
import { changeField, initialize } from '../../modules/write';
const EditorContainer = () => {
const dispatch = useDispatch();
const { title, body } = useSelector(({ write }) => ({
title: write.title,
body: write.body,
}));
const onChangeField = useCallback(
(payload) => dispatch(changeField(payload)),
[dispatch],
);
// 언마운트될 때 초기화
useEffect(() => {
return () => {
dispatch(initialize());
};
}, [dispatch]);
return <Editor onChangeField={onChangeField} title={title} body={body} />;
};
export default EditorContainer;
* 이 컨테이너 컴포넌트에는 title 값과 body 값을 리덕스 스토어에서 불러와 Editor 컴포넌트에 전달해 주었다. 참고로 Quill 에디터는 일반 input이나 textarea가 아니기 때문에 onChange와 value 값을 사용하여 상태를 관리할 수 없다. 따라서 지금은 에디터에서 값이 바뀔 때 리덕스 스토어에 값을 넣는 작업만 하고, 리덕스 스토어의 값이 바뀔 때 에디터의 값이 바뀌게 하는 작업은 추후 포스트 수정 기능을 구현 시 처리할 것이다.
* onChangeField 함수는 useCallback 으로 감싸 주었는데, 이는 Editor 컴포넌트에서 사용할 useEffect 에서 onChangeField를 사용할 것이기 때문이다. onChangeField를 useCallback으로 감싸 주어야만 나중에 Editor에서 사용할 useEffect가 컴포넌트가 화면에 나타났을 때 딱 한 번만 실행되기 때문이다.
* 또한, 사용자가 WritePage에서 벗어날 때는 데이터를 초기화해야 한다. 컴포넌트가 언마운트될 때 useEffect로 INITIALIZE 액션을 발생시켜서 리덕스의 write 관련 상태를 초기화해 준다. 만약 초기화를 하지 않는다면, 포스트 작성 후 다시 글쓰기 페이지에 들어왔을 때 이전에 작성한 내용이 남아 있게 된다.
* 컨테이너 컴포넌트를 다 만들었으면 WritePage에서 기존 Editor를 EditorContainer로 대체시키자.
[pages/WritePage.js]
import React from 'react';
import TagBox from '../components/write/TagBox';
import WriteActionButtons from '../components/write/WriteActionButtons';
import Responsive from '../components/common/Responsive';
import EditorContainer from '../containers/write/EditorContainer';
const WritePage = () => {
return (
<Responsive>
<EditorContainer />
<TagBox />
<WriteActionButtons />
</Responsive>
);
};
export default WritePage;
* 이어서 Editor 컴포넌트를 다음과 같이 수정하자.
[components/write/Editor.js]
import React, { useRef, useEffect } from 'react';
import Quill from 'quill';
import 'quill/dist/quill.bubble.css';
import styled from 'styled-components';
import palette from '../../lib/styles/palette';
import Responsive from '../common/Responsive';
(...)
const Editor = ({ title, onChangeField }) => {
const quillElement = useRef(null); // Quill을 적용할 DivElement를 설정
const quillInstance = useRef(null); // Quill 인스턴스를 설정
useEffect(() => {
quillInstance.current = new Quill(quillElement.current, {
theme: 'bubble',
placeholder: '내용을 작성하세요...',
modules: {
// 더 많은 옵션
// https://quilljs.com/docs/modules/toolbar/ 참고
toolbar: [
[{ header: '1' }, { header: '2' }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['blockquote', 'code-block', 'link', 'image'],
],
},
});
// quill에 text-change 이벤트 핸들러 등록
// 참고: https://quilljs.com/docs/api/#events
const quill = quillInstance.current;
quill.on('text-change', (delta, oldDelta, source) => {
if (source === 'user') {
onChangeField({ key: 'body', value: quill.root.innerHTML });
}
});
}, [onChangeField]);
const onChangeTitle = (e) => {
onChangeField({ key: 'title', value: e.target.value });
};
return (
<EditorBlock>
<TitleInput
placeholder="제목을 입력하세요"
onChange={onChangeTitle}
value={title}
/>
<QuillWrapper>
<div ref={quillElement} />
</QuillWrapper>
</EditorBlock>
);
};
export default Editor;
* 코드를 다 작성했는가? 브라우저에서 에디터 내용과 값을 입력해 보자. 그리고 리덕스 개발자 도구를 확인하여 에디터에 입력한 값이 스토어에도 그대로 반영되었는지 확인해 보자.
(2) TagBoxContainer 만들기
* 이번에는 TagBox를 위한 컨테이너 컴포넌트인 TagBoxContainer를 구현해 보자.
[containers/write/TagBoxContainer.js]
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import TagBox from '../../components/write/TagBox';
import { changeField } from '../../modules/write';
const TagBoxContainer = () => {
const dispatch = useDispatch();
const tags = useSelector((state) => state.write.tags);
const onChangeTags = (nextTags) => {
dispatch(
changeField({
key: 'tags',
value: nextTags,
}),
);
};
return <TagBox onChangeTags={onChangeTags} tags={tags} />;
};
export default TagBoxContainer;
* 다음으로 WritePage 에서 TagBox 를 TagBoxContainer 로 대체하자.
[pages/WritePage.js]
import React from 'react';
import WriteActionButtons from '../components/write/WriteActionButtons';
import Responsive from '../components/common/Responsive';
import EditorContainer from '../containers/write/EditorContainer';
import TagBoxContainer from '../containers/write/TagBoxContainer';
const WritePage = () => {
return (
<Responsive>
<EditorContainer />
<TagBoxContainer />
<WriteActionButtons />
</Responsive>
);
};
export default WritePage;
* 그리고 TagBox 컴포넌트에서 다음과 같이 props로 전달받은 onChangeTags와 tags를 사용하자.
[components/write/TagBox.js]
import React, { useState, useCallback, useEffect } from 'react';
import styled from 'styled-components';
import palette from '../../lib/styles/palette';
(...)
const TagBox = ({ tags, onChangeTags }) => {
const [input, setInput] = useState('');
const [localTags, setLocalTags] = useState([]);
const insertTag = useCallback(
(tag) => {
if (!tag) return; // 공백이라면 추가하지 않음
if (localTags.includes(tag)) return; // 이미 존재한다면 추가하지 않음
const nextTags = [...localTags, tag];
setLocalTags(nextTags);
onChangeTags(nextTags);
},
[localTags, onChangeTags],
);
const onRemove = useCallback(
(tag) => {
const nextTags = localTags.filter((t) => t !== tag);
setLocalTags(nextTags);
onChangeTags(nextTags);
},
[localTags, onChangeTags],
);
const onChange = useCallback((e) => {
setInput(e.target.value);
}, []);
const onSubmit = useCallback(
(e) => {
e.preventDefault();
insertTag(input.trim()); // 앞뒤 공백 없앤 후 등록
setInput(''); // input 초기화
},
[input, insertTag],
);
// tags 값이 바뀔 때
useEffect(() => {
setLocalTags(tags);
}, [tags]);
return (
<TagBoxBlock>
<h4>태그</h4>
<TagForm onSubmit={onSubmit}>
<input
placeholder="태그를 입력하세요"
value={input}
onChange={onChange}
/>
<button type="submit">추가</button>
</TagForm>
<TagList tags={localTags} onRemove={onRemove} />
</TagBoxBlock>
);
};
export default TagBox;
* setLocalTags 를 호출해야 하는 상황에서 onChangeTags 도 함께 호출했다. 또한, props 로 받아 온 tag 가 바뀔 때 setLocalTags 를 호출해 주었다. 이로써 TagBox 컴포넌트 내부에서 상태가 바뀌면 리덕스 스토어에도 반영되고, 리덕스 스토어에 있는 값이 바뀌면 TagBox 컴포넌트 내부의 상태도 바뀌게 된다.
* 컴포넌트를 다 작성했으면 리덕스 개발자 도구를 열고 태그를 추가해 보자. 리덕스 스토어에 바뀐 내용이 잘 반영되는가?
'React > React_프론트엔드 프로젝트' 카테고리의 다른 글
(14) 프론트엔드 프로젝트 : 포스트 조회 기능 구현하기 1 (0) | 2024.02.23 |
---|---|
(13) 프론트엔드 프로젝트 : 글쓰기 기능 구현하기 4 (0) | 2024.02.23 |
(11) 프론트엔드 프로젝트 : 글쓰기 기능 구현하기 2 (0) | 2024.02.23 |
(10) 프론트엔드 프로젝트 : 글쓰기 기능 구현하기 1 (0) | 2024.02.23 |
(9) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 9 (0) | 2024.02.23 |