Skip to content

Latest commit

ย 

History

History
512 lines (429 loc) ยท 21.9 KB

sprint-web.md

File metadata and controls

512 lines (429 loc) ยท 21.9 KB

๐Ÿ“ Sprint

๐Ÿ“š Sprint Backlog

Google Spreadsheet ๐Ÿ”—

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป ๊ฐœ๋ฐœ์ž


๐Ÿ’ป Sprint #3 - Day4

๐Ÿ“Œ [FE] IssueProvider ๊ตฌํ˜„

  • reduce๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๊ฒฝ์šฐ, ํ•ฉ์ณ์ฃผ๋Š” IssueProvider๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
    const IssueProvider = ({ contexts, children }) =>
      contexts.reduce(
        (prev, context) =>
          createElement(context, {
            children: prev,
          }),
        children
      );

๐Ÿ“Œ [FE] Issue, User Store & Reducer ๊ตฌํ˜„

  • Issue ํŽ˜์ด์ง€์— Store๋ฅผ ์ ์šฉํ•˜์—ฌ ์ด์Šˆ ์ƒ์„ฑ / ๋ฆฌ์ŠคํŠธ / ์ด์Šˆ ๋ฆฌ์ŠคํŠธ ํ—ค๋” ๋“ฑ์—์„œ ๋ชจ๋‘ ์ƒํƒœ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋งํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ [FE] ์ƒ๋‹จ ์ถ”๊ฐ€ ํ•„ํ„ฐ ๋ชฉ๋ก ๊ตฌํ˜„

  • ์ž‘์„ฑ์ž ์ถ”๊ฐ€ ํ•„ํ„ฐ ์˜ต์…˜ ์„ ํƒ ์‹œ ํŒ์—…์ฐฝ์œผ๋กœ ๋ชฉ๋ก์„ ๋„์šฐ๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋‹ค.
  • ์ด ๋•Œ, import { Dropdown } from 'semantic-ui-react' ๋ฅผ ์‚ฌ์šฉํ•ด๋ดค๋Š”๋ฐ ํด๋ž˜์Šค๋ฅผ ๋˜ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ๋””์ž์ธ์„ ์žก์•„์ค˜์•ผํ•ด์„œ ๋ถˆํŽธํ•จ๋„ ์žˆ์—ˆ๋‹ค.

๐Ÿ“Œ [FE] Close Issue, Reopen Issue ๋™์ ์œผ๋กœ ๊ตฌํ˜„

  • Reducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ issueDetail Store์˜ isOpen ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ๋‹ค.
  • reducer ํ•จ์ˆ˜์—์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“ค ๋•Œ์—๋Š” ๋ถˆ๋ณ€์„ฑ์„ ์ง€์ผœ์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— spread ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉ reducer ๋‚ด๋ถ€์—์„œ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

๐Ÿ“Œ [FE] Milestone ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ ๊ตฌํ˜„

  • useContext์™€ useReducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.
  • Label ํŽ˜์ด์ง€์—์„œ ๋น„์Šทํ•˜๊ฒŒ ๊ตฌํ˜„์„ ํ–ˆ๋˜ ๋‚ด์šฉ์ด๋ผ ์ด์ „๋ณด๋‹ค๋Š” ๊นจ๋—ํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•„์ง๋„ ๊ฐœ์„ ํ•  ์ ์ด ๋งŽ์ด ๋‚จ์€ ๊ฒƒ ๊ฐ™๋‹ค.

๐Ÿ“Œ [FE] ์ด๋ชจํ‹ฐ์ฝ˜ ์‰ฝ๊ฒŒ import ํ•˜๋Š” ๋ฒ• ์ฐพ์Œ

@primer/octicons-react ์„ค์น˜
import {
  IssueOpenedIcon,
  MilestoneIcon,
  IssueClosedIcon,
} from '@primer/octicons-react';

๐Ÿ’ป Sprint #3 - Day3

๐Ÿ“Œ [FE] useReducer๋ฅผ ํ†ตํ•œ Label ์ƒ์„ฑ ๋ฐ ์ œ๊ฑฐ ๋ชฉ๋ก ๊ด€๋ฆฌ

  • label ์ƒ์„ฑ๊ณผ ์ œ๊ฑฐ์‹œ label ๋ชฉ๋ก์„ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผํ–ˆ๋Š”๋ฐ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ํŒŒ์ผ(componenet)์—์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ค์› ๋‹ค. useReducer์˜ dispatch ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.
export const labelReducer = (labels, { type, payload }) => {
  switch (type) {
    case 'SET_INIT_DATA':
      return payload;

    case 'PUT_LABEL':
      return labels.map((label) => {
        if (label.id === payload.id) {
          label = payload;
        }
        return label;
      });

    case 'DELETE_LABEL':
      return labels.filter((label) => label.id !== payload);

    case 'NEW_LABEL_ADD':
      return [...labels, payload];

    default:
      break;
  }
};

๐Ÿ“Œ [FE] useReducer๋ฅผ ํ†ตํ•œ Label ์ƒ์„ฑ ํƒญ ๊ด€๋ฆฌ

  • ์œ„์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์ ์œผ๋กœ ํƒญ์ด ์—ด๋ฆฌ๊ณ  ๋‹ซํž ํ•„์š”๊ฐ€ ์žˆ์—ˆ๊ณ  ์ด๋ฅผ reducer๋ฅผ ํ†ตํ•ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ฐ๊ฐ์˜ ์ƒํ™ฉ์— ๋”ฐ๋ฅธ ๊ฐ’์„ ์ •์˜ํ•ด์ฃผ์–ด ํƒญ์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜์˜€๋‹ค.
export const newReducer = (isClickNew, { type }) => {
  switch (type) {
    case 'NEW_LABEL_TAB_OPEN':
      return true;

    case 'NEW_LABEL_TAB_CLOSE':
      return false;

    case 'NEW_LABEL_ADD':
      return false;

    default:
      break;
  }
};

๐Ÿ“Œ [FE] Issue Detail ํŽ˜์ด์ง€ ์‚ฌ์šฉ์ž ์ •๋ณด ๋™์  ํ• ๋‹น (match, useState, useEffect)

  • useState๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ›…์„ ๋งŒ๋“ค์–ด์„œ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ useEffect๋กœ ํ•œ๋ฒˆ๋งŒ fetchํ•˜์—ฌ ํ•„์š”ํ•œ ์ด์Šˆ์ž‘์„ฑ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋™์  ํ• ๋‹นํ•˜์˜€๋‹ค. match๋Š” ๊ฐ URL์— ๋“ค์–ด๊ฐ€๋Š” issueId๋ฅผ ๊ฐ€์ ธ์™”๋‹ค.
  • Open, Close์˜ ์ƒํƒœ์— ๋”ฐ๋ผ UI๋ฅผ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ํ•˜์—ฌ ๋‹ค๋ฅด๊ฒŒ ๋‚˜ํƒ€๋‚ด์—ˆ๋‹ค.
export default function IssueDetailPage({ match, location }) {
  const [issueAuthorInfo, setIssueAuthorInfo] = useState('');
  const [issueId, setIssueId] = useState(1);
  const userId = localStorage.getItem('userId');

  const getIssueAuthorInfo = async () => {
    const id = match.params.issueId;
    setIssueId(id);
    const options = getOptions();
    const response = await fetch(GET_ISSUE(id), options);
    const responseJSON = await response.json();
    setIssueAuthorInfo(responseJSON.data[0]);
  };

  useEffect(() => {
    getIssueAuthorInfo();
  }, []);

๐Ÿ“Œ [FE] ์ฝ”๋ฉ˜ํŠธ ์ž…๋ ฅ ์‹œ ์ฝ”๋ฉ˜ํŠธ ์ƒ์„ฑ ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์ž…๋ ฅ์ด ์•ˆ๋˜์–ด ์žˆ์„ ์‹œ ๋น„ํ™œ์„ฑํ™”

  • onChange์™€ Ref, useState ๋ฅผ ์ด์šฉํ•ด์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ”๋กœ ๋ฐ˜์˜ํ•ด์ฃผ๋Š” Handling ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋ฒ„ํŠผ ํ™œ์„ฑํ™”๋ฅผ ๋™์ž‘์‹œ์ผฐ๋‹ค.
  const IssueCommentForm = ({ issueId, userId }) => {
  const history = useHistory();
  const commentRef = useRef(false);
  const [comment, setComment] = useState('');
  const [userImage, setUserImage] = useState('');

  const createCommentData = () => {
    if (commentRef.current.value === '') {
      return;
    }

    const comment = {
      userId: userId,
      issueId: issueId,
      content: commentRef.current.value,
    };

๐Ÿ’ป Sprint #3 - Day2

๐Ÿ“Œ [FE] ์ปดํฌ๋„ŒํŠธ ํด๋” ๊ตฌ์กฐ ๋ณ€๊ฒฝ

  • ์ปดํฌ๋„ŒํŠธ ํด๋”์— ๊ด€๋ฆฌํ•  ํŒŒ์ผ๋“ค์ด ๋งŽ์•„์ ธ ํšŒ์˜๋ฅผ ํ†ตํ•ด ํด๋” ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฒดํ™”ํ•˜์˜€๋‹ค.
    • ํด๋” ๊ตฌ์กฐ ์˜ˆ์‹œ
      /components
       ใ„ด /issue
       ใ„ด /milestone
          ใ„ด MilestoneHeader.jsx
          ใ„ด Milestone.jsx
       ใ„ด /shared
          ใ„ด /button
          ใ„ด /container
              ใ„ด MainContainer.jsx
              ใ„ด HeaderContainer.jsx
      
    • ์—ฌ๋Ÿฌ item์—์„œ ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค์€ shared์— ๋„ฃ๊ณ  ๊ทธ ์™ธ component๋“ค์€ ์ž์‹ ์ด ์†ํ•œ item(ex. milestone, issue) ํด๋”์— ๋„ฃ๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.

๐Ÿ“Œ [FE] ๊ธ€์ž ์ˆ˜ ๋™์  ์ฒ˜๋ฆฌ

  • useState๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ธ€์ž ์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค setTimeiot์„ ํ™œ์šฉํ•ด์„œ 0.5์ดˆ๋งˆ๋‹ค ๊ธ€์ž ์ˆ˜ ์ฒดํ‚น์„ ํ–ˆ๋‹ค.
    • useState๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ํ™”๋ฉด์— ๋ณ€ํ™”์‹œํ‚ค๋Š” ๋ฐฉ์‹์„ ๊ณต๋ถ€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿ“Œ [FE] ์‹œ๊ฐ„ ์ฐจ์ด ๊ณ„์‚ฐํ•˜๋Š” util ๊ตฌํ˜„

  • ์ด์Šˆ๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ, ํ˜„์žฌ ํ™”๋ฉด์ด ๋ Œ๋”๋ง๋œ ์‹œ๊ฐ„๊ณผ ์ด์Šˆ๊ฐ€ ์ƒ์„ฑ๋œ ์‹œ๊ฐ„์˜ ์ฐจ๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ํ™”๋ฉด์— ๋ณด์ด๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.
    • 60์ดˆ ์ดํ•˜๋ผ๋ฉด -> ์ดˆ ๋‹จ์œ„๋กœ ํ‘œํ˜„ (seconds ago)
    • 60๋ถ„ ์ดํ•˜๋ผ๋ฉด -> ๋ถ„ ๋‹จ์œ„๋กœ ํ‘œํ˜„ (minutes ago)
    • 60์‹œ๊ฐ„ ์ดํ•˜๋ผ๋ฉด -> ์‹œ๊ฐ„ ๋‹จ์œ„๋กœ ํ‘œํ˜„ (hours ago)
    • ๊ทธ ์ด์ƒ์ด๋ผ๋ฉด -> days ago๋กœ ํ‘œํ˜„

๐Ÿ“Œ [FE] ๋ฐฐ๊ฒฝ RGB ์ƒ‰๊น”์„ ๋ถ„์„ํ•˜์—ฌ ๊ธ€์”จ์ƒ‰์„ ๊ฒฐ์ •ํ•ด์ฃผ๋Š” util ๊ตฌํ˜„

  • const result = (redValue * 0.299 + greenValue * 0.587 + blueValue * 0.114) / 255;
  • ์ƒ‰๊น” ๊ฐ’์—์„œ R, G, B ๊ฐ’์„ ๋ฝ‘์•„๋‚ด ์œ„ ์‹์— ์ ์šฉํ•˜์˜€๋‹ค.
  • ๊ฒฐ๊ณผ ๊ฐ’์ด 0.5 ๋ณด๋‹ค ํฌ๋ฉด ๊ฒ€์€ ๊ธ€์”จ๋ฅผ 0.5 ๋ณด๋‹ค ์ž‘์œผ๋ฉด ํฐ์ƒ‰ ๊ธ€์”จ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ์—ˆ๋‹ค.

๐Ÿ“Œ [FE] Custom Hook ๊ฐœ๋ฐœ

  • ์ด๋ฆ„์ด use๋กœ ์‹œ์ž‘ํ•˜๊ณ , ์•ˆ์—์„œ ๋‹ค๋ฅธ Hook์„ ํ˜ธ์ถœํ•จ์„ ํ†ตํ•ด custom hook์„ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • get ์š”์ฒญ์„ ๋ณด๋‚ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ์ „๊นŒ์ง€ loading์„ ๋„์šฐ๋Š” ๋ถ€๋ถ„์€ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„ custom hook ์œผ๋กœ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค.

๐Ÿ“Œ [FE] Github Login

  • ์–ป์€ ์œ ์ € ์ •๋ณด๋กœ JWT Token ์ƒ์„ฑํ›„ ํ”„๋ก ํŠธ๋กœ ๋ฐ˜ํ™˜
    • ๋ฐ˜ํ™˜ ์‹œ json ํ˜•ํƒœ๋กœ ๋ฆฌํ„ดํ•ด์ค˜์•ผ ํ•œ๋‹ค.
    • axios.post ๋ฐ˜ํ™˜์„ .then(res) ํ˜•์‹์œผ๋กœ ๋ฐ›์•„์ฃผ์–ด์•ผ not found(404) Error๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฐ˜ํ™˜๋œ ํ† ํฐ์„ ํ”„๋ก ํŠธ์˜ localstorage ์ €์žฅ.
  • res.json(๊ฐ’) ์—์„œ ๊ฐ’ ๋ถ€๋ถ„์ด ์ž˜๋ชป๋˜์–ด๋„ ์„œ๋ฒ„์—์„œ ๋ณ„๋‹ค๋ฅธ ์˜ค๋ฅ˜๋ฅผ return ํ•˜์ง€ ์•Š์Œ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

๐Ÿ’ป Sprint #3 - Day1

๐Ÿ“Œ [FE] ์Šคํ”„๋ฆฐํŠธ3 ์—ญํ•  ๋ถ„๋ฐฐ ๋ฐ ์ด์Šˆ ์ƒ์„ฑ

๐Ÿ“Œ[FE] ๊ตฌํ˜„ ์‚ฌํ•ญ

๐Ÿ“Œ [FE] github ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ

  • React์—์„œ useEffectํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊นƒํ—™ OAuth์˜ CallBack URL์„ ๊ฐ–๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ URL์˜ code๋ฅผ parsing ํ•˜์—ฌ ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒŒ ์„ค๊ณ„.
  • ๋ฐฑ์—”๋“œ์—์„œ ์ด๋ฅผ ๋ฐ›์•„ access_token์„ ์–ป๊ณ , ์œ ์ € ์ •๋ณด๋ฅผ ์–ป์Œ.


๐Ÿ’ป Sprint #2 - Day5

๐Ÿ“Œ [FE] Sprint 2์—์„œ ๊ฐœ๋ฐœํ•œ feature branch -> Dev-Client๋กœ Merge ์ง„ํ–‰


๐Ÿ“Œ JS tag ์ ์šฉ ๋‚ด์—ญ

  • [FE] Client ํ”„๋กœ์ ํŠธ ๊ตฌ์ถ•
    • [AS-IS] github ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€, Main ํŽ˜์ด์ง€, ์ด์Šˆ ์ƒ์„ธ ํŽ˜์ด์ง€, ์ด์Šˆ ๋“ฑ๋ก ๊ธฐ๋Šฅ ๊ฐ€๋Šฅ
  • [BE] API Cors ์„ค์ • ์ ์šฉ
  • [BE] API ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ๊ฐœ์„ 
    • [AS-IS] Issue & Comment Assignee ์ •๋ณด ์ถ”๊ฐ€ ๋ฐ˜ํ™˜
    • [AS-IS] status๊ฐ€ fail์ธ ๊ฒฝ์šฐ status ๊ฐ’๋งŒ ๋„˜๊ธฐ๋„๋ก ์ˆ˜์ •
    • [AS-IS] Github ๊ฐ€์ž…์‹œ ์ƒˆ๋กœ์šด user ์ƒ์„ฑ์‹œ imageURL ๋ฌธ์ œ ์ˆ˜์ •
    • [AS-IS] ๋ชจ๋“  ์‚ฌ์šฉ์ž(Assignee)๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” API ์ถ”๊ฐ€
    • [AS-IS] Milestone ์ •๋ณด ๋ฐ˜ํ™˜ API ์ถ”๊ฐ€
    • [AS-IS] Issue ์‚ญ์ œ API ์ถ”๊ฐ€
    • [AS-IS] ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ ๋ฐ˜ํ™˜ํ•˜๋Š” API ์ถ”๊ฐ€
    • [AS-IS] label get API ์ถ”๊ฐ€

๐Ÿ’ป Sprint #2 - Day4

๐Ÿ“Œ [FE] ๋กœ๊ทธ์ธ ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ ์ด๋™ ํŽ˜์ด์ง€ ์„ค์ • โœจ

  • ์ฐธ๊ณ  ์ฝ”๋“œ: /shared/App.jsx

๐Ÿ“Œ [FE] Prettier Formatter ์„ค์ • โœจ

  • ๋‚ด vscode์—์„œ prettier๊ฐ€ ์•ˆ๋  ๊ฒฝ์šฐ ์•„๋ž˜ ์‚ฌ์ดํŠธ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”

๐Ÿ“Œ [FE] config.js ํŒŒ์ผ โœจ

  • src ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ‘์— ํŒŒ์ผ ์ƒ์„ฑํ•ด์ฃผ๊ธฐ
export const BASE_URL = 'http://118.67.131.96:3000/';

๐Ÿ“Œ [BE] API enhancement โœจ

๊ตฌํ˜„ ๋‚ด์šฉ ๋ฐ ์ž‘์—… ํ–ˆ๋˜ ๋‚ด์—ญ

  • [Backend] ์ด์Šˆ์— ํ• ๋‹น๋œ Assignee ์ด๋ฆ„ ์™ธ์— ์ถ”๊ฐ€ ์ •๋ณด ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ธฐ
    • userId, imageUrl ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ด์คฌ์Šต๋‹ˆ๋‹ค.
  • [Backend] ์ด์Šˆ์— Comment ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„ ์™ธ์— ์ด๋ฏธ์ง€ url, id ์ถ”๊ฐ€ ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ธฐ
  • [Backend] ๋ชจ๋“  ์‚ฌ์šฉ์ž(Assignee) ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” API ์ œ์ž‘ํ•˜๊ธฐ
  • [Backend] milestone get API ์ˆ˜์ •ํ•˜๊ธฐ
  • [Backend] status -> fail์ธ ๊ฒฝ์šฐ status ๊ฐ’๋งŒ ๋„˜๊ธฐ๊ธฐ
    • ๋ชจ๋“  API ์ „๋ถ€ ๋‹ค ๊ณ ์น˜๊ธฐ
  • [Backend] ์ด์Šˆ ์‚ญ์ œํ•˜๊ธฐ
  • [Backend] Github ๊ฐ€์ž…์‹œ ์ƒˆ๋กœ์šด user ์ƒ์„ฑ์‹œ imageURL ๋ฌธ์ œ ๊ณ ์น˜๊ธฐ
  • [Backend] ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ ๋ฐ˜ํ™˜ํ•˜๋Š” API ๋งŒ๋“ค๊ธฐ
  • [Backend] label get API ๋งŒ๋“ค๊ธฐ

๐Ÿ“Œ [BE] Cors ์„ค์ • ์ถ”๊ฐ€ โœจ

  • fetch ์š”์ฒญ ์‹œ, cors ์˜ต์…˜ ์ถ”๊ฐ€ํ•ด์คฌ์Œ
  • Backend app.js cors ์„ค์ • ์ถ”๊ฐ€ํ•ด์คฌ์Œ


๐Ÿ’ป Sprint #2 - Day3

๐Ÿ“Œ [FE] React-router๋ฅผ ์ ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ณ€๊ฒฝ

  • react-router-dom์€ ์›น์—์„œ ์“ฐ์ด๋Š” ์ปดํฌ๋„ŒํŠธ์ด๊ณ , react-router-native๋Š” react-native๋ฅผ ํ™œ์šฉํ•œ ์•ฑ๊ฐœ๋ฐœ์— ์“ฐ์ด๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค. react-router๋Š” ์ด ๋‘˜์„ ํ•ฉ์นœ ํŒจํ‚ค์ง€์ด๋‹ค.
  • https://velopert.com/3417 ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ง„ํ–‰ํ•˜์˜€๋‹ค.
  • react-router-dom๋งŒ ์„ค์น˜ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.
npm install -d react-router-dom
  • ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์„ค๊ณ„
    • ๊ธฐ์กด

      • src/components: ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์œ„์น˜ํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ
      • src/pages: ๊ฐ ๋ผ์šฐํŠธ๋“ค์ด ์œ„์น˜ํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ
    • ์ถ”๊ฐ€๋œ ๋””๋ ‰ํ† ๋ฆฌ

      • src/utils: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋“ค์„ ๋ชจ์•„๋†“์€ ๋””๋ ‰ํ† ๋ฆฌ
      • src/shared: ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๊ณต์šฉ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ App.js ๊ฐ€ ์—ฌ๊ธฐ์— ์œ„์น˜ํ•จ
      • src/client: ๋ธŒ๋ผ์šฐ์ € ์ธก์—์„œ ์‚ฌ์šฉํ•  ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ
  • url๋กœ ์ง์ ‘ router์— ์ ‘๊ทผํ•˜๋‹ˆ ๋™์ž‘ํ•˜์ง€ ์•Š๊ณ  404 error๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.๐Ÿ˜ฅ
    • ๋ฒ„ํŠผ(Link)์„ ํด๋ฆญํ•ด์„œ ๋„˜์–ด๊ฐ€๋Š” ๋ผ์šฐํŒ… ์ด๋ฒคํŠธ๋Š” ๋ฌด์‚ฌํžˆ ๋™์ž‘ํ•˜์˜€๋‹ค.
  • pages๋ผ๋Š” ๋””๋ ‰ํ† ๋ฆฌ ๋„ค์ด๋ฐ๋„ ์•„์ง ์™€๋‹ฟ์ง€ ์•Š๊ณ (views ๋งŽ์ด ์ผ์—ˆ๋Š”๋ฐ,,) shared๋„,, client๋„.. ์ง„์งœ ์ƒ์†Œํ•œ ๊ฒŒ ๋งŽ์•„์„œ ๋ฆฌ์•กํŠธ์— ์•„์ง ์ •์ด ์•ˆ๊ฐ‘๋‹ˆ๋‹ค ^^7

๐Ÿ“Œ [FE] CSS-in-JS ์ ์šฉํ•˜๊ธฐ

  • ๊ณผ์ œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ฝ์–ด๋ณด๋‹ˆ CSS-in-JS๋ฅผ ์ ์šฉํ•ด์•ผํ•จ์„ ์•Œ๊ฒŒ ๋˜์–ด ์ˆ˜์ •ํ•˜์˜€๋‹ค.
npm install -d styled-components
  • SCSS๋กœ ์ ์šฉํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ๋„ˆ๋ฌด ์•„์‰ฝ๋„ค์š” 8ใ……8๐Ÿ˜‚

๐Ÿ“Œ [BE] ๋กœ๊ทธ์ธ callback...

  • ๊ตฌํ˜„ํ•œ OAuth Login์˜ ๊ฒฐ๊ณผ ๊ฐ’์„ ์–ด๋–ป๊ฒŒ FE์™€ iOS์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์„์ง€ ๋งŽ์€ ๊ณ ๋ฏผ์„ ํ•˜์˜€๋‹ค. ๐Ÿค”
  • redirect์˜ url๋กœ token์„ ๋„˜๊ฒจ์ฃผ๋Š” ๋ฐฉ๋ฒ•์€ ์•„๋‹Œ ๊ฒƒ ๊ฐ™๊ณ ...
  • githubOAuth๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋ฐœ๊ธ‰๋˜๋Š” code์™€ client_id, client_secret์„ ์ด์šฉํ•˜์—ฌ post์š”์ฒญ์œผ๋กœ access_token์„ ๋ฐœ๊ธ‰ํ•˜๋ ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด ๋ณด์•˜์œผ๋‚˜ ๋ฌด์Šจ ์ด์œ ์—์„œ์ธ์ง€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.. ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์•ผ๊ฒ ๋‹ค. ์ฐธ์กฐ๋งํฌ

๐Ÿ’ป Sprint #2 - Day2

๐Ÿ“Œ [BE] npm ๋ช…๋ น์–ด ์‹ค์ข…...!

  • ์„œ๋ฒ„์—์„œ npm ๋ช…๋ น์–ด๋ฅผ ์ณค์„ ๋•Œ command not found ์กฐ์ฐจ ๋œจ์ง€ ์•Š์•˜๋‹ค.
  • ์„œ๋ฒ„๋ฅผ ์žฌ๋ถ€ํŒ…ํ–ˆ๋‹ค. ์žฌ๋ถ€ํŒ…ํ•˜๋‹ˆ๊นŒ npm ๋ช…๋ น์–ด๊ฐ€ ์•ˆ๋˜๋˜ ์˜ค๋ฅ˜๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค.
  • ์ƒˆ๋กœ cloneํ•˜๊ณ  npm๊ณผ node, pm2๋ฅผ ์žฌ์„ค์น˜ํ–ˆ๋‹ค.
  • ๋ง์„ ์•ˆ๋“ฃ๋Š” ์„œ๋ฒ„...

๐Ÿ“Œ [FE] ํ”„๋ก ํŠธ์—”๋“œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ • ๋ฐ scss ์ƒ‰๊น” ๊ทœ์น™ ์„ค์ •

  • ํ”„๋ก ํŠธ์—์„œ๋Š” .env ๋Œ€์‹  src ์•„๋ž˜์— config.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์˜€๋‹ค. ์ฐธ์กฐ๋งํฌ
  • scssํŒŒ์ผ ์žฌ์‚ฌ์šฉ์„ ์œ„ํ•ด Client ์•„๋ž˜์— scssํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  _color.js ์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด์„œ ๋‹ค๋ฅธ scss์—์„œ importํ•ด์„œ ์‚ฌ์šฉ ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค. ์ฐธ์กฐ๋งํฌ
  • _color.js์—์„œ ์ž์ฃผ ์“ฐ๋Š” ์ƒ‰๊น”์€ ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ๋„ค์ด๋ฐํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.
    $gray-100: #f8f9fa !default;
    // $์ƒ‰๊น”-์ˆซ์ž๊ฐ’: #์ƒ‰์ƒ๊ฐ’
    // ๊ธฐ๋ณธ ์ƒ‰์ƒ์€ ๋ธ”๋ฃจ๋Š” 500์„ ๊ฐ–๋Š”๋‹ค.
    // ๋‚ฎ์€ ์ˆซ์ž์ผ์ˆ˜๋ก ์—ฐํ•œ ์ƒ‰์ด๊ณ  ๋†’์€ ์ˆซ์ž์ผ์ˆ˜๋ก ์–ด๋‘์šด ์ƒ‰์„ ์˜๋ฏธํ•œ๋‹ค. 
    • 100 ~ 999 ์‚ฌ์ด์˜ ์ˆซ์ž๋ฅผ ์ƒ‰๊น” ๋’ค์— ๋ถ™์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๐Ÿ’ป Sprint #2 - Day1

๐Ÿ“Œ [FE] Client ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

  • create react app ์—†์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค.
  • react์— ๋Šฅ์ˆ™ํ•œ ์‚ฌ๋žŒ์ด ์—†์–ด ํŒ€์› ๋ชจ๋‘ zoom์— ์ ‘์†ํ•˜์—ฌ ํ•จ๊ป˜ ์ฐพ์•„๋ณด๋ฉฐ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.
  • js์™€ jsx๋Š” ๊ธฐ๋Šฅ์€ ๋™์ผํ•˜์ง€๋งŒ React์—์„œ ๊ถŒ์žฅํ•˜๋Š” jsx๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.
  • React ํ”„๋กœ์ ํŠธ์˜ ํด๋” ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜์˜€๋‹ค. ๐Ÿ‘๐Ÿ‘
    • ex) Routes.jsx ๊ฐ€ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ๊ฐ–๊ณ ์žˆ๋‹ค.
      ใ„ด /Client
          App.jsx
          App.scss
          ใ„ด /src
              ใ„ด /components
                  ใ„ด Routes.jsx
                  ใ„ด /navbar
                      ใ„ด Navbar.jsx
                      ใ„ด Navbar.scss
                  ใ„ด /form
                      ใ„ด IssueAddForm.jsx
                      ใ„ด IssueAddForm.scss
      
              ใ„ด /pages
                  ใ„ด /login
                      ใ„ด LoginPage.jsx
                      ใ„ด LoginPage.scss 

๐Ÿ“Œ [FE] webpack ๋ฒ„์ „ ๊ด€๋ฆฌ

  • ์ตœ์‹  ๋ฒ„์ „์˜ webpack-dev-server/webpack-cli๋ฅผ ์‚ฌ์šฉํ•˜๋‹ˆ webpack-dev-server๋ฅผ ์‹คํ–‰ ์‹œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
  • ์˜์กด์„ฑ์ด ํ™•์ธ๋œ ๋ฒ„์ „์„ ์ฐพ์•„ ๋ณ€๊ฒฝ ํ›„, ์žฌ์‹คํ–‰์„ ์ง„ํ–‰ํ–ˆ๋”๋‹ˆ ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  • ๋ฒ„์ „ ๊ด€๋ฆฌ์˜ ์ค‘์š”์„ฑ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋Š๊ผˆ๋‹ค.

๐Ÿ’ป Sprint #1 - Day5

๐Ÿ“Œ [BE] Sprint 1์—์„œ ๊ฐœ๋ฐœํ•œ feature branch -> Dev-Server๋กœ Merge ์ง„ํ–‰

์ถฉ๋Œ์„ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ข‹์„๊นŒ?

  • ํŒ€์›๋“ค์ด ๊ฐ™์ด ์žˆ์œผ๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๋ณธ๋‹ค.
  • fork ๋“ฑ์˜ github GUI ํˆด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋žœ์น˜์™€ ์ €์žฅ์†Œ ๊ด€๋ฆฌ์˜ ๋„์›€์„ ๋ฐ›์œผ๋ฉด ๋” ์‰ฝ๋‹ค.
  • ์ถฉ๋Œ์ด ํฌ์ง€ ์•Š๋‹ค๋ฉด github web ํŽธ์ง‘์œผ๋กœ ์‰ฝ๊ฒŒ mergeํ•  ์ˆ˜ ์žˆ๋‹ค.

sub query

GET_OPEN_ISSUES:
  `SELECT issue.id as issueId, < 3๋ฒˆ : issue.id๋ฅผ ๊ฐ€์ ธ์™€์„œ issueID๋ผ ์ •์˜
  
  (SELECT user.email FROM user WHERE user.id = issue.userId) as email, < 4๋ฒˆ : where์ด ์„ฑ๋ฆฝํ•  ๋•Œ ์œ ์ €์˜ email ๊ฐ€์ ธ์™€์„œ email์ด๋ผ ์ •์˜ ์ดํ•˜ ๋™๋ฌธ
  
  (SELECT user.name FROM user WHERE user.id = issue.userId) as name,
  
  (SELECT milestone.title FROM milestone WHERE issue.milestoneID = milestone.id) as milestone,
  
  issue.title, issue.content, issue.isOpen, issue.createAt, issue.closeAt
  
  FROM issue < 1๋ฒˆ : ์ด์Šˆ๋กœ๋ถ€ํ„ฐ
  
  WHERE issue.isOpen = 1`, < 2๋ฒˆ : issue์˜ isOpen์ด true์ผ ๋•Œ

๐Ÿ“Œ [BE] ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ถ€๋ถ„์€ template๋กœ ๊ด€๋ฆฌ

  makeIssueTemplate: async (results) => {
    const issueList = [...results.data[0]];

    for (let issue of issueList) {
      const labelList = await requestQuery(query.GET_LABELS_BY_ISSUE_ID, [
        issue.issueId,
      ]);
      const assigneeResults = await requestQuery(
        query.GET_ASSIGNEES_BY_ISSUE_ID,
        [issue.issueId]
      );
      const assigneeList = [];

      for (let assign of assigneeResults.data[0]) {
        assigneeList.push(assign.name);
      }

      issue.label = labelList.data[0];
      issue.assign = assigneeList;
    }

    return issueList;
  },

๐Ÿ’ป Sprint #1 - Day4

๐Ÿ“Œ [BE] issue return ํ˜•์‹

{
    "status": "success",
    "data": [
        {
            "issueId": 1,
            "email": "[email protected]",
            "name": "test",
            "milestone": "FE",
            "title": "ViewCreate",
            "content": "we develop",
            "isOpen": 1,
            "createAt": "2020-01-02T15:00:00.000Z",
            "closeAt": "2020-01-02T15:00:00.000Z",
            "label": [
                {
                    "labelName": "web",
                    "labelColor": "#121212",
                    "labelDescription": "fighting"
                },
                {
                    "labelName": "FE",
                    "labelColor": "#243412",
                    "labelDescription": "fighting"
                }
            ],
            "assign": [
                "test",
                "reality"
            ]
        },
    ]
}

๐Ÿ“Œ [BE] content not null ์กฐ๊ฑด์„ null๋กœ ๋ณ€๊ฒฝ

  • ํ™”๋ฉด์—์„œ content๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฒฝ์šฐ no description provided ๋ฉ”์„ธ์ง€๋ฅผ ๋‹ค๋ฅธ ๊ธ€์”จ๋กœ ๋„์›Œ์ฃผ๊ธฐ ์œ„ํ•ด์„œ null๋กœ content๋กœ ์„ค์ •๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜์˜€๋‹ค.

    • NOT NULL ์ œ๊ฑฐ
    ALTER TABLE issue modify content text NULL;
  • ์ฟผ๋ฆฌ๋ฌธ์˜ DDL, DML์€ ๋Œ€๋ฌธ์ž๋กœ ๋‚˜๋จธ์ง€๋Š” ์†Œ๋ฌธ์ž๋กœ, ๋งˆ์ง€๋ง‰์—๋Š” ์„ธ๋ฏธ์ฝœ๋ก  ์•ˆ๋ถ™์ด๊ธฐ
  • ๋ผ์šฐํ„ฐ ์ฝ”๋“œ ์ˆœ์„œ
    • get -> post -> put -> delete ๋ณ„๋กœ ์ฝ”๋“œ๋ฅผ ๋„์›Œ์“ฐ๊ธฐ๋กœ ํ•˜์˜€๋‹ค.
    • ํ•จ์ˆ˜๋ช…์€ ๊ฐ get, create, update, delete ์˜ ๋‹จ์–ด๋ฅผ ์จ์„œ ์ž‘์„ฑํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.
  • put๊ณผ patch ์ค‘ put ์‚ฌ์šฉ์œผ๋กœ ํ†ต์ผํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.

๐Ÿ’ป Sprint #1 - Day3

๐Ÿ“Œ [BE] ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜ ์ž‘์„ฑ

๐Ÿ“Œ [BE] issue CRUD API, query๋ฌธ ํ‹€ ๊ตฌ์„ฑ

  • ๊ด€๋ จ PR : #81
  • ์ž‘์—… ์Šคํƒ€์ผ์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ์ฒซ API๋Š” ํ™”๋ฉด์„ ๊ณต์œ ํ•˜๋ฉฐ ํŒ€์› ์ „์›์ด ์ฐธ์—ฌํ•˜์—ฌ ๊ฐœ๋ฐœํ•˜์˜€๋‹ค.

๐Ÿ“Œ [BE] Datebase ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ

  • ๊ด€๋ จ issue : #15
  • user ํ…Œ์ด๋ธ”email ์ปฌ๋Ÿผ ์ถ”๊ฐ€
    • ๊ธฐ์กด์— user ํ…Œ์ด๋ธ”์—์„œ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์ •๋ณด์—์„œ name ๋งŒ ์ €์žฅํ•˜๋„๋ก ํ–ˆ๋Š”๋ฐ, API๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ email์ •๋ณด๋„ ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•จ์„ ๋Š๊ปด ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.
  • milestone ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ผ๋ถ€ ์ˆ˜์ •
    • ์ด์Šˆ open, close ๊ด€๋ฆฌํ•˜๋˜ ์ปฌ๋Ÿผ์„ ์‚ญ์ œํ•˜์˜€๋‹ค.
    • ๋งค issue ๋ณ€๊ฒฝ ์‹œ๋งˆ๋‹ค ํŠธ๋ฆฌ๊ฑฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ํšŸ์ˆ˜๋ณด๋‹ค, milestone ํŽ˜์ด์ง€์— ์‚ฌ์šฉ์ž๊ฐ€ ๋“ค์–ด์™”์„ ๋•Œ issue ๊ฐœ์ˆ˜๋ฅผ ํ™•์ธํ•˜๋Š” ํšŸ์ˆ˜๊ฐ€ ์ ์„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•ด์„œ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค.
  • issue_label table ์ˆ˜์ •
    • ์—ฐ๊ฒฐ์ด user table์— ์ž˜๋ชป ์—ฐ๊ฒฐ ๋˜์–ด ์žˆ์–ด์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜์ •

๐Ÿ“Œ [BE] VSCode ํ™•์žฅ ํŒŒ์ผ ์„ค์ •

  • eslint, prettier์˜ ์„ค์ •์„ ํŒ€์› ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ์„ค์ •ํ•˜์˜€๋‹ค.
    • eslint ์ŠคํŽ˜์ด์Šค 2์นธ, ; ์ƒ์„ฑ