λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
JavaScript/React

[React] κΈ€μ“°κΈ° κΈ°λŠ₯ κ΅¬ν˜„ν•˜κΈ°

by soyλ―Έλ‹ˆ 2021. 10. 22.

 

 

 

 

✍ ν•„μš”ν•œ 라이브러리 μ„€μΉ˜

 

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;

 

 

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

TagBoxContainer

 

 

 

 

 

write λΆ€λΆ„ μ›Ή μ‹€ν–‰ ν™”λ©΄

 

κΈ€μ“°κΈ° λΆ€λΆ„ μ›Ή μ‹€ν–‰ν™”λ©΄

 

 

 

 

 

 

λ„μ„œ [λ¦¬μ•‘νŠΈλ₯Ό λ‹€λ£¨λŠ” 기술] 을 μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•œ κΈ€μž…λ‹ˆλ‹€.

 

 

 

 

"이 ν¬μŠ€νŒ…μ€ 쿠팑 νŒŒνŠΈλ„ˆμŠ€ ν™œλ™μ˜ μΌν™˜μœΌλ‘œ, 이에 λ”°λ₯Έ μΌμ •μ•‘μ˜ 수수료λ₯Ό μ œκ³΅λ°›μŠ΅λ‹ˆλ‹€."

λŒ“κΈ€