[Mini Project] Note-App
๐ฉ๐ป๐ป Github Repository https://github.com/sw2377/note-app
์์ ๊ธฐ๊ฐ : 2024. 01. 09 ~ 2024. 01. 16
ํ๋ก ํธ์๋ ์ฑ์ฉ ๊ณผ์ ๋ก ์ํํ Note-App ๊ตฌํ ๊ณผ์ (๊ตฌํ ์ค ์๋ฌ์ฌํญ & ๊ณ ๋ฏผํ ๋ถ๋ถ)์ ์ ๋ฆฌํ ๊ธ์ ๋๋ค.
๐ ์๋ฆฌ์ฆ ์ด์ด๋ณด๊ธฐ
Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (1)
ํ์ฌ๊ธ : Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (2)
Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (3)
๐ ๋ชฉ์ฐจ
1. lexical text editor ์ฌ์ฉํ๊ธฐ
2. ํท๊ฐ๋ ธ๋ ๋ถ๋ถ 1, props type ์ ์ ๋ฐฉ๋ฒ
3. ํท๊ฐ๋ ธ๋ ๋ถ๋ถ 2, JSX๋ฅผ ๋ฐํํ์ง ์๋ React component์ return null
4. ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ ์๋์ ์ฅ ๊ธฐ๋ฅ
1. lexical text editor ์ฌ์ฉํ๊ธฐ
์๊ตฌ์ฌํญ์ ์ฌ์ฉํ๋ผ๊ณ ์ธ๊ธ๋์ด ์์๋ lexical text editor๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค.
๐ ๊ณต์ ํํ์ด์ง : https://lexical.dev/
์ธ๋ถ ์๋ฃ๋ ๋ณ๋ก ์๊ณ , ๊ณต์ ํํ์ด์ง๋ ์น์ ํ์ง ์๋ค๋ ๋๋์ ๋ฐ์๋๋ฐ ์๋ง ์ฒ์ ์ฌ์ฉํด๋ด์ ๋์ฑ ๊ทธ๋ฐ ๋๋์ด ๋ค์๋ ๊ฒ ๊ฐ๋ค. ์ฌ์ค ์ด๋ ๊ฒ ๊ณต์๋ฌธ์๋ง ๋ณด๊ณ ์ฌ์ฉํ๋๊ฒ ๊ฑฐ์ ์ฒ์์ด๋ผ ์ต์ํ์ง ์์ ์ด๋ ต๊ฒ ๋๊ปด์ก๋ค. ์ฌํผ lexical ์ฌ์ฉ๋ฒ์ ์ตํ๊ณ , ์ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ์๊ฐ์ ์ ๋ง ๋ง์ด ๋ณด๋๋ค.
์ฒซ ์ธํ ์ ๊ณต์๋ฌธ์์ ๐ React์์ ์ธํ ํ๋ ๋ฐฉ๋ฒ์ ์ฐธ๊ณ ํด์ ์์ฑํ๊ณ , ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ์๋ํ๋๋ก ์๋์ด ์ ๋์๋ค.
import {useState, useEffect} from 'react';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
const theme = {
// Theme styling goes here
paragraph: 'editor-paragraph',
}
const OnChangePlugin = ({ onChange }) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({editorState}) => {
onChange(editorState);
});
}, [editor, onChange]);
}
const Editor = () => {
const [editorState, setEditorState] = useState(""); // ํ์ฌ ์๋ํฐ์ ๊ธ์ด ์ ์ฅ
function onChange(editorState) {
const editorStateJSON = editorState.toJSON();
setEditorState(JSON.stringify(editorStateJSON));
}
function onError(error) {
console.log(error)
}
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
};
return (
<div className="editorWrapper">
<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin
contentEditable={<ContentEditable className='contentEditable' />}
placeholder={<div className='placeholder'>Enter some text...</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<OnChangePlugin onChange={onChange} />
</LexicalComposer>
</div>
)
}
export default Editor
์ฒ์์๋ lexical Editor์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ด ํท๊ฐ๋ ธ์๋๋ฐ, ๊ณ์ ๋ณด๋ค๋ณด๋ ์ด๋ค ๋ฐฉ์์ธ์ง ์ดํด๊ฐ ์ข ๋๋๋ฏํ๋ค.
์ปค์คํ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค๋ฉด LexicalComposer ๋ด์์ ์ฌ์ฉํ ์ ์๊ณ , ํด๋น ํ๋ฌ๊ทธ์ธ ๋ด์์ editor๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
export function MyLexicalPlugin() {
const [editor] = useLexicalComposerContext();
console.log("editor: ", editor)
}
<LexicalComposer initialConfig={initialConfig}>
// ...
<MyLexicalPlugin>
</LexicalComposer>
1-1) lexical editor์์ initial state ๊ฐ์ ธ์ค๊ธฐ
// Before
const initialEditorState = note.content
const initialConfig = {
namespace: "MyEditor",
theme,
onError,
editorState: initialEditorState
};
// โฌ๏ธ ๋ณ๊ฒฝ //
// After
// InitPlugin : note์ ์ฒซ ์ง์
์ ์ด๊ธฐ๊ฐ ์ธํ
export const InitPlugin = ({ note }) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const editorState = editor.parseEditorState(note.content);
editor.setEditorState(editorState);
}, [note, editor]);
return null;
};
์ฒ์์๋ initialConfig์ initialEditorState๋ฅผ ๋ฃ์ผ๋ฉด ์ด๊ธฐ๊ฐ์ผ๋ก ๋ค์ด์จ๋ค๊ณ ํด์ ๋ฃ์์๋๋ฐ, ์ด๋ ๊ฒ ํ๋ ์ ํํ Note๊ฐ ๋ฐ๋์ด๋ ์๋ํฐ์ ์ด๊ธฐ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ๐ https://lexical.dev/docs/concepts/editor-state ์ด ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ฉด,
“Note that Lexical uses initialConfig.editorState only once (when it's being initialized) and passing different value later won't be reflected in editor. See "Update state" below for proper ways of updating editor state.”
“Lexical์ initialConfig.editorState๋ฅผ ํ ๋ฒ๋ง ์ฌ์ฉํฉ๋๋ค(์ด๊ธฐํ๋ ๋๋ง ์ฌ์ฉ) ๊ทธ ์ดํ์๋ ๋ค๋ฅธ ๊ฐ ์ ๋ฌ์ด ํธ์ง๊ธฐ์ ๋ฐ์๋์ง ์์ต๋๋ค. ์ ์ ํ ๋ฐฉ๋ฒ์ ๋์ค์ "์ํ ์ ๋ฐ์ดํธ" ์น์ ์์ ์ค๋ช ํ ๋๋ก ํธ์ง๊ธฐ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ฒ์ ๋๋ค.”
์ด๋ฌํ ๋ถ๋ถ์ด ์๋๋ฐ, ์ด ๋๋ฌธ์ ์ด๊ธฐ์ ํ๋ฒ ์ฌ์ฉ๋ ์ด๊ธฐ๊ฐ์ด Note๋ฅผ ๋ฐ๊พธ์ด๋ ์๋ก์ด ๋ ธํธ์ ๊ฐ์ผ๋ก ๋ค์ด์ค์ง ์๋๊ฒ ๊ฐ์๋ค. (ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๋ฉด ์ ํํ ๋ ธํธ์ ๋ด์ฉ์ด ๋ค์ด์๋ค.)
๊ทธ๋์ initialEditorState ๋์ InitPlugin์ ๋ง๋ค์ด์ note์ ์ฒซ ์ง์ ์ ๊ธฐ์กด์ ๊ฐ์ง๊ณ ์๋ note.content๋ก ์ด๊ธฐ๊ฐ์ ์ธํ ํด์ฃผ๋๋ก ํ์๋๋ ์ ๋๋ก ๋์์ด ๋์๋ค.
1-2) lexical editor์์ Node ๊ฐ์ ธ์ค๊ธฐ (์ ๋ชฉ๊ณผ ๋ด์ฉ ๊ตฌ๋ถ)
1. DOM์์ ๊ฐ์ ธ์ค๊ธฐ
// DOM์์ ๊ฐ์ ธ์ค๊ธฐ 1
const CustomStylePlugin = () => {
const [editor] = useLexicalComposerContext();
editor.getEditorState().read(() => {
editor.getRootElement()?.getElementsByTagName("span")[0].classList.add("editor-title");
editor.getRootElement()?.getElementsByTagName("span")[1]?.classList.add("editor-desc");
})
}
ํ ์คํธ ์๋ํฐ์ ๊ธ์ ์ ๋ ฅํ๋ฉด pํ๊ทธ ์์ span ํ๊ทธ๋ก ๋ค์ด๊ฐ๋ค. ๊ทธ๋์ ์ฒ์์๋ ์ด๋ ๊ฒ editor์์ span ํ๊ทธ๋ฅผ ์ฐพ์ ์ฒซ๋ฒ์งธ span ์์๋ ์ ๋ชฉ์ผ๋ก, ๋๋ฒ์งธ span ์์๋ ๋ด์ฉ์ผ๋ก className์ ์ง์ ํ๋ ๋ฐฉ์์ ๋ ์ฌ๋ ธ์๋ค. ๋ด์ฉ์ ๊ตฌ๋ถํ ์ด์ ๋ NoteList์ ๋ด์ฉ์ด ๋ค์ด๊ฐ์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
// DOM์์ ๊ฐ์ ธ์ค๊ธฐ 2
const theme = {
paragraph: 'editor-paragraph',
}
const paragraphElement = document.querySelector(".editor-paragraph");
const title = paragraphElement?.childNodes[0]?.textContent;
๋๋ ์ด๋ฐ ์์ผ๋ก ์ฒ์์ theme์์ ์ค์ ํ p ํ๊ทธ์ className์ ๊ฐ์ง๊ณ ์ค๋ ๋ฐฉ๋ฒ๋ ์์๋ค.
2. textNode๋ฅผ ๊ฐ์ ธ์ค๊ธฐ → ์ต์ข ์ ์ผ๋ก ์ฌ์ฉํ ๋ฐฉ๋ฒ
ํ์ง๋ง ๊ฒฐ๊ตญ ๋ง์ง๋ง์ผ๋ก ๋ด๊ฐ ์ฌ์ฉํ ๋ฐฉ๋ฒ์, editorState์์ textNode๋ฅผ ์ฐพ๋ ๋ฐฉ๋ฒ์ด์๋ค. Lexical Editor๋ Node๋ก ์ด๋ฃจ์ด์ ธ ์๊ธฐ ๋๋ฌธ์ Node๋ฅผ ์ฐพ๋ ๋ฐฉ์์ผ๋ก ํ๋ ์ข ๋ ๊ฐํธํ๊ฒ ์ฒซ ๋ฒ์งธ์ ๋ ๋ฒ์งธ textNode๋ฅผ ์ฐพ์ ์ ์์๋ค.
// Editor.tsx
// editorState์ ์ ๋ชฉ์ ์ฒซ๋ฒ์งธ textNode
const textNodes = Array.from(editorState._nodeMap.values()).find(
node => node.__type === "text",
);
const titleNode = textNodes?.__text ?? null;
setTitleNode(titleNode);
// NoteList.tsx
// parsedEditorState์ ๋ด์ฉ์ index 1dml textNode
const textNode: string = parsedEditorState.root?.children[0].children.filter(
(node: any) => node.type === "text",
)[1]?.text;
์ค์ ์ฌ์ฉ์ titleNode๋ Editor ์ปดํฌ๋ํธ์์, contentNode๋ NoteList ์ปดํฌ๋ํธ์์ ๊ฐ๊ฐ ๋ฐ๋ก ์ฌ์ฉํ์๋ค.
๐ Array Method, filter์ find์ ์ฐจ์ด
์ textNode๋ฅผ ๊ฐ์ ธ์ค๊ธฐ์ ์ด์ด์ง๋ ๋ด์ฉ์ธ๋ฐ, ์ ๋ชฉ์ ๊ฐ์ ธ์ฌ ๋๋ Array ๋ฉ์๋ ์ค find๋ฅผ, ๋ด์ฉ์ ๊ฐ์ ธ์ฌ ๋๋ filter๋ฅผ ์ฌ์ฉํ๋ค. ํ์ ๋ฐฐ์ด์ ์์๋ฅผ ์ฐพ์ ๋ find๋ณด๋ค filter๋ฅผ ๋ ์์ฃผ ์ฌ์ฉํ์๋๋ฐ, find๋ก๋ ๋ฐฐ์ด์ ์์๋ฅผ ์ฐพ์ ์ ์๋ค.
// filter
// ๋ฐฐ์ด์ ์์ ์ค์์ ์ฃผ์ด์ง ํจ์์ ๋ฐํ๊ฐ์ด true์ธ ์์๋ค๋ก ์ด๋ฃจ์ด์ง ์๋ก์ด ๋ฐฐ์ด์ ๋ฐํํ๋ค.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]
// find
// ๊ฒ์ฌ๋ฅผ ์ํด ์ ๋ฌ๋ฐ์ ํจ์๋ฅผ ๋ง์กฑํ๋ ๋ฐฐ์ด ์์์ ์ฒซ๋ฒ์งธ ๊ฐ์ ๋ฐํํ๋ค.
// ๋ง์กฑํ๋ ๊ฐ์ด ์์ผ๋ฉด undefined๋ฅผ ๋ฐํํ๋ค.
const numbers = [1, 3, 5, 7, 2, 9, 11];
const firstEvenNumber = numbers.find((number) => number % 2 === 0);
console.log(firstEvenNumber); // 2
- filter๋ ๋ฐฐ์ด์ ์์์ ์ ๊ณต๋ ํจ์๋ฅผ ์ ์ฉํ์ฌ, ํจ์๊ฐ true๋ฅผ ๋ฐํํ๋ ๊ฒฝ์ฐ ์ ๋ฐฐ์ด์ ์์๋ฅผ ํฌํจํ๋ฉฐ, ์๋ก์ด ๋ฐฐ์ด์ ๋ฐํํ๋ค. ํน์ ์กฐ๊ฑด์ ์ถฉ์กฑํ๋ ์ฌ๋ฌ ์์๋ฅผ ๊ฒ์ํด์ผ ํ๋ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ผ๋ก filter๊ฐ ๋ ์ ํฉํ๋ค.
- find๋ ์ผ์นํ๋ ์ฒซ ๋ฒ์งธ ์์๋ฅผ ์ฐพ์ ๋๊น์ง ๋ฐฐ์ด์ ๋ฐ๋ณตํ ๋ค์ ํด๋น ์์๋ฅผ ๋ฐํํ๋ค. ์ผ์น ํญ๋ชฉ์ด ๋ฐ๊ฒฌ๋๋ฉด ๋ฐ๋ณต์ ์ค์งํ๋ฏ๋ก ๋จ์ผ ์์๋ฅผ ๊ฒ์ํ ๋ ์ ์ฌ์ ์ผ๋ก ๋ ํจ์จ์ ์ด๋ค.
Big O ํ๊ธฐ๋ฒ์ ๊ด์ ์์ 'filter'์ 'find'๋ ๋ชจ๋ O(n)์ ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ๋๋ค๊ณ ํ๋ค. (n์ ๋ฐฐ์ด์ ๊ธธ์ด) ๊ทธ๋ฌ๋ ์์ ์์์ ์ค์ ์ฑ๋ฅ์ ์ฌ์ฉ ์ฌ๋ก์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์๋ค.
lexical ์๋ํฐ๋ฅผ ์ฌ์ฉํ๋ฉฐ ์๊ฐ์ ์ ๋ง ๋ง์ด ๋ณด๋๋ค. ์ต์ํ์ง ์๊ณ , ์๋ฃ๊ฐ ๋ณ๋ก ์๋ (์์ด๋ ์ ๋ถ ์์ด๋ก ๋์ด ์๋) ์๋ก์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ด๋ ค์ ์ง๋ง ๊ทธ๋๋ ์ด๋ฒ ๊ธฐํ๋ฅผ ํตํด ๊ณต์๋ฌธ์๋ฅผ ์ฝ๊ณ , ์ฌ์ฉ๋ฒ์ ์ตํ๊ณ , ํ์ ์ ์ ํ์ผ์ ์ฐพ์๋ณด๋ฉฐ ์ด๋ ํ ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ์ฝ๊ณ ์ฌ์ฉ๋ฒ์ ํ์ ํด์ ์ค์ ๋ก ๋์ ํ๋ก์ ํธ์ ์ ์ฉํ๋์ง ์กฐ๊ธ์ด๋๋ง ์๊ฒ๋์๊ณ , ์์ ๊ฐ์ด ์๊ฒผ๋ค. ๋ค์๋ฒ์ ๋ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค๋ฉด ํ๋ก์ ํธ์ ๋์ ํ๋๋ฐ ์ง๊ธ๋ณด๋ค๋ ๋ ํค๋งฌ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
๋ ์ด์์์ ์ก๊ณ ๊ธฐ๋ฅ๋ง ๋น ๋ฅด๊ฒ ๊ตฌํํ๋ ค ๋น ๋ฅด๊ฒ ๋ง๋ React + JavaScript ์กฐํฉ์ ์ฝ๋๋ฅผ ๋ณธ๋ ์ด๊ธฐ์ ๋ง๋ค์๋ ํ๋ก์ ํธ๋ก ์ฎ๊ธฐ๋ ์์ ์ ์ ์ถ ์ดํ์ ๋จ๊ธฐ๊ณ ํ๊ธฐ ์์ํ๋ค. ๋ ์ด์์๊ณผ ์๋ํฐ๋ฅผ ํฌํจํ ๋๋ถ๋ถ์ ๊ธฐ๋ฅ ๊ตฌํ์ด ์์ ํ๋ก์ ํธ์์๋ ์๋ฃ๋ ์ํ์๊ธฐ ๋๋ฌธ์ ๋ง์์ด ์กฐ๊ธ ํธํด์ก๋ค.
์ฝ๋๋ฅผ ์ฎ๊ธฐ๋ฉฐ TypeScript๋ก ํฌํ ๋ ํ๊ณ , styled components๋ฅผ ์ฌ์ฉํด ์คํ์ผ๋ง๋ ํ์๋ค. ๊ธฐ์กด์ ๊ตฌํ์๋ง ์ด์ ์ ๋์ด ์๋ง์ผ๋ก ์์ฑํ๋ ์ฝ๋๋ ์ ๋ฆฌํ๊ณ , ํด๋ ๊ตฌ์กฐ๋ ๋ค์ ์งฐ๋ค. ๋ค์ ์ง ํด๋๊ตฌ์กฐ๊ฐ ์ ํฉํ์ง๋ ์ฌ์ค ์ ๋ชจ๋ฅด๊ฒ ์ง๋ง.
Git์ commit๋ ์์ฃผ ํ๋ฉด์ ๋ง๋ค๋ ค๊ณ ํ๋๋ฐ, ์ค๊ฐ์ ๋ค๋ฅธ ํ์ผ์์ ์์๋ก ๋ ์ด์์์ ์ง๋ฉฐ ์ปค๋ฐ ๋ก๊ทธ๊ฐ ๊ผฌ์ฌ๋ฒ๋ ค์ ์ฝ๊ฐ ์๋ฏธ์๊ฒ ๋์๋ค. ๊ทธ๋๋ ๋ค์ ํ๋ก์ ํธ๋ฅผ ์ฎ๊ฒจ์ค๋ฉด์๋ ์ปค๋ฐ ๋ก๊ทธ๋ฅผ ์ ๋จ๊ธฐ๋ ค๊ณ ํด๋ณด์๋ค. ๋ฌผ๋ก ์์๊ณผ๋ ๋ค๋ฅด๊ฒ ์ค๊ตฌ๋จ๋ฐฉ์ด ๋์ด๋ฒ๋ฆฌ๊ธด ํ์ง๋ง.
2. ํท๊ฐ๋ ธ๋ ๋ถ๋ถ 1, props type ์ ์ ๋ฐฉ๋ฒ
import { NoteType } from "../../type/notebookTypes";
import NoteEditor from "./Editor";
// const Note = ({ note }: NoteType) => { // โ ์ด๋ ๊ฒ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ
const Note = ({ note }: { note: NoteType }) => { // โญ๏ธ ์ด๋ฐ ์์ผ๋ก ์์ฑ์ ํด์ค์ผ ํ๋ค.
return (
<div className="note-item">
<NoteEditor note={note} />
</div>
);
};
export default Note;
์ด๊ฑด ์ฌ์ํ๊ฒ ํท๊ฐ๋ ธ๋ ๋ถ๋ถ์ด๋ค. ํญ์ props์ type์ ์๋ก ๋ฐ๋ก ๋นผ์ ์ ์ํ๋ค ๋ณด๋ ๋ฌธ๋ฒ์ด ํท๊ฐ๋ ค ์๋ชป ์์ฑํ์๋ค.
3. ํท๊ฐ๋ ธ๋ ๋ถ๋ถ 2, JSX๋ฅผ ๋ฐํํ์ง ์๋ React component์ return null
JavaScript๋ก ์์ ์์๋ ์๋ฌด๋ฐ ์๋ฌ๊ฐ ์์๋๋ฐ, TypeScript๋ก ์์ ์ ํ๋ return ๊ฐ์ด ์์ผ๋ ์๋ฌ๊ฐ ๋ฌ๋ค. customPlugin์ผ๋ก ๋ง๋ OnChangePlugin๊ณผ InitPlugin์ JSX๋ฅผ ๋ฆฌํดํ์ง ์๊ธฐ ๋๋ฌธ์ return null์ผ๋ก JSX๋ฅผ ๋ฐํํ์ง ์์์ ๋ช ์์ ์ผ๋ก ๊ธฐ์ ํ๋ค.
4. ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ ์๋์ ์ฅ ๊ธฐ๋ฅ
์๋์ ์ฅ์ ๊ตฌํํ๋ผ๋ ๋ผ๋ ์๊ตฌ์ฌํญ์ด ์์๋๋ฐ, ์ด ๋ถ๋ถ์ ์์ ํ๋๋ฐ ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค. ๋ก์ง์ ์๊ฐํ๋ ๊ฒ๋ณด๋ค ์ฝ๋๋ก ์ด๋ป๊ฒ, ์ด๋ค ์์๋ก ํํ์ ํด์ผํ๋์ง ๊ณ ๋ฏผํ๋๋ฐ ๋ ๋ง์ ์๊ฐ์ด ๊ฑธ๋ ธ๋ ๊ฒ ๊ฐ๋ค.
save๋ฅผ ์ํ ํจ์๋ ์ด๋ฏธ zustand store์ ์ ์ฅ๋์ด ์์๊ณ , ํ๋ก์ ํธ๋ฅผ ํ๋ฉฐ ์์๋ก ๋ง๋ค์ด๋ ๋ฒํผ์ ํด๋ฆญํจ์ผ๋ก์จ ์ ์ฅ๊ธฐ๋ฅ์ ์ฌ์ฉํ์๊ธฐ ๋๋ฌธ์ ํจ์์๋ ๋ฌธ์ ๊ฐ ์์๊ณ , ๋จ์ง ์ผ์ ํ ์๊ฐ๋ง๋ค ํจ์๋ฅผ ํธ์ถํ๊ธฐ๋ง ํ๋ฉด ๋์๋ค. ์๊ฐํ๋ ๋ก์ง์ ์๋์ ๊ฐ์๋ค.
1. ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๋ค.
2. ์ํ isModified๋ ์ด๊ธฐ๊ฐ false๋ฅผ ๊ฐ์ง๋ค.
3. editor์ ์ฌ์ฉ์๊ฐ ์ ๋ ฅ์ ํ๋ฉด isModified๋ false๊ฐ ๋๋ค. → ์ฆ, ๊ณ์ ์ ๋ ฅํ๋ ์ํ๋ผ๋ฉด isModified๋ ๊ณ์ํด์ false ์ํ์ด๋ค.
4. editor์ ์ฌ์ฉ์์ ์ ๋ ฅ์ด ๋ฉ์ถ๊ณ 3์ด ํ isModified๋ true๊ฐ ๋๋ค.
5. isModified๊ฐ true๊ฐ ๋๋ฉด auto save๋ฅผ ํ๋ค.
6. ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ค.
7. editorState๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค โก~โฅ์ด ๋ฐ๋ณต๋๋ค.
const Editor = ({ note }: { note: NoteType }) => {
const { saveNote } = useStore();
const [editorState, setEditorState] = useState(""); // JSON ํํ๋ก ์ ์ฅ๋ editorState(ํ์ฌ ์๋ํฐ์ ๊ธ)
const [isModified, setIsModified] = useState(false);
// 1. editor์ ์ฌ์ฉ์๊ฐ ์
๋ ฅ์ ํ๋ฉด isModified๋ false
const onChange = (editorState: EditorState) => {
setIsModified(false);
const editorStateJSON = editorState.toJSON();
setEditorState(JSON.stringify(editorStateJSON));
// ...
};
const onSave = () => {
if (note.id && titleNode && editorState) {
saveNote(note.id, titleNode, editorState);
}
};
// 2. editor์ ์ฌ์ฉ์์ ์
๋ ฅ์ด ๋ฉ์ถ๊ณ 3์ด ํ isModified๋ฅผ true๋ก ์ค์
useEffect(() => {
const timeoutId = setTimeout(() => {
setIsModified(true);
}, 3000);
return () => clearTimeout(timeoutId);
}, [editorState]);
// 3. isModified๊ฐ true๊ฐ ๋๋ฉด auto save
useEffect(() => {
if (isModified && titleNode !== "" && editorState !== "") {
onSave();
}
}, [isModified]);
return (
<EditorWrapper className="note-item">
{...}
</EditorWrapper>
);
};
์ด๊ฑธ ๊ตฌํํ๋๋ฐ ๊ฑฐ์ 3์๊ฐ์ ๊ฑธ๋ฆฐ ๊ฒ ๊ฐ๋ค.
onChange๊ฐ ๋ ๋๋ง๋ค ์๋์ ์ฅ์ ์ํ ํ์ด๋จธ๊ฐ ์์ฑ๋์ด์ผ ํ๋๋ฐ, ์ด๋ฅผ ์ํด useEffect์ ์ด๋ค๊ฑธ ์์กด์ฑ ๋ฐฐ์ด๋ก ๋ฃ์ด์ผ ํ๋์ง๋ ํท๊ฐ๋ ธ๊ณ , ๊ทธ๋ฅ ์ ๋ฐ์ ์ผ๋ก ์ด๋ป๊ฒ ํด์ผํ ์ง ์ด๋ ค์ ๋ ๊ฒ ๊ฐ๋ค.
๊ฒฐ๊ตญ “change ๋๋ ์ค์๋ ๊ณ์ isModified๋ false์ฌ์ผ ํ๊ณ , change๊ฐ ๋ฉ์ถ ํ 3์ด ํ์ isModified๋ true๊ฐ ๋๋ค.” ์ด๊ฑธ ์์์ผ๋ก ๋ก์ง์ ์๊ฐํด๋ณด๊ธฐ ์์ํ๋ ๊ฒ ๊ฐ๋ค. ์ ๋ง ๋๊ณ ๋์ ์ด๊ฒ์ ๊ฒ ์๋ํด๋ณด๊ณ ๊ตฌํ๋ ๊ธฐ๋ฅ์ด๋ค.
โญ๏ธ
โฌ๏ธ ๊ธ์ด ๊ธธ์ด์ ธ์ ๋๋์ด ์์ฑํ์์ต๋๋ค. ๋ค์ ๊ธ ์ ๋๋ค. โฌ๏ธ