β νμν λΌμ΄λΈλ¬λ¦¬ μ€μΉ
yarn add quill : κΈ μμ±νλ Editor λΌμ΄λΈλ¬λ¦¬ μ€μΉ
yarn add styled-components : CSS κ΄λ ¨ 리μ‘νΈ λΌμ΄λΈλ¬λ¦¬ μ€μμ κ°μ₯ μΈκΈ°μλ λΌμ΄λΈλ¬λ¦¬ μ€μΉ
리λμ€ νμ₯ νλ‘κ·Έλ¨ : https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
Redux DevTools
Redux DevTools for debugging application's state changes.
chrome.google.com
리λμ€ κ°λ° ν΄μ λ€μ΄λ°μΌλ©΄ ν¬λ‘¬μΌλ‘ react λ₯Ό μ€νμμΌμ κ°λ°ν λ redux state λ³νλ₯Ό νμΈν μ μλ€.
β ꡬ쑰
1. page μμ κΈμ μμ±νλ©΄ state λ³νκ° μΌμ΄λλ€.
2. modules μμ handleActions λ‘ reducer ν¨μ λ§λ€μ΄μ λ³νλ state λ₯Ό store μ μ μ₯νλλ‘ νκ³ , createAction μΌλ‘ ν΄λΉ reducer ν¨μμ λν action μ μμ±νλ€.
3. page μ μ°κ²°λ container μμ useDispatch() λ‘ dispatch κ°μ²΄λ₯Ό λ§λ€κ³ dispatch() μ νλΌλ―Έν°λ‘ modules μμ μμ±ν action μ λ£μ΄μ action μ λ°μμν¨λ€. dispatch(action) νμ§ μμΌλ©΄ reducer κ° μ€νλμ§ μλλ€.
4. container μμ component λ₯Ό 리ν΄νλ€.
β backticks(`)μ μ΄μ©ν μ€νμΌ μ μ©
const EditorBlock = styled(Responsive)
`
padding-top: 5rem;
paddin-bottom: 5rem;
`
μ μ½λμ styled(Resonsive) λ₯Ό 보면 Responsive μ»΄ν¬λνΈλ₯Ό νλΌλ―Έν°λ‘ κ°μ§κ³ μλ€.
λ¬΄μ¨ μλ―ΈμΈμ§ μμ보기 μν΄ μλμ Responsive.js λ₯Ό μ΄ν΄λ³΄λ©΄, return κ°μ΄ <ResponsiveBlock> μΌλ‘ κ°μΈμ Έ μλ ννμ΄λ€. {...rest} λ λͺ¨λ props λ€μ Responsive νκ·Έμ μ μ©νλ€λ μλ―Έμ΄κ³ , {children} μ λ€λ₯Έ μ»΄ν¬λνΈμμ <Responsive> λ‘ κ°μΈμ Έ μλ contents λ₯Ό λ§νλ κ² κ² κ°λ€.
β children μ΄λ?
리μ‘νΈ μ»΄ν¬λνΈλ₯Ό μ¬μ©ν λ μ»΄ν¬λνΈ νκ·Έ μ¬μ΄μ λ΄μ©μ 보μ¬μ£Όλ props κ° children μ΄λ€.
import styled from 'styled-components'
const ResponsiveBlock = styled.div`
padding-left: 1rem;
padding-right: 1rem;
width: 1024px;
margin: 0 auto;
@media (max-width: 1024px){
width: 768px;
}
@media (max-width: 768px){
width: 100%;
}
`
const Responsive = ({ children, ...rest }) => {
// λ€λ₯Έ μ»΄ν¬λνΈμμ ꡬνλ children λΆλΆμ ResponsiveBlock μ€νμΌ μ μ©
return <ResponsiveBlock {...rest}>{children}</ResponsiveBlock>
}
export default Responsive;
WritePage.js λ₯Ό μ€νν html μ 보면 <Responsive> λ‘ κ°μΈμ§ ν΄λΉ νμ΄μ§μ μ€νμΌμ΄ μ μ©λ κ±Έ μ μ μλ€.
const WritePage = () => {
return(
<Responsive>
<Editor/>
<TagBox/>
</Responsive>
)
};
export default WritePage;
β useState()
ν¨μν μ»΄ν¬λνΈμμλ μνλ₯Ό κ΄λ¦¬ν μ μλλ‘ λ§λλ ν¨μμ΄λ€.
const TagItem = React.memo(({ tag, onRemove }) => (
<Tag onClick={() => onRemove(tag)}>#{tag}</Tag>
));
const TagList = React.memo(({ tags, onRemove }) => (
<TagListBlock>
{tags.map((tag) => (
<TagItem key={tag} tag={tag} onRemove={onRemove} />
))}
</TagListBlock>
));
const TagBox = () => {
const [localTags, setLocalTags] = useState([]);
// const [μν κ° μ μ₯ λ³μ, μν κ° μ
λ°μ΄νΈ ν¨μ] = useState(μν μ΄κΈ°κ°)
const onRemove = useCallback(
(tag) => {
setLocalTags(localTags.filter((t) => t !== tag)); // μ νν tag κ° μλ κ²λ§ localTags μ λ¨κΈ°κΈ°
console.log(localTags, tag);
},
[localTags]
);
return(
<TagBoxBlock>
<h4>νκ·Έ</h4>
<TagList tags={localTags} onRemove={onRemove} />
</TagBoxBlock>
)
};
1. 첫 λ²μ§Έ μμ localTags μ νμ¬ μν κ° λ³μμ΄κ³ λ λ²μ§Έ μμ setLocalTags μ μν κ°μ κ°±μ ν΄μ£Όλ ν¨μμ΄λ€. useState() μμ κ°μ μνμ μ΄κΈ° κ°μ΄λ€.
2. TagList νκ·Έμ μμ±μ onRemove λ₯Ό λ£μ΄μ£Όκ³ κ·Έ κ°μΌλ‘ onRemove() ν¨μλ₯Ό λ£μλ€.
3. TagList μ½λλ₯Ό 보면 TagItem νκ·Έκ° μκ³ κ·Έ μμ±μ onRemove={onRemove} κ° μλ€.
4. TagItem μ½λλ₯Ό 보면 <TagonClick={() =>onRemove(tag)}>#{tag}</Tag> μΌλ‘ νκ·Έλ₯Ό ν΄λ¦νλ©΄ onRemove κ° λκ²λ λμ΄μλ€.
μ¦, νκ·Έ 리μ€νΈ μ€ νλμ νκ·Έλ₯Ό μ νν΄μ μμ νλ κ²μ΄κΈ° λλ¬Έμ TagItem μμ onClick μμ± κ°μΌλ‘ onRemove λ₯Ό λ£μ΄μ€μΌλ‘μ¨ TagItem μ μμ νκ³ κ·Έκ²μ TagList μ λ°μνλ κ²μ΄λ€. (TagList μμ TagItem μμ onClick ν¨μκ° μλ ꡬ쑰μ΄λ€.)
β useRef()
ν¬μ»€μ€λ₯Ό μ€μ νκ±°λ νΉμ μμμ ν¬κΈ°λ₯Ό κ°μ Έμ€λ λ± νΉμ DOM μ μ νν΄μΌ ν λ μ¬μ©νλ ν¨μμ΄λ€.
const Editor = () => {
const quillElement = useRef(null); // Quill μ μ μ©ν DOM(DivElement) λ₯Ό μ€μ
const quillInstance = useRef(null); // Quill μΈμ€ν΄μ€ μ€μ
useEffect(() => {
quillInstance.current = new Quill(quillElement.current, {
theme: 'bubble',
placeholder: 'λ΄μ©μ μμ±νμΈμ...',
modules: {
toolbar: [
[{ header: '1' }, { header: '2' }],
['bold', 'italic', 'underlinde', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['blockquote', 'code-block', 'link', 'image'],
],
}
})
}, [])
return (
<EditorBlock>
<TitleInput placeholder="μ λͺ©μ μ
λ ₯νμΈμ" />
<QuillWrapper>
<div ref={quillElement} />
</QuillWrapper>
</EditorBlock>
)
}
1. Ref κ°μ²΄ μμ± : quillElement = useRef(null);
2. Ref κ°μ²΄μ current κ°μ μ ννκ³ μ νλ DOM μ κ°λ¦¬ν¨λ€ : quillElement.current
μ¦, quillElement.current μ λ΄μ©μ μμ±νμΈμ.. κ° ν¬ν¨λ Quill(μλν°) κ°μ²΄μ΄λ€.
3. return λ΄μ <div ref={quillElement}/> μμ DOM μ μμ±μΌλ‘ ref κ°μ λ£μ΄μ€λ€.
β useEffect(function, deps)
useEffect() ν¨μλ μ»΄ν¬λνΈκ° λ λλ§ ν λλ§λ€ νΉμ μμ μ μ€νν μ μλλ‘ νλ Hook μ΄λ€.
μ»΄ν¬λνΈκ° μ²μ λ λλ§ λ λ ν λ²λ§ μ¬μ©νκΈ° μν΄μλ deps μμΉμ λΉ λ°°μ΄ [] μ λ£λλ€. λ°°μ΄μ μλ΅νλ©΄ 리λ λλ§ λ λλ§λ€ μ€νλλ€. κ°μ΄ μ λ°μ΄νΈ λ λ λ λλ§ νλ€λ©΄ deps λ°°μ΄ μμ κ²μ¬νκ³ μ νλ κ°μ λ£λλ€. deps νλΌλ―Έν°λ₯Ό μμ±νμ§ μμΌλ©΄ μ»΄ν¬λνΈκ° 리λ λλ§ λ λλ§λ€ useEffect() ν¨μκ° νΈμΆλλ€.
// modules/write.js
import { createAction, handleActions } from "redux-actions";
const INITIALIZE = "write/INITIALIZE";
const CHANGE_FIELD = "write/CHANGE_FIELD";
// action λ§λ€κΈ°
export const initialize = createAction(INITIALIZE);
export const changeField = createAction(CHANGE_FIELD, ({ key, value }) => ({
key,
value,
}));
const initialState = {
title: "",
body: "",
tags: [],
};
// λ°μμ action μ μ¬μ©ν μ μλλ‘ νλ€.
const write = handleActions(
{
[INITIALIZE]: (state) => initialState,
[CHANGE_FIELD]: (state, { payload: { key, value } }) => ({
...state,
[key]: value,
}),
},
initialState
);
export default write;
import Editor from '../../components/write/Editor'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect, useCallback } from 'react'
import { changeField, initialize } from '../../modules/write'
// Quill μλν°λ μΌλ° input μ΄λ textarea κ° μλκΈ° λλ¬Έμ onChange μ value κ°μ μ¬μ©νμ¬ μνλ₯Ό κ΄λ¦¬ν μ μλ€.
// λ°λΌμ μ§κΈμ μλν°μμ κ°μ΄ λ°λ λ 리λμ€ μ€ν μ΄μ λ£λ μμ
λ§ νκ³ λ¦¬λμ€ μ€ν μ΄μ κ°μ΄ λ°λ λ μλν° κ°μ΄ λ°λκ² νλ μμ
μ
// μΆν ν¬μ€νΈ μμ κΈ°λ₯μ ꡬνν λ μ²λ¦¬νλ€.
const EditorContainer = () => {
const dispatch = useDispatch()
const {title, body} = useSelector(({write}) => ({
title: write.title,
body: write.body,
}))
const onChangeField = useCallback(payload => dispatch(changeField(payload)),[
dispatch,
])
// dispatch κ° λ°λ λλ§λ€(=μλ‘ κΈμ μμ±ν λλ§λ€) μ΄κΈ°ν action (=μ μ°½) μ dispatch νλ€.
useEffect(() => {
return () => {
dispatch(initialize())
}
}, [dispatch])
return <Editor onChangeField={onChangeField} title={title} body={body}/>
}
export default EditorContainer
μ μ½λμμ useEffect() ν¨μλ₯Ό 보면 dispatch κ° λ°λ λλ§λ€(=κΈ μμ± ν μ κΈμ μμ±νλ¬ λ€μ΄μ¬ λλ§λ€) μ΄κΈ°ν action(= default μ μ°½) μ dispatch νλ€.
β useSelector()
connect() ν¨μλ₯Ό μ¬μ©νμ§ μκ³ λ¦¬λμ€μ state λ₯Ό μ‘°νν μ μλ€.
index.js μμ combineReducer λ‘ λ¬Άμ¬μλ reducer λ€ μ€ write 리λμ λ΄μ μ΄κΈ°νκ° λμ΄ μλ (initState) state μ μ κ·Όνμ¬ ν΄λΉ κ°μ κ°μ Έμ¨λ€.
const {title, body} = useSelector(({write}) => ({
title: write.title,
body: write.body,
}))
β useDispatch()
modules ν΄λμμ μμ±ν action μ useDispatch λ₯Ό ν΅ν΄ λ°μμν¬ μ μλ€.
(action μ λ΄κ³ reduce λ‘ κ°λ dispatch μ΄μ°¨)
// -------------------------------- modules/write.js
import { createAction, handleActions } from "redux-actions";
const INITIALIZE = "write/INITIALIZE";
const CHANGE_FIELD = "write/CHANGE_FIELD";
// action λ§λ€κΈ°
export const initialize = createAction(INITIALIZE);
export const changeField = createAction(CHANGE_FIELD, ({ key, value }) => ({
key,
value,
}));
const initialState = {
title: "",
body: "",
tags: [],
};
// λ°μμ action μ μ¬μ©ν μ μλλ‘ νλ€.
const write = handleActions(
{
[INITIALIZE]: (state) => initialState,
[CHANGE_FIELD]: (state, { payload: { key, value } }) => ({
...state,
[key]: value,
}),
},
initialState
);
export default write;
// -------------------------------- containers/write/EditorContainer.js
// dispatch
const dispatch = useDispatch()
const onChangeField = useCallback(payload => dispatch(changeField(payload)),[
dispatch,
])
useEffect(() => {
return () => {
dispatch(initialize())
}
}, [dispatch])
μ‘μ μ νμν μΆκ° λ°μ΄ν°λ payload λΌλ μ΄λ¦μ νλΌλ―Έν°λ₯Ό μ¬μ©νμ¬ μΆκ°νλ€.
β payload λ?
payload λ action μ μ€νμ νμν λ°μ΄ν°μ΄λ€.
// Bpp.js
import React, { Component } from "react";
class Bpp extends Component {
render() {
return (
<div>
<h1>Bpp</h1>
<h1>{this.props.result}</h1>
<button
onClick={() => {
this.props.onINC({ a: 10, b: 20 });
}}
>
λνκΈ°
</button>
</div>
);
}
}
export default Bpp;
component/Bpp.js μμ onClick μ΄λ²€νΈλ‘ μ λ¬νλ onINC κ°μ΄ κ°μ²΄ {a: 10, b: 20} μ΄λ€.
// bpp.js
import { createAction, handleActions } from "redux-actions";
const INC = "INC";
const DEC = "DEC";
export const inc = createAction(INC);
export const dec = createAction(DEC);
const initState = {
num: 100,
};
export default handleActions(
{
// [INC]: (state, action) => ({ num: state.num + action.payload }), // inc(n) μμ νλΌλ―Έν° κ° n μ΄ μ¬κΈ°μ action.payload μ΄λ€.
[INC]: (state, { payload: data }) => {
return { num: state.num + data.a + data.b };
},
// [INC]: (state, { payload: {a, b} }) => {
// return { num: state.num + a + b };
// },
},
initState
);
component/Bpp.js μμ μ λ¬ν onINC κ°μ²΄λ₯Ό payload λ‘ λ°λλ€. {payload: data} λ payload λ₯Ό data λ‘ λΆλ₯΄λ κ²μ΄κ³ data λ‘ payload μ λ°μ΄ν°μ μ κ·Όνλ€. data.a λ 10 μ΄κ³ data.b λ 20 μ΄ λλ κ²μ΄λ€.
β useMemo() : λ λλ§ μ κ°μ μ¬μ¬μ©νκΈ° μν ν¨μ
useMemo() ν¨μλ λ λλ§ μ κ°μ μ¬μ¬μ© ν μ μλλ‘ νλ€. useMemo(function, deps) ννλ‘, deps(κ°μ λ΄μ λ°°μ΄) κ°μ΄ λ°λλ©΄ function μ νΈμΆν΄μ μ°μ°νκ³ , deps κ°μ΄ λ°λμ§ μμΌλ©΄ μ΄μ μ μ°μ°ν κ°μ μ¬μ¬μ©νλ€.
β useCallback() : νΉμ ν¨μλ₯Ό μ¬μ¬μ©νκΈ° μν ν¨μ
νΉμ ν¨μλ₯Ό μ¬μ¬μ©νκΈ° μν ν¨μμ΄λ€. μμ μ»΄ν¬λνΈμ props λ₯Ό μ λ¬ν λ useCallback() ν¨μλ₯Ό μ¬μ©νλ©΄ λΆνμν 리λ λλ§μ λ°©μ§ν μ μλ€. 첫 λ²μ§Έ νλΌλ―Έν°μλ μμ±νκ³ μΆμ ν¨μλ₯Ό λ£κ³ , λ λ²μ§Έ νλΌλ―Έν°μλ λ°°μ΄μ λ£λλ€. ν΄λΉ λ°°μ΄μλ μ΄λ€ κ°μ΄ λ°λμμ λ ν¨μλ₯Ό μλ‘ μμ±ν΄μΌ νλμ§λ₯Ό λͺ μν΄μΌ νλ€.
β React.memo()
λ λλ§μ μ΅μ ννκΈ° μν΄μ μ»΄ν¬λνΈ λΆλ¦¬λ₯Ό νκ³ λΆνμν λ λλ§μ λ°©μ§νλ€. μ»΄ν¬λνΈμ props κ° λ°λμ§ μμλ€λ©΄ , 리λ λλ§νμ§ μλλ‘ μ€μ νμ¬ ν¨μ μ»΄ν¬λνΈμ 리λ λλ§ μ±λ₯μ μ΅μ νν΄ μ€ μ μλ€. λΆλͺ¨ μ»΄ν¬λνΈμμλ κ°μ΄ λ³κ²½λμ§ μμ κ²½μ°μ 리λ λλ§μ λ°©μ§νκΈ° μν΄ useCallback() ν¨μλ₯Ό μ¬μ©νλ©΄ λμ§λ§ κ·Έκ²μ μμ μ»΄ν¬λνΈμλ μ μ©λμ§ μλλ€. λ°λΌμ μμ μ»΄ν¬λνΈμμ 리λ λλ§μ λ°©μ§νκΈ° μν΄μλ React.memo() μ μ¬μ©ν΄μΌ νλ€. μ»΄ν¬λνΈμ 리λ λλ§μ λ°©μ§ν λλ μλͺ μ£ΌκΈ° ν¨μμΈ shouldComponentUpdate λ₯Ό μ¬μ©νλ©΄ λμ§λ§ ν¨μ μ»΄ν¬λνΈμμλ λΌμ΄νμ¬μ΄ν΄ λ©μλλ₯Ό μ¬μ©ν μ μλ€. λμ React.memo() λΌλ ν¨μλ₯Ό μ¬μ©νλ€. νλΌλ―Έν°λ‘λ μ»΄ν¬λνΈκ° νμνκ³ React.memo() λ‘ κ°μ μ»΄ν¬λνΈκ° 2κ° μ΄μμΌ λ function μ μ¬μ©νμ¬ μ²λ¦¬νλ€.
const TagItem = React.memo(({ tag }) => <Tag>#{tag}</Tag>);
const TagList = React.memo(({ tags }) => (
<TagListBlock>
{tags.map((tag) => (
<TagItem key={tag} tag={tag} />
))}
</TagListBlock>
));
μμ μ½λμμ λ λλ§μ νλ μν©μ λ κ°μ§μ΄λ€.
1. Tag μ λν input (=Item) μ΄ λ°λ λ
2. Tag λͺ©λ‘ (=List) μ΄ λ°λ λ
React.memo() λ₯Ό μ¬μ©ν΄μ μ»΄ν¬λνΈλ₯Ό κ°μΈμ£Όλ©΄ μ€μ λ‘ ν΄λΉ μ»΄ν¬λνΈκ° λ°μμ€λ props κ°μ΄ λ³κ²½λμμ λλ§ λ¦¬λ λλ§ ν΄μ€λ€.
β function* μ΄λ?
function* f1() {
console.log('1');
yield 1;
console.log('2');
yield 2;
console.log('3');
yield 3;
}
let func = f1();
console.log(func.next()); // 1
console.log(func.next()); // 2
console.log(func.next()); // 3
console.log(func.next()); // undefined
*λ₯Ό λΆμ΄λ ν¨μλ Generator κ° λλ€. μ΄ ν¨μλ λ΄λΆμμ yield λ¬Έλ²μ μ¬μ©ν μ μλλ°,
yield λ return κ³Ό κ°μ ν¨μ μ’ λ£μ μν μ νμ§λ§ ν¨μκ° μ¬νΈμΆ λ κ²½μ° ν΄λΉ μ§μ μμ μμνλ€λ
μ°¨μ΄μ μ΄ μλ€.
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,
});
// all([authSaga(), userSaga()]) μ λ°ν, ν¨μ μ¬νΈμΆ μ λ€μ auth μ λν μ²λ¦¬λ₯Ό μ§ννλ€.
export function* rootSaga() {
yield all([authSaga(), userSaga()]);
}
export default rootReducer;
μμ μ½λλ modules/index.js μ΄λ€. μμμ function* rootSaga() ν¨μλ all([authSaga(), userSaga()]) μ λ°ννκ³ , ν¨μ μ¬νΈμΆ μ λ€μ auth(user) μ λν μ²λ¦¬λ₯Ό μ§ννλ€.
π‘ Reducer (= containers ν΄λ)
Store μ state μ Dispatch λ‘λΆν° action μ λ°μμ action μ λν μμ μ νμ¬ state λ₯Ό λ³κ²½μν¨λ€.
// EditorContainer.js
import Editor from '../../components/write/Editor'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect, useCallback } from 'react'
import { changeField, initialize } from '../../modules/write'
// Quill μλν°λ μΌλ° input μ΄λ textarea κ° μλκΈ° λλ¬Έμ onChange μ value κ°μ μ¬μ©νμ¬ μνλ₯Ό κ΄λ¦¬ν μ μλ€.
// λ°λΌμ μ§κΈμ μλν°μμ κ°μ΄ λ°λ λ 리λμ€ μ€ν μ΄μ λ£λ μμ
λ§ νκ³ λ¦¬λμ€ μ€ν μ΄μ κ°μ΄ λ°λ λ μλν° κ°μ΄ λ°λκ² νλ μμ
μ
// μΆν ν¬μ€νΈ μμ κΈ°λ₯μ ꡬνν λ μ²λ¦¬νλ€.
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
μΉ μ€ν νλ©΄
// EditorContainer.js
import Editor from '../../components/write/Editor'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect, useCallback } from 'react'
import { changeField, initialize } from '../../modules/write'
// Quill μλν°λ μΌλ° input μ΄λ textarea κ° μλκΈ° λλ¬Έμ onChange μ value κ°μ μ¬μ©νμ¬ μνλ₯Ό κ΄λ¦¬ν μ μλ€.
// λ°λΌμ μ§κΈμ μλν°μμ κ°μ΄ λ°λ λ 리λμ€ μ€ν μ΄μ λ£λ μμ
λ§ νκ³ λ¦¬λμ€ μ€ν μ΄μ κ°μ΄ λ°λ λ μλν° κ°μ΄ λ°λκ² νλ μμ
μ
// μΆν ν¬μ€νΈ μμ κΈ°λ₯μ ꡬνν λ μ²λ¦¬νλ€.
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])
// Editor μ νλΌλ―Έν° κ° μ λ¬
return <Editor onChangeField={onChangeField} title={title} body={body}/>
}
export default EditorContainer
// Editor.js
import Quill from "quill"; // κΈ μμ±νλ Editor λΌμ΄λΈλ¬λ¦¬
import { useRef, useEffect } from "react";
import "quill/dist/quill.bubble.css";
import styled from "styled-components"; // css in js κ΄λ ¨ 리μ‘νΈ λΌμ΄λΈλ¬λ¦¬ μ€μμ κ°μ₯ μΈκΈ°μλ λΌμ΄λΈλ¬λ¦¬
import palette from "../../lib/styles/palette";
import Responsive from "../common/Responsive";
// μ€νμΌ μ½λ μ€λ΅
// EditorContainer μμ μ λ¬λ°μ κ° onChangeField
const Editor = ({ title, body, onChangeField }) => {
const quillElement = useRef(null); // Quill μ μ μ©ν DOM(DivElement) λ₯Ό μ€μ
const quillInstance = useRef(null); // Quill μΈμ€ν΄μ€ μ€μ
useEffect(() => {
quillInstance.current = new Quill(quillElement.current, {
theme: "bubble",
placeholder: "λ΄μ©μ μμ±νμΈμ...",
modules: {
toolbar: [
[{ header: "1" }, { header: "2" }],
["bold", "italic", "underlinde", "strike"],
[{ list: "ordered" }, { list: "bullet" }],
["blockquote", "code-block", "link", "image"],
],
},
});
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;
// TagBoxContainer.js
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,
})
);
console.log("onChangeTags:", nextTags);
}; // TagBox μ νλΌλ―Έν° κ° μ λ¬
return <TagBox onChangeTags={onChangeTags} tags={tags} />;
};
export default TagBoxContainer;
// TagBox.js
import React from "react";
import styled from "styled-components";
import palette from "../../lib/styles/palette";
import { useState, useCallback, useEffect } from "react";
// css μ€νμΌ μ½λ μ€λ΅
// λ λλ§μ μ΅μ ννκΈ° μν΄μ μ»΄ν¬λνΈ λΆλ¦¬ TagItem / TagList
// TagBox μμ λ λλ§ νλ μν©μ λ κ°μ§
// 1. input μ΄ λ°λ λ
// 2. tag λͺ©λ‘μ΄ λ°λ λ
// React.memo λ₯Ό μ¬μ©ν΄μ μ»΄ν¬λνΈλ₯Ό κ°μΈμ£Όλ©΄ μ€μ λ‘ μ»΄ν¬λνΈκ° λ°μμ€λ props κ° λ³κ²½λμμ λλ§ λ¦¬λ λλ§ ν΄μ€λ€.
// tag κ°μ΄ λ°λ λλ§ λ¦¬λ λλ§ λλλ‘ μ²λ¦¬
const TagItem = React.memo(({ tag, onRemove }) => (
<Tag onClick={() => onRemove(tag)}>#{tag}</Tag>
));
const TagList = React.memo(({ tags, onRemove }) => (
<TagListBlock>
{tags.map((tag) => (
<TagItem key={tag} tag={tag} onRemove={onRemove} />
))}
</TagListBlock>
));
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); // νκ·Έ 리μ€νΈκ° λ³κ²½(μ
λ ₯, μμ )λλ κ²μ λν log
console.log(nextTags);
},
[localTags, onChangeTags]
);
// νκ·Έ μμ ν λ
const onRemove = useCallback(
(tag) => {
const nextTags = localTags.filter((t) => t !== tag);
setLocalTags(nextTags);
onChangeTags(nextTags);
console.log(localTags, tag);
},
[localTags, onChangeTags]
);
// νκ·Έ μ
λ ₯ μ°½μ κ°μ΄ μ
λ ₯ νΉμ λ°λλ κ±° μ€μκ°μΌλ‘ λ°μ
const onChange = useCallback((e) => {
setInput(e.target.value); // input μμ λ°μ κ° μΆμΆ
console.log("onChange:", e.target.value);
}, []);
// νκ·Έ μΆκ° ν λ
const onSubmit = useCallback(
(e) => {
e.preventDefault();
insertTag(input.trim());
setInput(""); // νκ·Έ μΆκ°νλ κ³΅κ° μ΄κΈ°ν
},
[input, insertTag]
);
// tags κ°μ΄ λ°λμμ λ
useEffect(() => {
setLocalTags(tags);
console.log("onChangeTags: " ,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;
μΉ μ€ν νλ©΄
write λΆλΆ μΉ μ€ν νλ©΄
λμ [리μ‘νΈλ₯Ό λ€λ£¨λ κΈ°μ ] μ μ°Έκ³ νμ¬ μμ±ν κΈμ λλ€.
"μ΄ ν¬μ€ν μ μΏ ν‘ ννΈλμ€ νλμ μΌνμΌλ‘, μ΄μ λ°λ₯Έ μΌμ μ‘μ μμλ£λ₯Ό μ 곡λ°μ΅λλ€."
'JavaScript > React' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[React] Hooks μ 리 (0) | 2021.10.26 |
---|---|
[React] μ»΄ν¬λνΈ μμ보기 (0) | 2021.10.26 |
[React] Redux ν΄λ ꡬ쑰 μ 리νκΈ° (0) | 2021.10.21 |
[React] Redux λ? (0) | 2021.10.21 |
[React] React μ΄λ²€νΈ μ¬μ©νκΈ° (0) | 2021.10.20 |
λκΈ