CRA를 사용하지 않고 React 프로젝트 생성하기

CRA (create-react-app)는 매우 편리한 리액트 프로젝트 빌드 도구입니다.

Webpack, Babel 등 설정하기가 까다롭고 시간이 들어가는 작업을 한번에 해주니까요.

그러나 Babel 혹은 Webpack의 설정을 건드려야 할 경우, eject를 통해 숨겨져 있던 설정 파일들을 끄집어 내야 하는 번거로움이 존재합니다.

리액트 프로젝트를 생성할 때 Webpack과 Babel을 어떻게 구성해보는지 알아보기 위해서
한번 직접 리액트 프로젝트를 구성해 봤습니다.

다만 제가 구성한 방법으로는 Debugging에 몇몇 문제가 있어 실제 제작 프로젝트에는
CRA를 이용해 boiler plate를 생성하려고 합니다.

설치

이 프로젝트에서는 yarn을 사용합니다.

최초로 yarn init명령을 실행해 package.json 파일을 생성합니다.

yarn init -y

리액트의 핵심인 모듈들을 설치해줍니다.

이 때 build 이후에 module은 사용하지 않으므로 (개발환경에서만 사용하므로) devDependency로 설치합니다.

yarn add -D react react-dom

그리고 모듈 번들러인 Webpack을 설치합니다.

yarn add -D webpack webpack-cli webpack-dev-server

웹팩에 번들링에 필요한 바벨을 설치합니다.

yarn add -D babel-loader css-loader style-loader file-loader
  • babel-loader : JSX 및 ES6+ 문법을 트랜스파일링
  • css-loader : CSS 파일을 자바스크립트가 이해할 수 있도록 변환
  • style-loader : 변환된 CSS 파일을 style 태그로 감싸서 삽입
  • file-loader : 이미지 및 폰트 등의 파일 로딩

마지막으로 웹팩으로 번들링 할 때 필요한 플러그인들을 설치합니다.

  • html-webpack-plugin : HTML 파일에 번들링된 자바스크립트 파일을 삽입해주고 번들링된 결과가 저장되는 폴더에 옮겨줌
  • clean-webpack-plugin : 번들링을 할 때마다 이전 번들링 결과를 제거함
yarn add -D html-webpack-plugin clean-webpack-plugin

폴더 구조

이 보일러 플레이트에서 사용할 폴더들은 다음과 같습니다.

  • dist : 빌드된 파일들이 생성
  • src : 컴포넌트, 유틸 등 소스 파일들이 위치함
  • public : 빌드할 때 참고할 html 등 정적 파일

여기서 dist 폴더의 경우 build 명령을 수행할 때 자동으로 만들어 주므로 생성하지 않아도 괜찮습니다.

mkdir src public dist

바벨 설정

바벨 설정 파일인 babel.config.js 를 작성합니다.

module.exports = function (api) {
  api.cache(true);

  const presets = ["@babel/preset-env", "@babel/preset-react"];

  const plugins = [];

  return {
    presets,
    plugins,
  };
};

웹팩 설정

웹팩 설정 파일인 webpack.config.js 를 작성합니다.

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "development",
  entry: {
    main: "./src/index.js",
  },
  resolve: {
    extensions: [".js", ".jsx"],
  },
  devtool: "eval-cheap-source-map", // source-map을 설정하는 부분
  devServer: {
    contentBase: path.join(__dirname, "dist"), // 이 경로에 있는 파일이 변경될 때 번들을 다시 컴파일
    compress: true, // Enable gzip compression for everything served
    port: 8080, // 각자의 portNumber 작성
    hot: true, // 모듈의 변화된 부분만 자동으로 리로딩하는 HMR(Hot Module Replacement)
    overlay: true, // 에러가 발생했을 때 브라우저에 띄울 것인지
    writeToDisk: true, // 메모리 뿐만 아니라 직접 파일로 만들 것인지
    open: true, // Tells dev-server to open the browser after server had been started. Set it to true to open your default browser.
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/, // 컴포넌트 파일을 읽어오는 규칙입니다.
        exclude: "/node_modules/",
        loader: "babel-loader",
      },
      {
        test: /\.css$/, // 스타일 속성 파일을 읽어오는 규칙입니다.
        use: [{ loader: "style-loader" }, { loader: "css-loader" }],
      },
      {
        test: /\.jfif$/,
        loader: "file-loader",
        options: {
          name: "[name].[ext]",
        },
      },
    ],
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // index.html에 output에서 만들어진 bundle.js를 적용하여, dist에 새로운 html 파일 생성
      template: `./public/index.html`,
    }),
  ],
};

몇가지 중요한 속성을 살펴보겠습니다.

  • entry : 모듈의 의존성이 시작되는 부분으로 이름을 지정할 수 있고 여러개를 만들 수 있음.
  • resolve : 웹팩이 모듈을 처리하는 방식 정의하는 것으로 확장자를 생략하고도 인식하게 함.
  • devtool : 참고링크 source-map을 설정하는 부분으로 에러가 발생했을 때 번들링된 파일에서 어느 부분에 에러가 났는지를 쉽게 확인할 수 있게 해주는 도구.
  • devServer : webpack-dev-server의 옵션을 설정해주는 부분. 자세한 설명은 주석으로 작성했습니다.

package.json 에 script 추가

webpack-dev-server 에 --progress 옵션을 주는 경우, console에 결과가 나타납니다.

(Output running progress to console)

{
  "scripts": {
    "start": "webpack-dev-server --progress --mode development",

    "build": "webpack --progress --mode production"
  }
}

리액트 컴포넌트 생성

public 폴더에 다음과 같은 index.html 파일을 생성합니다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>리액트 프로젝트 시작</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src 폴더에 entry point 파일과 (index.js), 컴포넌트 파일을 생성합니다. (App.jsx)

// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.querySelector("#root"));
// App.jsx
import React from "react";

const App = () => {
  return <div>hello world!</div>;
};

export default App;

실행

개발환경의 경우 다음 명령어를 통해 실행해 볼 수 있습니다.

yarn start

빌드

다음 명령어를 통해 빌드해 볼 수 있습니다.

yarn build

타입스크립트 개발 환경 구성해보기

우선 타입스크립트와, 타입 정의 파일들을 설치해야합니다.

yarn add -D typescript @types/react @types/react-dom

타입스크립트 명령어를 사용하면 typescript 설정 파일을 생성할 수 있습니다.

npx typescript --init

tsconfig.json 파일이 자동으로 생성됩니다.

여기서 리액트 jsx 코드를 사용하기 위해서는 compilorOptions의 jsx 속성에 "react" 값을 추가합니다.

{
  "compilorOptions": {
    "jsx": "react"
  }
}

Webpack으로 빌드하기 위해 ts-loader를 설치합니다.

yarn add -D ts-loader

그리고 Webpack 설정 파일에 다음 내용들을 추가합니다.

module.exports = {
  // 엔트리 포인트
  entry: "./src/index.tsx",

  // 빌드 결과물을 dist/main.js에 위치
  output: {
    filename: "main.js",
    path: __dirname + "/dist",
  },

  resolve: {
    // 파일 확장자 처리
    extensions: [".ts", ".tsx", ".js"],
  },

  module: {
    rules: [
      // .ts나 .tsx 확장자를 ts-loader가 트랜스파일
      { test: /\.tsx?$/, loader: "ts-loader" },
    ],
  },
};

'공부' 카테고리의 다른 글

Git Hooks + commitlint를 이용한 커밋 메시지 검사  (0) 2020.08.16

커밋 메시지 자동 검사

Git Hooks와 commitlint를 이용해 작성한 커밋 메시지가 유효한지 검사할 수 있습니다.

다음 글들을 참고하시면 더 많은 정보를 얻으실 수 있습니다.

 

 

Conventional Commits | 쿡앱스 기술 블로그

Conventional Commits 본 포스팅의 완성 된 예제 소스 는 GitHub에 올려져 있습니다. 개요 Conventional Commits 이란 git 으로 commit 시에 일괄된 양식을 유지하고 그 양식을 바탕으로 버전 관리나 Change Log 를 만

blog.cookapps.io

 

 

conventional-changelog/commitlint

📓 Lint commit messages. Contribute to conventional-changelog/commitlint development by creating an account on GitHub.

github.com

Git Hooks

 

 

Git - Git Hooks

It’s important to note that client-side hooks are not copied when you clone a repository. If your intent with these scripts is to enforce a policy, you’ll probably want to do that on the server side; see the example in An Example Git-Enforced Policy.

git-scm.com

프로젝트의 .git 폴더 내부는 다음과 같이 구성되어 있습니다.

├── COMMIT_EDITMSG
├── HEAD
├── ORIG_HEAD
├── config
├── description
├── hooks
├── index
├── info
├── logs
├── objects
└── refs

이 중 hooks 폴더에 들어가면 다음과 같은 sample 파일들을 만나게 됩니다.

├── applypatch-msg.sample
├── commit-msg.sample
├── fsmonitor-watchman.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-merge-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
└── update.sample

각각의 파일들은 쉘 스크립트 파일들이며, .sample을 지울 경우 git의 각 과정에서 작동합니다.

이 중에서 commit-msg 파일을 작성해 commit message 작성 후 convention 검사를 진행할 수 있습니다.

정규표현식을 사용해서 commit 메시지 체크하기

# exit with an error
exit 1

각 과정에서 위와 같은 방식으로 종료할 경우, 그 commit (이 외에 다른 동작들도) 은 자동적으로 취소됩니다.

따라서 다음과 같은 sh 파일을 만들 수 있습니다.

config=commit-msg.config.json

# set variables
enabled=$(jq -r .enabled $config)
revert=$(jq -r .revert $config)
types=($(jq -r '.types[]' $config))
min_length=$(jq -r .length.min $config)
max_length=$(jq -r .length.max $config)

if [[ ! -f $config || ! $enabled ]]; then
    exit 0
fi

regexp="^("

if $revert; then
    regexp="${regexp}revert: )?(\w+)("
fi

for type in "${types[@]}"
do
    regexp="${regexp}$type|"
done

regexp="${regexp})(\(.+\))?: "

regexp="${regexp}.{$min_length,$max_length}$"

msg=$(head -1 $1)

if [[ ! $msg =~ $regexp ]]; then
  echo -e "\n\e[1m\e[31m[INVALID COMMIT MESSAGE]"
  echo -e "------------------------\033[0m\e[0m"
  echo -e "\e[1mValid types:\e[0m \e[34m${types[@]}\033[0m"
  echo -e "\e[1mMax length (first line):\e[0m \e[34m$max_length\033[0m"
  echo -e "\e[1mMin length (first line):\e[0m \e[34m$min_length\033[0m\n"

  # exit with an error
  exit 1
fi
// commit-msg.config.json

{
    "enabled": true,
    "revert": true,
    "length": {
        "min": 1,
        "max": 52
    },
    "types": [
        "build",
        "ci",
        "docs",
        "feat",
        "fix",
        "perf",
        "refactor",
        "style",
        "test",
        "chore"
    ]
}

위 스크립트는 json 파일로 설정을 읽어들여오고 정규표현식을 이용해 검사하는 코드입니다.

huscky & commitlint를 이용한 검사

git hook 을 트리거 하는 용도로 npm 모듈인 huscky를 사용할 수 있습니다.

commitlint의 경우 commit 에 대한 lint를 확인하여 성공/실패를 리턴합니다.

다음 명령을 .git 폴더가 있는 프로젝트의 root에서 실행해주세요

yarn add husky @commitlint/cli @commitlint/config-conventional -D

husky가 githook 을 덮어쓰기 때문에 husky 설정 이전에 repo를 먼저 초기화 해야 합니다.

그리고 package.json에 다음과 같은 내용을 추가합니다.

  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "commitlint": {
    "extends": [
      "@commitlint/config-conventional"
    ]
  }

위 명령은 husky에서 hooks가 일어났을 때, commit-message에서 commitlint를 검사한다는 내용입니다.

commitlint에서 extends로 node_modules 내부의 @commitlint/config-conventional 에 존재하는 index.js 파일에서 설정을 읽어 오는데요,

설정 파일은 다음과 같습니다.

module.exports = {
    parserPreset: 'conventional-changelog-conventionalcommits',
    rules: {
        'body-leading-blank': [1, 'always'],
        'body-max-line-length': [2, 'always', 100],
        'footer-leading-blank': [1, 'always'],
        'footer-max-line-length': [2, 'always', 100],
        'header-max-length': [2, 'always', 100],
        'scope-case': [2, 'always', 'lower-case'],
        'subject-case': [
            2,
            'never',
            ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
        ],
        'subject-empty': [2, 'never'],
        'subject-full-stop': [2, 'never', '.'],
        'type-case': [2, 'always', 'lower-case'],
        'type-empty': [2, 'never'],
        'type-enum': [
            2,
            'always',
            [
                'build',
                'chore',
                'ci',
                'docs',
                'feat',
                'fix',
                'perf',
                'refactor',
                'revert',
                'style',
                'test',
            ],
        ],
    },
};

package.json의 commitlint.extends 부분을 직접 작성한 옵션으로 변경하면, 프로젝트에 적합한 commit convention을 적용할 수 있습니다.

'공부' 카테고리의 다른 글

CRA를 사용하지 않고 React 프로젝트 생성하기  (0) 2020.08.16

프로젝트를 하면서, 혹은 인턴 생활을 하면서 "좋은 동료"가 되자 라는 말은 끊임없이 들었었다.

 

시니어 개발자들이 주니어 개발자들에게 하는 조언이기도 하다.

 

 

"좋은 동료를 찾는것도 좋지만, 좋은 동료가 되자"

 

 

최근 블라인드에서 글을 보면, 동료 때문에 퇴사하는 이야기를 많이 볼 수 있었다.

 

사람과 사람 사이에 일을 하면서 갈등이 없을 수는 없지만,

 

동료 때문에 좋은 직장을 떠난다는 것은 그만큼

"나쁜 동료가 나에게 끼치는 영향이 크다" 는 것을 나타낸다고 생각한다.

 

(아니면 이 바닥이 이직이 자유로워서 떠나는 좋은 핑계를 찾는걸지도...)

 

 


http://www.newsprime.co.kr/news/article/?no=346211

 

직장인 10명 중 8명 "싫어하는 동료 있다"

[프라임경제] 다양한 사람이 모여 관계를 맺고 업무를 진행하는 회사에서 싫어하는 동료는 생기기 마련. 이 중 업무를 미루거나 책임을 회피하는 동료를 제일 싫어하는 것으로 나타났다.구인구�

www.newsprime.co.kr

일반적으로 취직을 준비할 때 중요하게 생각하는 점은 연봉과 복지 라고 생각한다. 

 

취준생의 입장에서 이 이상은 고려하기가 쉽지 않은것이 사실이다.

 

NAVER에서 인턴을 짧게 진행하면서, 우연치않게 부서를 이동할 일이 생겼었다.

 

그 때 정말 부바부 (부서 by 부서) 사바사 (사람 by 사람)이라고 말 할 수 있을 정도로 다른 분위기의 부서를 경험할 수 있었다.

 

그 때 가장 먼저 느꼇던 점은 "과연 나한테 맞는 부서로 갈 확률은 얼마나 될까?" 였다.

 

그리고 이 고민의 시작점은 당연하게도 주위 동료였다.

 

동료! 동료! 동료!

http://mediask.co.kr/36901

 

너무 싫어하는 사람과 한 팀이 되었어요

말투부터 업무 방식까지 전혀 맞지 않는 동료와 함께 일하게 되었나요?

mediask.co.kr

팀을 꾸리게 되었을 때, 나와 정말 잘 맞는 사람과 팀이 될 수도 있고 반대의 경우도 존재할 것이다.

 

만약 자기 팀원이 전부 마음에 들고 너무 좋은사람이라고??

 

 

 

 

팀원한테 진지하게 물어보자...

사실 팀원들이 자기한테 잘 맞춰주고 있는것은 아닐까??

 

 

상대방이 나한테 영향을 주는 만큼, 자기도 상대방한테 영향을 줄 수 있다는 점을 항상 기억해야 한다.

 

"우리가 그 심연을 오랫동안 들여다본다면, 심연 또한 우리를 들여다보게 될 것이다."

 


나는 어떤 동료였나?

이제부터 지금까지 내가 경혐했던 팀 프로젝트를 나열하고, 좋은 동료에 대한 생각이 어떻게 변화했는지 적어보려 한다.

 

많지 않은 팀 프로젝트를 경험했지만, 어쩌다보니 굵직한 사건들은 다 경험했기 때문에 동료에 대해 가지는 생각의 변화는 매우 컸다.

 

이런 경험들이 "함께 성장하는 개발자" 를 목표로 삼게 된 배경이기도 하다.

 

 

 

일만 잘하면 장땡이다! 하드캐리형

국내 모 대기업의 회사 분위기 이기도 하다. (물론 완전하게 일반화 하기는 힘들겠지만.)

 

대부분의 대학교 프로젝트시에 많이 보이는 유형이라고 생각한다.

 

대학교 프로젝트의 경우 팀원은 다음과 같이 구성되는데,

 

  • 학점을 잘 받으려는 사람
  • 학점에 욕심이 없는 사람
  • 수업에 욕심이 없는 사람

그리고 여기에 대학생 특유의 특성을 하나 고려해야하는데...

 

대학생은 제일 나약한 존재이다

바로 대학생은 "귀찮음"에 매우 취약하다는 것이다.

 

이런 분위기에서 팀 프로젝트를 진행할 경우, 욕심있는 친구가 하드캐리하는 모습이 될 수밖에 없는 구조이다.

 

자기는 대학 팀 프로젝트에서 굉장히 만족스러웠다고???

같이 있던 팀원이랑 이번기회에 꼭 연락하기를 바란다. 절대 놓치지 마세요~~~

 

 

흔한 대학교 팀플

 

사실 자기자신이 이런 유형이고, 지금까지 팀원들을 의욕적인 사람을 만나보지 못했다면 협업을 하는데 큰 어려움을 느끼지 못했을 것이다.

 

물론 협업이 끝나고 나서 "나는 다른사람이랑 다른가봐" 던가 "나는 얘네보다는 뛰어나구나" 라는 선민의식 비슷한 감정을 가지게 될 수도 있다.

 

개인적으로 이런 생각이 가장 위험하다고 생각한다.

 

자기보다 잘하는 사람과 같은 팀이 되었을 때, 자존심때문에 버티질 못하기 때문이다.

 

아 물론 내 이야기는 아니고 ㅎㅎ;;

 

이번 생은 틀린것같아요... 책임감 없는 유형

책임감은 프로젝트에 자신의 자원을 지속적으로 투자하기 위한 원동력이다.

 

Q: 에이 같이 프로젝트를 하는 팀원이 대놓고 책임감이 없는걸 어필 하는 경우가 있나요??

A: 어필하지 않고 도망가버립니다 ^^

 

아니 이 좋은걸 기업에선 왜 진부하다고 느끼는거지???

 

개인적인 사정이던, 아니면 개인적인 감정이던 간에

프로젝트를 진행하면서 팀원들의 태도에는 변화가 있을 수 밖에 없다.

 

특히 이직등의 이유로 프로젝트를 중도 하차하는 경우가 생각보다 잦은 점을 항상 염두해두어야 한다.

 

(특히 개발자 이바닥은 이직이 자유롭기 때문에...)

 

필자도 팀 프로젝트 도중에 팀원 한명이 취업으로 도망친 경우가 있는데,

처음에 끝까지 함께 하겠다고 하고 그만둔 경우이기 때문에 ㅎㅎ....

 

 

 

물론 학생 시절에 이런 경험을 해보는 것은 큰 도움이 된다.

당장 자기소개서에 "어려움을 극복했다" 라는 점으로 어필할 수 있기 때문이지

 

그렇다고 팀원을 위해서 직접 탈주닌자가 되지는 말자...

 

아 모르겠고 내가 맞아! 독불장군형

 

한국 개발자가 가장 잘하는 언어는 일단 한국어는 아닐거같다.

 

NAVER에서 느꼇던 문화는, 개발자는 자기 코드에 자신감이 있어야한다 였다.

그렇다고 자기 코드에 너무 애착을 가지는 점도 바람직하지 못하다.

 

이런 나르시스트 적인 생각을 타파하기 위해서 나는 다음과 같은 노력을 하고있다.

 

내 코드는 똥이다

내 코드는 똥이다!!!!

 

바로 자기 코드를 똥이라고 생각하는 것이다. 

 

좋은 프로그램이란 다양한 상황에 유연하게 대처할 수 있는 코드이다.

자신의 생각이 당장 지금은 올바를 지 모르지만, 미래에도 가장 최적의 답일지는 아무도 모르기 때문이다.

 

이 자기주장 강한 친구들과 함께 팀 프로젝트를 수행해야한다

여런분이 동료와 커뮤니케이션이 힘들다고?

그러면 적어도 자기 자신은 커뮤니케이션이 어려운 동료가 되지는 말자

 


그러면 좋은 동료는 뭐에요??

얼마전 모 기업의 자기소개서를 쓰면서 가장 고민했던 부분이 바로 이부분이다.

 

"개발자가 되기 위해 필요한 자질이 뭐라고 생각하세요?"

 

내가 내린 답은 다음 두가지였다.

  • 빠른 시간 내에 학습할 수 있는 능력.
  • 다른 사람을 설득할 수 있는 능력.

빠른 시간 내에 학습할 수 있는 능력

항상 환경은 완벽하지 않다. 완벽하지 않은 상황에서 성과를 내야하고, 되도록 성공적으로 마무리지어야한다.

 

열악한 환경의 대표적인 경우 중 하나는 바로 "시간"이다.

 

지금 당장 해야한다!

대부분의 경우 기술에 대해 충분하게 학습할 시간을 주고 업무에 투입하는 것이 아니라

학습과 병행하며 실제 업무를 수행해야 한다.

 

거기에 기술은 항상 빠르게 변화하기 때문에 빨리 배우는 능력은 

  • 커리어를 유지하기 위해서라도
  • 성과를 내기 위해서라도
  • 동료에게 피해를 주지 않기 위해서라도

중요하다.

 

다른 사람을 설득할 수 있는 능력

이는 달리 말하면 커뮤니케이션 능력일 것이다.

 

특히나 페어 프로그래밍 (짝 코딩) 등 동료와 1:1로 접할 기회가 많은 개발자들에게 이는 훨씬 더 중요할 수 있다.

 

뭐가 자꾸 산으로가네?

https://zdnet.co.kr/view/?no=20160425094045

 

개발자의 생명은 커뮤니케이션 능력

프로그래머(programmer)와 개발자(developer)는 비슷하지만 동의어가 아니다. 코딩을 통해서 컴퓨터에게 원하는 일을 시킬 수 있으면 모두 프로그래머지만, 학교 ...

zdnet.co.kr

특히나 좋은 코드를 위해 동료와 토론하거나, 가볍게 이야기 할 때 커뮤니케이션 능력이 부족할 경우 동료를 설득 시키기 힘든건 당연지사

 

오히려 최악의 경우 동료와 트러블이 생겨 "싫은 동료"로 낙인찍힐 수도 있다.

 

그렇다면 좋은 커뮤니케이션을 위해선 어떻게 해야 할까?

 

내가 내린 답은 "공감"이다.

 

  • 상대방의 말을 경청하고
  • 상대방의 입장을 고려하고
  • 상대방에게 적합한 조언 혹은 공감을 해준다.

이 세가지 모두 너무 중요하다.

 

동료와 이야기를 하는데, 동료가 말을 끊어버리면 무슨 생각이 드는가?

혹은 동료가 현재 상황을 고려하지 않고 자기 말만 한다면??

 

동료는 여러분의 샌드백이 아닙니다.


함께 성장하는 개발자가 되자

개발 직군은 동료와의 관계도 중요하고, 개인적인 역량도 중요한 직군이다.

 

내가 찾은 답은 동료에게 긍정적인 영향을 줄 수 있는 촉매가 되자는 것이였다.

 

지금도 좋은 기회를 얻어 동료들과 성장할 수 있는 기회를 얻었는데,

 

동료를 긍정적인 방향으로 성장시키고, 나도 긍정적인 방향으로 성장하는 좋은 문화를 이어나가려고 노력하고 있다.

 

 

물론 사람마다 좋은 동료에 대한 관점은 다르겠지만, 적어도 좋은 문화를 만들기 위해 노력하는 것을 서로 이해하면 공통 분모를 찾을 수 있을 것이다.

 

함든 개발자의 삶을 택한 동료들, 친구들과 조금이라도 더 좋은 미래를 만들기 위해서...

 

 

희망찬 미래로 나아가자

 

이벤트 위임을 이용해 블록 옮기기 구현하기

투두리스트

https://move-block-event-delegation.herokuapp.com/index.html 로 가시면 예제 데모를 확인하실 수 있습니다.

 

이 글에 나온 예제소스는 https://github.com/changicho/move_block-event_delegation 에서도 확인할 수 있습니다.

 


블록의 이동을 구현할 때, 각 블록마다 이벤트를 등록하진 않으셨나요?

 

각각 이벤트를 등록하고 잘 동작하게 구현하셨다면 다음 글을 읽어보시는것은 어떨까요?

왜 이벤트 위임(delegation)을 해야 하는가?

성능 측정 결과는 매우 실망스러웠다.
각주의 추가 삭제를 반복할수록 메모리 누수로 인해 메모리 사용량이 누적되는 것을 발견했다.
무슨 이유로 메모리 사용량이 누적되는 것일까?

 

각 블록들이 추가와 삭제가 빈번하게 일어난다면, 이벤트를 등록하고 삭제해줘야 합니다.

만약 등록된 이벤트를 삭제하지 않는다면 메모리 누수가 발생할 수 있어요.

 

그렇다면 어떻게 하나의 이벤트에서 하위 이벤트들을 관리할 수 있을까요?

저는 이벤트 위임을 이용해 이를 구현했습니다.

 

그렇다면 이벤트 위임이란 무엇일까요??

이벤트 위임

javascript.info event-delegation

이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다.

쉽게 말하면 각각 개별 이벤트를 등록해야할 자식들 대신, 부모에 이벤트를 등록하고 분기처리 하는 것입니다.

 

다음과 같은 구조의 html이 존재한다고 가정해봅시다.

<div class="parent">
  <div class="child1"></div>
  <div class="child2"></div>
  <div class="child3"></div>
</div>

 

그리고 각각 다음과 같은 이벤트를 등록한다고 해봅시다.

// child1, 2, 3는 querySelect 등으로 찾아왔다고 가정합시다.

child1.addEventListener("click", () => {
  console.log("child1");
});
child2.addEventListener("click", () => {
  console.log("child2");
});
child3.addEventListener("click", () => {
  console.log("child3");
});

 

위와 같은 코드는 잘 동작합니다. 하지만 child가 매우 많아진다면 어떻할까요??

<div class="parent">
  <div class="child1"></div>
  <!-- ~ -->
  <div class="child1000000"></div>
</div>

 

이 경우에는 querySelectorAll로 찾아서 하나하나 등록하는 방법도 있겠지만, 매우 번거롭습니다.

 

이 경우에 다음과 같은 것은 어떨까요?

parent.addEventListener('click',(event)=>{
  const class = event.target.className;

  switch(class){
    case 'child1':{
      console.log('chlid1');
    }
    // ...
  }
})

하나의 이벤트로 여러개의 이벤트를 대신할 수 있습니다.

 

더 최적화 하고 싶다면 다음 코드는 어떨까요??

parent.addEventListener('click',(event)=>{
  const class = event.target.className;

  console.log(class); // 단순히 자기 자신의 class를 표시할 수 있습니다.
})

각 child div마다 dataset등을 설정해서, 하나의 로직으로 처리하게 할 수도 있습니다.

 

블록 이동 만들어보기

사용할 예제 소스

그렇다면 이제 블록 이동을 만들어 볼까요??

우선 다음과 같은 구조의 html을 만들어주세요

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>블록 옮기기 테스트</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <ul class="first">
      <li class="start"></li>
      <li>
        <p>하나</p>
      </li>
      <li>
        <p>둘</p>
      </li>
    </ul>
    <ul class="second">
      <li class="start"></li>
      <li>
        <p>셋</p>
      </li>
    </ul>
    <ul class="third">
      <li class="start"></li>
      <li>
        <p>넷</p>
      </li>
    </ul>
    <div class="hover"></div>
  </body>
  <script src="./main.js"></script>
</html>
body {
  display: flex;
  flex-direction: row;
}

ul {
  width: 300px;
  height: 600px;

  margin: 10px;
  padding: 10px;

  background-color: #dddddd;
  border-radius: 10px;
}

li {
  list-style: none;
  padding: 10px;
  margin: 10px 0 10px 0;

  width: 280px;

  background-color: #888888;
  border-radius: 10px;

  text-align: center;
  color: #ffffff;
  user-select: none;
}

li.start {
  height: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}

.hover {
  position: absolute;
  padding: 0;
  margin: 0;

  transform: rotate(-5deg);
}

.temp {
  opacity: 0.5;
}

각 ul은 블록을 쌓을 column을, li는 블록을 의미합니다.

 

이 때 편의상 ul의 맨 처음에는 시작 ul (start point)을 넣음에 유의해주세요.

클릭 구현하기

다음과 같은 기본 javascript 코드를 생성합니다.

 

타겟으로 설정할 element를 찾아옵니다.

const body = document.querySelector("body");
const hover = document.querySelector(".hover");

전역 변수로 사용할 변수들을 설정해주세요

let clicked = false;
let hoverLi = undefined;

다음으로 이벤트 핸들러들을 각각 작성해줍니다

function mousedown(event) {
  // 마우스 왼쪽클릭 & 터치가 아닌 경우 예외처리합니다.
  if (event.button !== 0) {
    return;
  }
  // 클릭했으므로 clicked를 설정합니다.
  clicked = true;
  // 현재 클릭한 element에서 가장 가까운 li 태그를 찾습니다.
  targetRemove = event.target.closest("li");
  if (targetRemove === null || targetRemove.className === "start") {
    // 만약에 li태그를 찾지 못했거나, 시작점을 클릭했다면 return합니다.
    return;
  }

  // 현재 삭제하려고 하는 target은 li태그입니다.
  targetLi = targetRemove;
  // 내부 값을 복사한 element를 마우스를 따라다닐 hover로 설정합니다.
  hoverLi = targetRemove.cloneNode(true);
  // target을 불투명하게 하기 위해 class를 넣어주세요
  targetLi.classList.add("temp");

  const { pageX, pageY } = event;

  // hover에 아까 clone한 element를 붙여넣어줍니다.
  hover.appendChild(hoverLi);

  // 마우스 중앙에 hover가 오도록 설정합니다.
  hover.style.left = pageX - hover.offsetWidth / 2 + "px";
  hover.style.top = pageY - hover.offsetHeight / 2 + "px";
}
function mouseup() {
  // 클릭되지 않은 상태면 실행하지 않습니다.
  if (!clicked) {
    return;
  }

  clicked = false; // 클릭이 종료되었으므로 했으므로 clicked를 설정합니다.
  if (targetLi) {
    // targetLi가 있으면 class를 제거해주세요
    targetLi.classList.remove("temp");
  }
  if (hoverLi) {
    // hoverLi는 hover가 끝나므로 더이상 필요하지 않습니다.
    hoverLi.remove();
  }
  // 아래 전역변수들을 초기화해주세요
  hoverLi = undefined;
  targetLi = undefined;
}

이벤트 리스너를 등록해줍니다.

body.addEventListener("mousedown", mousedown);
body.addEventListener("mouseup", mouseup);

 

자세한 설명은 주석으로 대신하겠습니다.

 

위 코드를 적용하면 다음과 같은 결과를 얻을 수 있습니다.

 

클릭하면 hover가 발생합니다

 

해당 li를 클릭할 때, hover가 구현되는것을 알 수 있습니다.

 

hover로 이동된 target 말고 기존 자리에 임시 li도 남아있는것을 확인할 수 있습니다.

이동 구현하기

이제 제일 중요한 이동을 구현해볼 건데요,

 

이동이 다음 순서로 일어남에 유의해주세요

  1. 마우스 클릭
    1. li태그를 찾음
    2. hover에 표시
  2. 마우스 움직이기 (클릭한 상태로)
    1. hover의 좌표를 마우스 좌표에 맞춰서 변경
    2. 현재 마우스 좌표가 어떤 li 위에 있는지에 따라서 분기처리
      • 현재 li 앞에 이동? 뒤에 이동?
  3. 마우스에서 손 떼기
    1. hover 초기화하기
    2. 전역으로 선언한 부분 초기화하기

그런데 hover가 마우스를 따라다니는 경우에, 마우스 좌표에 존재하는 li를 어떻게 찾을 수 있을까요?

hover가 마우스 좌표를 항상 따라다녀서 hover만을 찾아올 텐데요.

 

이를 위해서 hover를 잠시 가려두는 방법을 사용합니다.

// 잠시 현재 hover element를 가리고 현재 좌표의 element를 가져온다
hover.hidden = true;
// do something
hover.hidden = false;

이렇게 가져오는 element에서 주요하게 고려해야 할 종류는 2가지가 있습니다.

  • li
  • ul

이 때 ul의 경우에는 간단히 맨 위에 붙일 경우와, 맨 아래에 붙일 경우만 나누겠습니다.

 

ul인 경우에 시작 li 보다 좌표가 위에 있다면, 첫번째에 추가하면 되고, 그 외의 경우는 맨 아래에 추가하면 될 것입니다.

 

li의 경우는 좀 복잡한데요, 우선 다음 함수를 작성해주세요.

// element2이 element1보다 앞에 있는지 검사하는 함수입니다.
function isBefore(element1, element2) {
  if (element2.parentNode === element1.parentNode) {
    for (let cur = element1.previousSibling; cur; cur = cur.previousSibling) {
      if (cur === element2) {
        return true;
      }
    }
  }

  return false;
}

위 함수를 이용해서 저희가 추가하려는 element를 뒤에 붙일지 앞에 붙일 지 정할 수 있습니다.

 

따라서 mousemove에 할당할 callback은 다음과 같이 구성됩니다.

function mousemove(event) {
  if (!clicked || !hoverLi) return;

  // pageX, pageY 는 모든 페이지 기반
  // clientX, clientY 는 현제 보이는 화면 기반
  const { pageX, pageY } = event;

  // 잠시 현재 hover element를 가리고 현재 좌표의 element를 가져온다
  hover.hidden = true;
  const elemBelow = document.elementFromPoint(pageX, pageY);
  const li = elemBelow.closest("li");
  const ul = elemBelow.closest("ul");
  hover.hidden = false;

  // 이동할 때마다 hover의 위치를 수정해줍니다.
  hover.style.left = pageX - hover.offsetWidth / 2 + "px";
  hover.style.top = pageY - hover.offsetHeight / 2 + "px";

  // 현재 마우스가 ul을 가리키는 경우에는 li를 찾을 수 없습니다.
  if (!li) {
    // 만약 ul을 가리키고 있는 경우가 맞다면??
    if (ul) {
      const start = ul.querySelector(".start");
      const { top } = start.getBoundingClientRect();

      // 시작점보다 위에있는 경우에 맨 앞에 붙이고
      if (top > pageY) {
        start.parentNode.insertBefore(targetLi, start.nextSibling);
      } else {
        // 그 외에는 맨 아래에 붙이면 되겠네요.
        ul.appendChild(targetLi);
      }
    }
    return;
  }

  // 만약 같은 ul에서 li가 현재 좌표에 있는 target보다 앞에있으면
  // target을 li 앞으로 옮겨줍니다.
  if (isBefore(targetLi, li) && li.className !== "start") {
    li.parentNode.insertBefore(targetLi, li);
  } else if (li.parentNode) {
    // 그 외에는 뒤로 이동시켜 버리면 됩니다.
    li.parentNode.insertBefore(targetLi, li.nextSibling);
  }
}

// 마지막으로 이벤트를 등록해주세요
body.addEventListener("mousemove", mousemove);

 

MDN insertBefore

이 때 insertBefore를 이용해서 이전에 위치했던 targetLi를 이동시켜 버리는 것에 유의해주세요.

만약 주어진 자식 노드가 document에 존재하는 노드를 참조한다면, insertBefore() 가 자식 노드를 현재 위치에서 새로운 위치로 옮깁니다. (노드를 다른 노드에 추가하기 전에 상위 노드에서 제거할 필요가 없습니다)

마우스가 화면을 벗어날 때

만약 마우스가 화면을 벗어나는 경우에는 어떻게 할까요?

 

가장 간단한 방법으로는, 마우스 클릭이 끝났을 때의 동작을 시키는 것입니다.

다음과 같은 방법을 사용합시다.

function mouseleave() {
  // 화면을 벗어났을 때 click한 상태가 아니라면 return합니다.
  if (!clicked) {
    return;
  }
  // mouseup 함수를
  mouseup();
}

body.addEventListener("mouseleave", mouseleave);

자 이렇게 블록이동의 구현을 완료했습니다.

 

어떤가요? 마우스 이벤트 만으로 그럴듯한 블록이동의 구현이 완료되었습니다.

 

drag & drop api를 사용할 경우 몇몇 코드를 삭제할 수도 있습니다.

한번 위 코드를 drag & drop api 에 맞춰서 수정해보세요.

서울역 SK 브로드밴드 타워

2020년 6월 26일 SK 브로드밴드 인턴전형 면접을 보러갔었다.

 

날씨도 흐리고, 서울역이 매우 혼잡해 길을 잃고 헤맸었다.

근처 서브웨이 같은곳에서 샌드위치를 먹고 면접을 보려 했으나, 못찾은 관계로 아무거나 주워먹고 면접을 보러 갔다.

 


30분정도 일찍 도착했었다. 기다리는 사람이 나를 포함해 2명밖에 없었고, 오전반과 오후반의 자리를 분리해놔 실제 층에는 절반의 의자만 채워져있었다.

 

면접 OT 시작 전에 출석하지 않은 인원에게 따로 인사팀 직원분이 전화를 걸어 확인하던데,

 

직접 전화를 걸어서 확인해주는 구나 싶어 놀랐다...

면접온 취준생을 위한 선물

확실히 SK는 선물을 잘준다.

 

작년 SKT에 지원했을 때도 인적성 때도 빠방하게 줬던 기억이 있는데, 브로드밴드는 면접에도 다양한 선물을 줬다.

 

특히 간편식으로 나온 죽은 너무좋았다.

 

SKT의 면접같은 경우는 아침에 과일등 뷔페로 식사를 간단히 해결할 수 있도록 구성했었는데,

올해는 코로나 19 때문에 그런 준비는 하지 못했을것 같다.

 


누구보다 빠르게 난 남들과는 다르게~

 

평소에 말이 빠르다고는 생각하지 않았는데, 면접관님께서 말이 빨라 긴장한것 같다며 생수 한병을 나에게 주셨다.

 

2017년 난생 처음 삼성전자 면접을 봤을 때 있었던 일과 같은 상황이였는데, 그때에 나와 지금의 나는 어떤점이 달라졌는가 잠시 주마등이 보였다...

 

올해 NAVER, 현대카드, SW 마에스트로 11기 등 면접에 참가하며 실전 연습은 충분히 되어 있어서 크게 긴장하지는 않았다.

 

근데 말이 빨라서 긴장한것처럼 느껴졌다니...

 

평범한 면접. 다를것도 특이한 것도 없었다

SKT 면접 때 처럼 12시간 풀타임으로 종류별로 모든 면접을 진행하거나 하지는 않았다.

 

다만 대기시간은 거의 없었던 편으로 스피디하게 진행되어서 너무 마음에 들었다.

 

인사팀에서 시간 배분을 굉장히 잘 해줬구나를 느낄 수 있었다.

 


 

면접자분들 화이팅!

SK가 참 사람을 챙기는 그룹이구나를 느낄 수 있는 면접이였다.

 

그래도 대기업 면접이라 익숙한 구성인건 어쩔 수 없지만...

 

확실히 SK 브로드밴드는 좋은 기업인 것 같다.

+ Recent posts