[카테고리:] IT

다양한 IT 기술에 대한 내용을 공유합니다.

  • [JavaScript] Fetch API

    [JavaScript] Fetch API

    자바스크립트의 fetch 함수는 비동기 통신 API로써 서버에 네트워크 요청을 보내 새로운 정보를 받아올 수 있다. ES6부터 지원하며, 가독성이 매우 뛰어난 장점이 있다. 이곳을 클릭하면 {"message": "hello world"}라는 JSON 데이터 화면이 나온다. fetch API를 이용해 이 JSON을 가져와 보자.

    기본 형태

    fetch API의 기본 형태는 아래와 같다.

    fetch(url, [options])
      .then((res) => res.json())
      .then((res) => {
        // data를 응답 받은 후 로직
      });
    
    • url에는 접근하고자 경로를 넣으면 된다.
    • options에는 methodheader등을 지정하여 요청할 수 있다.
    • options에 아무값도 넘기지 않으면 요청은 GET 메서드로 진행된다.

    화살표 함수를 함수 선언식으로 변경하면 아래와 같다.

    fetch(url, [options])
      .then(function(res) {
        return res.json();
      })
      .then(function(res) {
        // data를 응답 받은 후 로직
      });
    

    위에서 언급한 주소를 입력하여 JSON 데이터를 잘 가져오는지 확인해 본다.

    요청 하기

    fetch('https://recordboy.github.io/ui/dummy/data.json')
      .then(function(res) {
    
        // Response Object
        console.log(res);
    
        // 응답값 JSON 형태로 얻기
        return res.json();
      })
      .then(function(res) {
    
        // 리턴 받은 JSON
        console.log(res);
      });
    
    • 응답값은 첫번째 then에 지정된 함수의 res에 담겨지며, 이 값은 http 응답값을 가지고 있는 Response Object이다.
    • 첫번째 then의 응답값을 JSON 형태로 얻기 위해 Response Objectjson() 함수를 호출하여 값을 리턴한다.
    • 두번째 then에서 응답 받은 JSON을 확인할 수 있다.

    References

    fetch
    fetch() 함수 사용법
    Javascript에서의 비동기 통신

  • [Node.js] http 서버 만들기

    [Node.js] http 서버 만들기

    http 서버 만들기

    Node.js 환경에서 간단한 http 서버를 만들어 보자.

    Node.js는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임(특정 언어로 만든 프로그램들을 실행할 수 있는 환경)이다. 기존의 자바스크립트는 웹 브라우저에서만 실행할 수 있었지만, Node.js를 사용하면 자바스크립트를 서버 환경에서도 사용할 수 았다.

    노드 환경에서 서버를 만들려면 노드 기본 모듈인 http가 필요하다. 이 모듈은 말그대로 HTTP의 각종 기능을 가지고 있다. 모듈을 불러오고 아래 코드를 입력한다.

    HTTP 모듈 생성

    const http = require("http");
    
    http
      .createServer((req, res) => {
        res.writeHead(200, {
          "Content-Type": "text/html; charset=utf-8",
        });
        res.write("<div>hello world</div>");
        res.end("<div>end</div>");
      })
    
      .listen(8080, () => {
        console.log("8080포트에서 서버 대기중");
      });
    

    아래 명령어를 시용하여 서버를 구동시켜 보자.

    $ node server
    

    8080포트에서 서버 대기중이란 문구가 터미널에 출력되면 http서버가 정상 작동하는 것이다. http://localhost:8080 주소로 들어가보면 hello world를 확인할 수 있다.

    • http 모듈의 createServer메서드로 서버 객체를 생성하였고 요청이 들어올 때 마다 매번 이 콜백함수가 실행된다. 콜백의 인수로 res(request)는 요청에 관한 정보들을, res(respone)은 응답에 관한 정보들을 담고있다.


    • res.writeHead 메서드는 응답에 대한 정보를 기록하는 메서드이며, 첫번째 인수로 요청이 성공했다는 상태 코드 200을 넣어주고, 두번째 인수로 응답에 대한 정보인 형식(HTML)과 문자셋(utf-8)를 넣어준다. 이 정보가 기록되는 부분을 헤더(header) 라고 한다.


      상태 코드

      res.writeHead 메서드의 첫번째 인수로 넣어주는 200이나 500과 같은 숫자는 응답 상태를 나타내는 값으로서 브라우저는 상태 코드를 보고 요청이 성공/실패했는지 판단한다.


      • 2XX: 성공을 알리는 상태코드며, 대표적으로 아래가 있다.

        • 200(성공)

        • 201(작성됨)



      • 3XX: 리다이렉션(다른 페이지로 아동)을 알리는 상태코드로 넘겨받은 URL을 브라우저가 열려고 하면 다른 URL로 갈 때 이 코드가 사용된다. 대표적으로 아래가 있다.

        • 301(영구 이동)

        • 302(임시 이동)

        • 302(수정되지 않음, 요청의 응답으로 캐시를 사용했음)



      • 4XX: 요청 오류를 알리는 코드로서 요청 자체에 오류가 있을 때 표시된다. 대표적으로 아래가 있다.

        • 400(잘못된 요청)

        • 401(권한 없음)

        • 403(금지됨)

        • 404(찾을 수 없음)



      • 5XX: 서버 오류를 나타내며, 요청은 제대로 왔지만 서버에 오류가 있을 때 발생한다. 이 오류는 res.writeHead에서 직접 보내는 경우는 거의 없고, 예기치 못한 에러가 발생 시 서버가 알아서 5XX대 코드를 보낸다. 대표적으로 아래가 있다.

        • 500(내부 서버 오류)

        • 502(불량 게이트웨이)

        • 503(서비스를 사용할 수 없음)




    • res.write 메서드는 클라이언트로 보낼 데이터이며, 인수로 지정한 값이 바디 부분의 컨텐츠로 작성된다. 여러번 호출해서 데이터를 여러개 보낼 수 있다.


    • res.end 메서드는 출력을 완료하는 메서드이며, 인수로 값을 지정하면 해당 인수의 값을 작성한 뒤 내용을 완료한다. 이 메서드로 인해 응답 처리는 종료되고, 그 요청의 처리가 종료된다. res.writeHead, res.write, res.end 이 3개의 메서드로 클라이언트에 대한 응답 내용을 사용할 수 있다.


    • createServer 메서드 뒤에 listen 메서드에 클라이언트에게 공개할 포트번호를 인수로 넣고 두번째 인자로 포트연결 완료 후 살행될 콜백 함수를 넣었다.


    HTML 불러오기

    res.writeres.end에 HTML 코드를 일일히 작성하는 것은 비효율적이므로 미리 HTML 파일을 만들고 fs 모듈로 파일을 읽어 전송할 수 있다. 간단한 HTML 코드를 작성하고 index.html 파일로 루트경로에 저장한다,

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>title</title>
    </head>
    <body>
        <div>contents</div>
    </body>
    </html>
    
    const http = require("http");
    
    // fs 모듈 로드
    const fs = require("fs").promises;
    
    http
      .createServer(async (req, res) => {
    
        // 통신 성공 시
        try {
            
          // fs 모듈로 index.html 로드
          const data = await fs.readFile("./index.html");
          res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8",
          });
    
          // html 응답
          res.end(data);
    
        // 에러 발생 시
        } catch (err) {
          console.error(err);
          res.writeHead(500, {
            "Content-Type": "text/plain; charset=utf-8",
          });
    
          // 에러 메세지 응답
          res.end(err.message);
        }
      })
    
      .listen(8080, () => {
        console.log("8080포트에서 서버 대기중");
      });
    

    요청이 들어오면 fs 모듈로 HTML 파일을 읽어 클라이언트의 응답 값으로 보낼 수 있다.

    라우팅

    라우팅이란 특정한 URL에 대해 특정한 화면으로 연결하는 역할이라 정의할 수 있다. 주소가 http://localhost:8080/index.htmlindex.html 화면을 보여주고 http://localhost:8080/about.htmlabout.html 화면을 보여주는 역할이라 보면 된다. 간단히 라우팅을 구현해 보겠다. index.html 파일을 아래와 같이 수정해 주고, about.html 파일도 새롭게 추가해 준다.

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>title</title>
    </head>
    <body>
        <nav>
          <a href="/">home</a>
          <a href="/about">about</a>
        </nav>
        <div>메인 페이지</div>
    </body>
    </html>
    

    about.html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>title</title>
    </head>
    <body>
        <nav>
          <a href="/">home</a>
          <a href="/about">about</a>
        </nav>
        <div>소개 페이지</div>
    </body>
    </html>
    

    server.js 파일은 아래와 같이 수정해 준다.

    const http = require("http");
    
    // fs 모듈 로드
    const fs = require("fs").promises;
    
    http
      .createServer(async (req, res) => {
    
        // 통신 성공 시
        try {
          if (req.url === "/") {
    
            // index.html 로드
            const data = await fs.readFile("./index.html");
            res.writeHead(200, {
              "Content-Type": "text/html; charset=utf-8",
            });
            res.end(data);
          } else if (req.url === "/about") {
    
            // about.html 로드
            const data = await fs.readFile("./about.html");
            res.writeHead(200, {
              "Content-Type": "text/html; charset=utf-8",
            });
            res.end(data);
          }
    
        // 에러 발생 시
        } catch (err) {
          console.error(err);
          res.writeHead(500, {
            "Content-Type": "text/plain; charset=utf-8",
          });
    
          // 에러 메세지 응답
          res.end(err.message);
        }
        
      })
    
      .listen(8080, () => {
        console.log("8080포트에서 서버 대기중");
      });
    

    특정 링크를 클릭하면 지정한 화면으로 이동하는 것을 확인할 수 있다.

    References

    Node.js 교과서
    [Node.js코드랩] 2.기본 모듈 http
    기본 스크립트와 http 객체

  • [ESLint] ESLint, Prettier 적용하기

    [ESLint] ESLint, Prettier 적용하기

    자바스크립트 개발을 하다 보면 문법 오류나 코드 정리로 인해 시간을 많이 소비한다. ESLintPrettier는 이러한 상황을 해결해 주는 도구들이며, VSCode, WebStorm, Atom 등 여러 에디터와 연동해 사용이 가능하다. 이번 포스팅에서는 두가지 도구를 간단히 살펴보고 리액트 프로젝트에 적용하는 방법을 알아보겠다. 에디터는 VSCode를 기준으로 하겠다.

    ESLint

    ESLint는 ES + Lint의 합성어로 ES는 EcmaScript를 의미하고 Lint는 보푸라기라는 뜻인데, 프로그래밍에서는 에러가 있는 코드에 표시를 달아 놓는 것을 의미한다. 즉 ESLint는 JavaScript의 스타일 가이드를 따르지 않거나 문제가 있는 안티 패턴들을 찾아주고 일관된 코드 스타일로 작성하도록 도와준다. 코딩 컨벤션 및 안티 패턴을 자동 검출 하므로 옮바른 코딩 습관을 위해 필히 사용할 것을 권장한다.

    ESLint는 스타일 가이드를 편리하게 적용하기 위해 사용되기도 하는데, 많은 개발자가 사용중인 Airbnb Style Guide, Google Style Guide가 대표적인 예이다.

    ESLint가 어떻게 오류를 잡아주는지 예제를 통해 간단히 알아보자.

    $ mkdir test
    $ cd test
    $ touch test.js
    

    프로젝트를 생성할 디렉토리로 이동하여 test 폴더를 만들고 test.js 파일을 생성하였다.

    let foo = text;;
    

    문자열에 따옴표도 없고, 세미콜론도 두개고, 변수에 값이 할당되어도 사용이 안되는 엉망인 코드를 작성하였다. 그냥 js 파일은 문법적 오류를 따로 잡아주지 않을 것이다.

    ESLint 설치

    npm 프로젝트를 하나 생성하고 ESLint를 설치하도록 하겠다.

    $ npm init -y
    $ npm i -D eslint
    

    축약법 간단 설명

    1. npm init -y 명령어에 -y--yes의 축약법으로 npm 프로젝트를 초기 세팅할 때 아무 질문 없이 기본값으로 프로젝트가 세팅된다. 비슷한 명령어로 --force(-f)가 있다.
    2. npm i -D eslint 명령어의 -D--save-dev의 축약법이며, 비슷한 옵션으로 --save가 있다. 차이는 아래를 참고한다.
    3. --save-dev는 설치한 패키지 정보를 ./package.json 파일의 devDependencies 항목에 저장하며, npm install을 할 때 해당 패키지가 같이 설치된다. 설치할 때 --production 옵션을 붙이면 해당 패키지를 제외하고 npm이 설치된다.
    4. --save는 설치한 패키지 정보가 ./package.jsondependencies에 추가되며, npm install을 할 때 해당 패키지는 항상 설치가 된다.
    5. 아무 옵션을 넣지 않으면 순수하게 ./node_modules에 패키지만 설치한다.

    설치가 끝나면 ESLint 실행 파일이 ./node_modules 디렉토리 안에 생성될 것이다. ESLint 설정 파일을 생성하기 위해 아래 명령어를 실행한다.

    $ node_modules/.bin/eslint --init
    

    실행하면 몇가지 질문들이 나온다. 본인의 프로젝트 상황에 맞게 답변을 하면 된다.

    ? How would you like to use ESLint?
        ❯ To check syntax and find problems
    ? What type of modules does your project use?
        ❯ None of these
    ? Which framework does your project use?
        ❯ None of these
    ? Does your project use TypeScript?
        ❯ No
    ? Where does your code run?
        ❯ browser
    ? What format do you want your config file to be in?
        ❯ JavaScript
    

    답변을 전부 마치면 루트 경로에 .eslintrc.js 파일이 생성되며, 여기에서 ESLint를 설정할 수 있다.

    module.exports = {
        "env": {
            "browser": true,
            "es2020": true
        },
        "extends": "eslint:recommended",
        "parserOptions": {
            "ecmaVersion": 11,
            "sourceType": "module"
        },
        "rules": {
        }
    };
    
    • 환경(env): 프로젝트의 사용 환경을 설정한다.
    • 확장(extends): 다른 ESLint 설정을 확장해서 사용할때 설정한다. 위 파일에서는 ESLint가 추천하는 규칙을 적용하라는 설정이며, 실제 프로젝트에서는 위에서 언급한 airbnbprettier 등을 확장해서 사용한다.
    • 파서 옵션(parserOptions): ESLint 사용을 위해 지원하려는 Javascript 언어 옵션을 설정할 수 있다.
    • 규칙(rules): 프로젝트에서 자체적으로 덮어쓰고 싶은 규칙을 정의할 때 사용한다.

    설정 규칙 및 리스트는 상당히 방대하며, ESLint 공식 레퍼런스에서 참고 가능하다.

    ESLint 검사

    이제 위에서 엉망으로 작성한 test.js 파일을 아래 명령어를 사용하여 검사해 보자.

    $ node_modules/.bin/eslint test.js
    

    터미널에서 오류를 세가지를 아래처럼 알려준다.

    C:\code\test\test.js
      1:5   error  'foo' is assigned a value but never used  no-unused-vars
      1:11  error  'text' is not defined                     no-undef
      1:16  error  Unnecessary semicolon                     no-extra-semi
    
    ✖ 3 problems (3 errors, 0 warnings)
      1 error and 0 warnings potentially fixable with the `--fix` option.
    
    1. foo에 값이 할당되었지만 사용되지 않음
    2. text가 정의되지 않았음
    3. 불필요한 세미콜론이 있음

    위에서 3번째 항목에는 오른쪽에 no-extra-semi라고 나와있다. 이는 ESLint가 의도된 것이 아닌 실수라고 판단한 것이며, 자동으로 코드를 수정할 수 있다. 검사 명령어에 --fix를 붙이면 no-extra-semi항목을 자동으로 고쳐준다.

    $ node_modules/.bin/eslint test.js --fix
    

    명령어를 실행하면 세미콜론이 두개에서 한개로 바뀐것을 확인할 수 있다.

    실제 프로젝트에서는 검사할 파일이 많은데 위와 같이 일일히 터미널에서 ESLint를 실행하는것은 비효율적이다. 그래서 일반적으로 ESLint를 사용할때는 package.json 파일에 따로 설정해준다. 전체 파일을 상대로 ESLint를 실행되도록 설정해 보자.

      "scripts": {
        "lint": "eslint ."
      },
    

    이제 터미널에 아래 명령어를 실행하면 전체 파일을 상대로 ESLint가 실행될 것이다.

    $ npm run lint
    

    Prettier

    Prettier는 기존의 코드에 적용되어있던 스타일들을 전부 무시하고, 정해진 규칙에 따라 자동으로 코드 스타일을 정리해 주는 코드 포멧터이다.

    코드 포멧터(Code Formatter)란 개발자가 작성한 코드를 정해진 코딩 스타일을 따르도록 변환해주는 도구를 말한다.

    ESLint와 다른점이라면 ESLint는 문법 에러를 잡아내고, 특정 문법 요소를 쓰도록 만드는 등 코드 퀄리티와 관련된 것을 고치기 위해 사용되지만 Prettier는 코드 한 줄의 최대 길이나, 탭의 길이는 몇으로 할 것인지, 따옴표는 홀따옴표(‘)나 쌍따옴표(“)중 무엇을 사용 할 것인지 등등 코드 퀄리티보단 코딩 스타일을 일괄적으로 통일하는 도구에 가깝다.

    다시 테스트를 위해 test.js에 엉망인 코드를 작성하였다.

    let func=function 
    ( )
        {
      let 
    foo  
    ='text'
    return     foo}
    

    Prettier 설치

    Prettier을 아래 명령어로 설치해 준다.

    $ npm i prettier -D -E
    

    위 명령어중 -E--save-exac의 축약법이다. 위 ESLint 모듈을 설치할때와 다르게 Prettier에서는 이 옵션을 붙이는 것을 권장하는데, 버전이 달라지면서 생길 스타일 변화를 막기 위해서라고 한다.

    Prettier 실행

    $ npx prettier test.js
    

    위 명령어를 사용하면 엉망인 코드가 아래처럼 올바른 코드로 포멧팅되어 터미널창에 출력이 된다.

    let func = function () {
      let foo = "text";
      return foo;
    };
    

    코드 자체를 수정하고 싶다면 명령어에 --write 옵션을 추가하면 된다.

    $ npx prettier --write test.js
    

    에디터에 아래처럼 코드가 수정된다.

    let func = function () {
      let foo = "text";
      return foo;
    };
    

    이제 리액트 프로젝트에 ESLint와 Prettier를 적용하는 방법을 알아보겠다.

    리액트에 ESLint와 Prettier 적용하기

    이 포스팅에서는 CRA(Create React App)을 이용하여 생성한 리액트 프로젝트를 기준으로 한다. 아래 명령어를 사용하여 리액트 프로젝트를 생성한다.

    리액트 프로젝트 생성

    자바스크립트 사용 경우

    $ create-react-app 프로젝트이름 --use-npm
    

    타입스크립트 사용 경우

    $ create-react-app 프로젝트이름 --use-npm --template typescript
    

    CRA로 생성된 프로젝트는 안에 ESLint가 따로 탑재되어 있기 때문에 위처럼 따로 설치할 필요가 없다. 만약 CRA로 생성한 프로젝트에 수동으로 ESLint를 설치한다면, 프로젝트는 실행이 안되고, 터미널에 아래 경고가 출력될 것이다.

    There might be a problem with the project dependency tree.
    It is likely not a bug in Create React App, but something you need to fix locally.
    
    The react-scripts package provided by Create React App requires a dependency:
    
      "eslint": "^6.6.0"
    
    Don't try to install it manually: your package manager does it automatically.
    However, a different version of eslint was detected higher up in the tree:
    
      /Users/kongseongjoo/Documents/app/node_modules/eslint (version: 7.5.0) 
    
    Manually installing incompatible versions is known to cause hard-to-debug issues.
    
    If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
    That will permanently disable this message but you might encounter other issues.
    .
    .
    .
    

    이는 기본 탑재되는 node_module과 수동설치한 node_module의 버전 호환성 문제이며 터미널 경고 아래에 해결 방법이 나온다. CRA로 생성된 프로젝트는 ESLint를 따로 설치를 하지 않도록 한다.

    Prettier 설치

    $ npm i prettier -D -E
    

    필요한 추가 모듈

    Prettier와 ESLint를 같이 사용하려면 아래 모듈을 추가로 설치해야 한다.

    • eslint-config-prettier
      ESLint의 formatting 관련 설정 중 Prettier와 충돌하는 부분을 비활성화 한다.
    • eslint-plugin-prettier
      Prettier를 ESLint 플러그인으로 추가한다. 즉, Prettier에서 인식하는 코드상의 포맷 오류를 ESLint 오류로 출력해준다.

    아래 명령어를 입력하여 위에서 언급한 모듈을 설치한다.

    $ npm i eslint-plugin-prettier eslint-config-prettier -D
    

    그리고 프로젝트의 루트 경로에 .eslintrc.json파일을 만들고 아래 내용을 추가한다.

    {
      "plugins": ["prettier"],
      "extends": ["eslint:recommended", "plugin:prettier/recommended"],
      "rules": {
        "prettier/prettier": "error"
      }
    }
    

    ESLint, Prettier 익스텐션 설치

    Node 모듈을 설치했으니, 이제 VSCode와 같이 사용할 때 필요한 익스텐션을 설치하고 설정을 바꿔주자. VSCode의 Extensions: Marketplace로 들어가서 ESLint와 Prettier를 검색하여 설치한다.

    VSCode 설정

    VSCode에서 파일을 저장할 때마다 자동으로 코드가 수정되도록 설정해보자. VSCode 설정(윈도우, 리눅스에서는 Ctrl + , 맥에서는 Cmd + ,)으로 들어간다. 설정으로 들어가면 Search settings 입력창 아래에 User와 Workspace 항목이 있다. User는 VSCode 자체 설정으로 모든 프로젝트에 적용이 되고, Workspace는 현재 프로젝트에서만 설정이 적용되며, .vscode/settings.json에 설정 항목이 저장된다. ESLint와 Prettier의 경우 프로젝트별로 설정이 다른경우가 많이 때문에 작업공간마다 설정파일을 따로 관리하는 것을 선호한다. 설정은 json파일에 직접 입력이 가능하며, 우측 상단에 종이 모양의 Open Setting(JSON)아이콘을 클릭하면 settings.json파일이 열린다. 아래처럼 설정한다.

    {
      // Set the default
      "editor.formatOnSave": false,
      // Enable per-language
      "[javascript]": {
        "editor.formatOnSave": true
      },
      "editor.codeActionsOnSave": {
        // For ESLint
        "source.fixAll.eslint": true
      }
    }
    

    ESLint 설정

    ESLint 규칙은 상당히 방대하며, 모든것을 다 바꾸기 어렵기 때문에 여러가지 규칙을 정해준 모음이 있는데 위에서 언급한 Airbnb Style Guide나 Google Style Guide가 있다. 여기선 Airbnb를 적용해 보겠다.

    Airbnb Style Guide 적용

    아래 명령어를 사용하여 Airbnb를 설치해준다.

    $ npm i -D eslint-config-airbnb
    

    eslint-config-airbnb 말고도 eslint-config-airbnb-base가 있는데, 차이는 base에는 리액트 관련 규칙이 들어있지 않다는 점이다.

    그리고 .eslintrc.json파일의 "extends"속성에 "airbnb"를 추가 해준다.

    {
      "plugins": ["prettier"],
      "extends": ["eslint:recommended", "plugin:prettier/recommended", "airbnb"],
      "rules": {
        "prettier/prettier": "error"
      }
    }
    

    App.js파일을 열어보면 빨간줄이 엄청 그어져 있을 것이다. Airbnb의 규칙이 상당히 까다롭기 때문이다. 꼭 Airbnb 규칙을 따를 필요는 없다. ESLint 문서에서 본인 스타일에 맞는 스타일을 찾거나 수정하여 사용하면 된다.

    Prettier 설정

    ESLint 설정 파일과 마찬가지로 루트 경로에 .prettierrc.json을 만들어 준다. Prettier의 옵션 문서에서 필요한 옵션을 골라 설정해 주면 된다. 아래는 간단한 예시이다.

    {
      "trailingComma": "es5",
      "tabWidth": 2,
      "semi": true,
      "singleQuote": true
    }
    

    References

    [자바스크립트] ESLint로 소스 코드의 문제 찾기
    [자바스크립트] Prettier로 코딩 스타일 통일하기
    VS Code에서 ESlint와 Prettier 함께 사용하기
    ESLint 설정 살펴보기
    27. 리액트 개발 할 때 사용하면 편리한 도구들 – Prettier, ESLint, Snippet
    ESLint 조금 더 잘 활용하기
    Airbnb JavaScript Style Guide
    프론트엔드 개발환경의 이해: 린트
    ESLint(TSLint)와 Prettier 함께 사용하기
    리액트 프로젝트에 ESLint 와 Prettier 끼얹기
    ESLint

  • [JavaScript] 비동기 처리를 위한 프로미스

    [JavaScript] 비동기 처리를 위한 프로미스

    정의

    자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 콜백 패턴은 가독성이 나쁘고, 비동기 처리 중 발생한 에러의 예외처리가 곤란하며, 여러개의 비동기 처리 로직을 한꺼번에 처리하는 것도 한계가 있다. ES6에서 비동기 처리를 위한 또 다른 패턴으로 프로미스(Promise)를 도입하였다. 프로미스는 비동기적으로 요청한 결과(성공/실패)를 나타내는 객체로서, 콜백 패턴이 가진 단점을 보완하며, 비동기 처리 시점을 명확하게 표현한다.

    기존의 콜백 함수 패턴

    기존의 비동기 처리 방식은 콜백 패턴을 이용하는 방법이였으며 코드는 아래와 같다.

    function findUser(id, callback) {
      setTimeout(function () {
        const user = {
          id: id,
          name: 'my name is ' + id
        };
        callback(user);
      }, 1000);
    }
    
    findUser('foo', function (user) {
      console.log(user); // { id: 'foo', name: 'my name is foo' }
    });
    

    user객체의 정보를 받아오는 구조다. getUser() 함수를 호출할 때 두번째 인자로 콜백 함수가 할당된다. setTimeout()함수로 인하여 1초 뒤 user객체의 정보를 담는 로직이 실행되며 결과물이 두번째 인자로 들어간 콜백 함수의 인자에 할당되어 콜백 함수가 호출된다.

    프로미스 패턴

    상단의 비동기 처리 방식을 프로미스를 이용하면 아래와 같이 작성할 수 있다.

    function findUser(id) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          const user = {
            id: id,
            name: 'my name is ' + id,
          };
          resolve(user);
        }, 1000);
      });
    }
    
    findUser('foo').then(function (user) {
      console.log(user); // { id: 'foo', name: 'my name is foo' }
    });
    

    위 코드는 콜백 함수를 인자로 넘기는 대신에 프로미스 객체를 생성하여 리턴을 하고, 리턴받은 프로미스 객체의 then()메서드를 호출하여 실행할 로직을 인자값으로 넣어줬다. 실행할 로직은 resolve 메서드에 할당되어 user객체의 결과값을 콘솔창에 출력하고 있다. resolve()then()가 생소하다. 아래에서 천천히 알아보도록 하겠다.

    프로미스의 3가지 상태

    프로미스는 3가지의 상태(states)를 가지며, 여기서 상태란 프로미스의 처리 과정을 의미한다. new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 가진다.

    • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
    • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
    • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

    Pending(대기)

    아래처럼 new Promise()생성자를 호출하면 대기 상태가 된다. new Promise() 생성자를 호출할 때 인자로 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject이다.

    const promise = new Promise(function (resolve, reject) {
      // ...
    });
    

    실제로는 변수에 할당하기 보단 어떤 함수의 리턴값으로 많이 사용되며, 생성자의 인자로 화살표 함수를 많이 사용한다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        // ...
      });
    }
    

    Fulfilled(이행)

    넘겨 받은 인자에서 resolve를 함수로 실행하면 이행(완료) 상태가 된다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        resolve();
      });
    }
    

    이행 상태에서 아래와 같이 then()메서드를 이용하면 결과값을 받을 수 있다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        const data = 'my name is foo';
        resolve(data);
      });
    }
    
    returnPromise().then((data) => {
      console.log(data); // my name is foo
    });
    

    Rejected(실패)

    넘겨 받은 인자에서 reject를 함수로 실행하면 실패 상태가 된다. 실패 상태가 되면 실패 처리의 결과값을 catch()메서드를 통해 받을 수 있다.

    function returnPromise() {
      return new Promise((resolve, reject) => {
        reject(new Error('Request is failed'));
      });
    }
    
    returnPromise()
      .then()
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    사용 방법

    기본 코드

    아래는 응답값에 따라 결과를 출력하는 예제이다.

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
    
          const data = 'my-data';  // data 값이 있다면 data 값을 출력 
          // const data = ''; // data 값이 없다면 에러를 출력
    
          if (data) {
            resolve(data);
          }
          reject(new Error('Request is failed'));
        }, 1000);
      });
    }
    
    getData()
      .then((data) => {
        console.log(data); // my-data
      })
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    setTimeout()함수로 1초 뒤 data값을 받아오며, data값이 있을 경우 then()메서드를 호출하고, data값이 없을 경우 catch() 메서드를 호출한다.

    프로미스 연결(Promise Chaining)

    프로미스의 특징으로 여러개의 프로미스를 연결하여 사용할 수 있다. then()메서드를 호출하면 새로운 프로미스 객체가 반환된다. 아래처럼 각 프로미스를 연결하여 사용할 수 있다.

    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout((data) => {
    
          const data = 10; // data 값이 10일 경우
    
          if (data) {
            resolve(data);
          }
          reject(new Error('Request is failed'));
        }, 1000);
      });
    }
    
    getData()
      .then((data) => {
        console.log(data); // 10
        return data + 10;
      })
      .then((data) => {
        console.log(data); // 20
        return data + 10;
      })
      .then((data) => {
        console.log(data); // 30
      })
      .catch((err) => {
        console.log(err); // Error: Request is failed
      });
    

    setTimeout()함수에서 data값이 10으로 왔다고 가정해 본다. 첫번째 then()메서드의 data에 10을 더하여 리턴하고 두번째 then()에 더한 data를 인자로 넣어 호출한다. 이와 같은 방법으로 then()함수를 연결하여 사용할 수 있다.

    References

    [자바스크립트] 비동기 처리 2부 – Promise
    자바스크립트 Promise 쉽게 이해하기
    JavaScript 비동기 처리를 위한 promise 이해하기
    Promise
    프로미스

  • [JavaScript] 비동기 처리와 콜백 함수

    [JavaScript] 비동기 처리와 콜백 함수

    비동기 처리

    비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고, 순차적으로 다음 코드를 먼저 실행하는 자바스크립트의 특성이다. 비동기의 반대로는 동기 처리가 있다.

    • 동기: 요청을 보낸 후 해당 응답을 받아야 다음 동작을 실행(ex: 은행)
    • 비동기: 요청을 보낸 후 응답에 관계 없이 다음 동작을 실행(ex: 카페)

    비동기 예제

    비동기 처리의 대표적인 사례는 제이쿼리의 Ajax와 자바스크립트의 내장 함수인 setTimeout()이 있다.

    Ajax

    아래 처럼 Ajax로 통신을 하고 있다고 가정해 보자

    function getData() {
      let data;
    
      $.ajax({
        type: 'post',
        url: 'https://recordboy.github.io/',
        data: {
          // 전송 데이터
        },
        success: function (result) {
          // 통신 성공시 결과값 할당
          data = result;
        },
      });
    
      return data;
    }
    
    console.log(getData()); // undefined
    

    https://recordboy.github.io/주소로 data를 전송하고, 통신 성공시 응답값을 data변수에 할당하여 리턴하도록 되어있다. 정상적으로 응답값을 받아 올 것 같지만 리턴값은 undefined가 출력된다. 이유는 Ajax를 요청하고 응답값을 받기 전에 return data를 실행했기 때문이다. 결국 getData()의 리턴값은 초기 값을 설정하지 않는 undefined가 출력되는 것이다. 이렇게 특정 로직의 실행이 끝날 때까지 대기하지 않고, 나머지 코드를 먼저 실행하는 것이 비동기 처리이다.

    자바스크립트에서 비동기 처리가 필요한 이유는 화면에서 서버로 데이터를 요청했을 때 서버가 응답값을 언제 줄지도 모르는데 마냥 기다릴 순 없기 때문이다. 만약 어플리케이션이 비동기 처리를 하지 않고 동기적 처리를 한다고 가정해 보자. 위에서는 1번만 요청하였지만, 만약 요청을 100번이상 보낸다면 해당 어플리케이션은 실행하는데 수십분이 걸릴 것이다.

    setTimeout()

    비동기 처리의 두번째 사례이다.

    function getData() {
      let data;
    
      setTimeout(function () {
        data = 'result';
      }, 1000);
    
      return data;
    }
    
    console.log(getData()); // undefined
    

    위에서의 setTimeout()은 1초 뒤에 data변수에 result값을 할당하도록 되어있다. Ajax와 마찬가지로 코드는 setTimeout()이 실행되는 것을 기다리지 않고 return data를 먼저 실행하기 때문에 리턴값은 undefined를 출력한다.

    비동기 처리 방식의 문제 해결

    위와 같은 문제점들은 콜백 패턴으로 처리가 가능하다.

    function getData(callback) {
      let data;
    
      setTimeout(function () {
        data = 'result';
        callback(data);
      }, 1000);
    }
    
    getData(function (data) {
      console.log(data); // result
    });
    

    함수를 실행할 때 인자값으로 콜백 함수를 넣는 것이다. getData()의 인자값으로 콜백 함수가 들어갔고, setTimeout()이 1초뒤 실행되고 결과값을 콜백 함수의 인자값으로 넣어 호출하였다. 이렇게 콜백 패턴을 사용하면 특정 로직이 끝났을 때 원하는 동작을 실행 시킬 수 있다.

    콜백 지옥

    비동기 처리를 위해 콜백 패턴를 사용하면 처리 순서를 보장하기 위해 여러 개의 콜백 함수가 중첩되어 복잡도가 높아지는 콜백 함수가 발생하는 단점이 있다. 아래는 콜백 지옥이 발생하는 전형적인 사례이다.

    step1(function (value1) {
      step2(function (value2) {
        step3(function (value3) {
          step4(function (value4) {
            step5(function (value5) {
              // 탈출
            });
          });
        });
      });
    });
    

    step1에서 어떤 처리 (주로 입출력) 이후 그 결과를 받아와, 인자로 전달된 익명 함수의 매개변수로 넘겨준다. 이후 step2에서 또 어떤 처리를 하고, 다음 익명 함수가 실행된다. 이를 반복하다보면 코드가 위에서 아래가 아니라 기묘한 피라미드 모양으로 기술되게 된다. 이러한 콜백 지옥은 가독성을 나쁘게 하며 실수를 유발하는 원인이 된다.

    콜백 지옥 해결 방법

    일반적으로 콜백 지옥을 해결하는 방법에는 Promise나 Async를 사용하는 방법이 있으며, 만약 코딩 패턴으로만 콜백 지옥을 해결하려면 아래와 같이 각 콜백 함수를 분리해 주면 된다.

    step1(afterStep1);
    function afterStep1(value1) {
      step2(afterStep2);
    }
    function afterStep2(value2) {
      step3(afterStep3);
    }
    // 생략
    

    위와 같은 방법으로 콜백 지옥을 해결할 수 있지만 Promise나 Async를 이용하면 더욱 편리하게 비동기 처리를 구현할 수 있다.

    References

    자바스크립트 비동기 처리와 콜백 함수
    자바스크립트 비동기처리
    동기식 처리 모델 vs 비동기식 처리 모델
    동기와 비동기, 콜백함수
    프로미스
    콜백 지옥

  • [JavaScript] 랜덤 배열(Shuffle)

    [JavaScript] 랜덤 배열(Shuffle)

    아래는 배열을 무작위로 섞는 알고리즘이며, arr배열을 선언한 뒤 각 요소를 랜덤으로 추출하여 newArr라는 새로운 배열에 할당하도록 작성되었다.

    let arr = [1, 2, 3, 4, 5],
      newArr = [],
      len = arr.length,
      ranIdx = 0,
      ran = 0,
      target = 0;
    
    console.log(arr);
    // [ 1, 2, 3, 4, 5 ]
    
    for (let i = 0; i < len; i++) {
      ranIdx = Math.floor(Math.random() * arr.length);
      ran = arr[ranIdx];
      if (arr.indexOf(ran) !== -1) {
        target = arr.indexOf(ran);
        arr.splice(target, 1);
        newArr.push(ran);
      }
    }
    console.log(newArr);
    // 무작위로 배열 출력
    

    구글링을 하다 더욱 간결한 코드를 발견하였다. 코드는 아래와 같다.

    function shuffle(a) {
      let j, x, i;
      for (let i = a.length; i; i -= 1) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
      }
      console.log(a);
      // 무작위로 배열 출력
    }
    shuffle([1, 2, 3, 4, 5]);
    

    shuffle()에 배열을 인자로 넘겨 호출한다. 함수안 반복문의 인덱스 값은 내림차순으로 할당된다. Math.random()로 배열 요소의 난수를 구한 뒤 x에 배열의 마지막 요소를 할당하고, 마지막 요소 자리에 난수를 인덱스로 가지는 요소를 대입한다. 난수를 인덱스 값으로 가지는 요소 자리에는 아까 할당해둔 x값을 대입한다. 이 로직을 반복하여 랜덤 배열을 얻게 된다.

  • [TypeScript] 타입스크립트 타입 선언

    [TypeScript] 타입스크립트 타입 선언

    타입 기본

    타입 지정

    타입스크립트는 일반 변수, 매개 변수, 객체 속성 등에: TYPE과 같은 형태로 타입을 지정할 수 있다.

    let a: string = 'text'; // 문자열
    let b: number = 0; // 숫자형
    let c: boolean = true; // 논리형
    let d: any = true; // 어떤 타입이 올지 모를 때
    let e: string | number = '0'; // 문자열이나 숫자가 올 때
    

    타입 에러

    만약 아래와 같이 타입을 선언하고 다른 타입의 값을 대입하거나 값이 선언된 후에 다른 타입의 값이 대입되면 에러를 발생한다.

    // 문자열로 선언하고 숫자를 대입하면 에러 발생
    let a: string = 0; // error
    
    // 문자열 값이 들어가고 이후에 숫자가 대입하면 에러 발생
    let b: string = 'text';
    b = 1; // error Type '1' is not assignable to type 'string'.  TS2322
    

    출력창에서 TS2322라는 에러 코드를 볼 수 있으며, 이를 검색하면 쉽게 에러 코드에 대한 정보를 얻을 수 있다.

    타입 선언

    타입스크립트는 ES5, ES6의 상위 확장인 언어이므로 자바스크립트의 타입을 그대로 사용하며, 이외에도 타입스크립트의 고유의 타입이 추가로 제공된다.

    TypeJSTSDescription
    booleantrue와 false
    null값이 없다는 것을 명시
    undefined값을 할당하지 않은 변수의 초기값
    number숫자(정수와 실수, Infinity, NaN)
    string문자열
    symbol고유하고 수정 불가능한 데이터 타입이며 주로 객체 프로퍼티들의 식별자로 사용(ES6에서 추가)
    object객체형(참조형)
    array배열
    tuple고정된 요소수 만큼의 타입을 미리 선언후 배열을 표현
    enum열거형. 숫자값 집합에 이름을 지정한 것
    any타입 추론(type inference)할 수 없거나 타입 체크가 필요없는 변수에 사용, var 키워드로 선언한 변수와 같이 어떤 타입의 값이라도 할당 가능
    void일반적으로 함수에서 반환값이 없을 경우 사용
    never결코 발생하지 않는 값

    논리형(Boolean)

    let bool01: boolean = true;
    let bool02: boolean = false;
    

    숫자형(Number)

    let num01: number = 5;
    let num02: number = 3.14;
    let num03: number = NaN;
    

    문자열(String)

    ES6의 템플릿 문자열도 지원한다.

    let str01: string = 'text';
    let str02: string = `my name is ${val}`;
    

    배열(Array)

    배열은 아래와 같이 두가지 방법으로 타입을 선언할 수 있다.

    // 문자열만 가지는 배열
    let arr01: string[] = ['a', 'b', 'c'];
    let arr02: Array<string> = ['a', 'b', 'c'];
    
    // 숫자형만 가지는 배열
    let arr03: number[] = [1, 2, 3];
    let arr04: Array<number> = [1, 2, 3];
    
    // Union 타입(다중 타입)의 문자열과 숫자를 동시에 가지는 배열
    let arr05: (string | number)[] = [1, 'a', 2, 'b', 'c', 3];
    let arr06: Array<string | number> = [1, 'a', 2, 'b', 'c', 3];
    
    // 배열이 가지는 값의 타입을 추측할 수 없을 때 any를 사용할 수 있다.
    let arr07: (any)[] = [1, 'a', 2, 'b', 'c', 3];
    let arr08: Array<any> = [1, 'a', 2, 'b', 'c', 3];
    

    함수(Function)

    함수는 파라미터에 각각 타입을 선언해 주며, 파라미터 우측에는 해당 함수의 리턴값 타입도 선언해 주면 된다.

    function sum(a: number, b: number): number {
      return a + b;
    }
    console.log(sum(2, 3)); // 5
    

    리턴값을 숫자형으로 선언하였는데 다른 값이 리턴된다면 역시 에러가 난다.

    function sum(a: number, b: number): number {
      return null; // error
    }
    

    객체(Object)

    기본적으로 typeof 연산자가 object로 반환하는 모든 타입을 나타낸다. 여러 타입의 상위 타입이기 때문에 그다지 유용하지 않다.

    let obj: object = {};
    let arr: object = [];
    let func: object = function() {};
    let date: object = new Date();
    

    보다 정확하게 타입 지정을 하기 위해 아래와 같이 객체 속성들에 대한 타입을 개별적으로 지정할 수 있다.

    let user: { name: string, age: number } = {
      name: 'a',
      age: 20
    };
    console.log(user); // {name: "a", age: 20}
    

    튜플(Tuple)

    배열과 유사하다. 차이점은 정해진 타입의 요소 개수 만큼의 타입을 미리 선언후 배열을 표현한다.

    let tuple: [string, number];
    tuple = ['a', 0];
    console.log(tuple); // ["a", 0]
    

    정해진 요소의 순서 및 개수가 다르면 오류를 출력한다.

    let tuple: [string, number];
    tuple = [0, 'a']; // error
    tuple = ['a', 0, 1]; // error
    

    아래와 같이 데이터를 개별 변수로 지정하지 않고, 단일 튜플 타입으로 지정해 사용할 수 있다.

    // 기존 변수
    let name: string = 'a';
    let age: number = 20;
    
    // 튜플
    let user: [string, number] = ['a', 20];
    console.log(user); // ["a", 20]
    

    값으로 타입을 대신할 수도 있다. 처음 선언할 때의 값과 다른 값이 할당 되면 에러가 출력된다.

    let user: ['a', number];
    user = ['a', 20]; // ["a", 20]
    user = ['a', 30]; // ["a", 30]
    user = ['b', 30]; // error
    

    튜플은 정해진 타입과 고정된 길이의 배열이지만, 값을 할당할 때만 해당된다. push나 splice같은 메서드를 통해 값을 넣는건 막을 수 없다.

    let user: [string, number];
    user = ['a', 20]
    console.log(user); // ["a", 20]
    user.push(30);
    console.log(user); // ["a", 20, 30]
    

    열거형(Enum)

    숫자 혹은 문자열 값 집합에 이름을 부여할 수 있는 타입으로, 값의 종류가 일정한 범위로 정해져 있는 경우 유용하다. 기본적으로 0부터 시작하며, 값은 1씩 증가한다.

    enum obj {
      a,
      b,
      c,
      d,
      e
    }
    console.log(obj);
    // 0: "a"
    // 1: "b"
    // 2: "c"
    // 3: "d"
    // 4: "e"
    // a: 0
    // b: 1
    // c: 2
    // d: 3
    // e: 4
    

    수동으로 값을 변경할 수 있으며, 변경한 부분부터 다시 1씩 증가한다.

    enum obj {
      a,
      b = 10,
      c,
      d,
      e
    }
    console.log(obj.b); // 10
    console.log(obj.c); // 11
    

    모든 타입(Any)

    Any는 모든 타입을 의미하며, 기존의 자바스크립트 변수와 마찬가지로 어떠한 타입의 값도 할당할 수 있다. 불가피하게 타입을 선언할 수 없는 경우, 유용할 수 있다.

    let any:any = 'String';
    any = 0;
    console.log(any); // 0
    any = true;
    console.log(any); // true
    

    빈 타입(Void)

    빈 타입인 Void는 리턴값이 없는 함수에서 사용된다. 리턴값의 타입을 명시하는 곳에 작성하며, 리턴값이 없는 함수는 undefined를 반환한다.

    function hello(): void {
      console.log("hello");
    }
    console.log(hello()); // undefined
    

    never

    Never 타입은 절대 발생할 수 없는 타입을 나타낸다.

    function errorMsg() {
      throw new Error("오류 발생!");
    }
    console.log(errorMsg()); // Uncaught Error: 오류 발생!
    

    errorMsgd 함수는 오류를 발생시키기 때문에 null, undefined를 포함한 어떠한 값도 반환하지 않는다. 이럴 경우 never 타입을 사용하면 된다.

    References

    한눈에 보는 타입스크립트(updated)
    정적 타이핑
    타입스크립트 기초 연습
    TypeScript-Handbook 한글 문서
    3.1 기본 타입
    타입스크립트 기초 연습

  • [TypeScript] 타입스크립트 시작하기

    [TypeScript] 타입스크립트 시작하기

    정의

    타입스크립트(TypeScript)는 마이크로소프트에서 개발, 유지하고 있으며 엄격한 문법을 지원한다. 자바스크립트 엔진을 사용하며, 자바스크립트의 상위 집합으로 ECMA의 최신 표준을 지원한다.

    장점

    정적 타입 언어(static type language)

    C#과 Java같은 정적 타입 언어들에서 사용하는 타입 시스템은 높은 가독성과 코드 품질 등을 제공할 수 있고, 런타임이 아닌 컴파일 환경에서 에러가 발생해 치명적인 오류들을 더욱 쉽게 잡아낼 수 있다.

    기존의 자바스크립트는 타입 시스템이 없는 동적 프로그래밍 언어로, 변수에 문자열, 숫자, 불리언 등 여러 타입의 값을 가질 수 있다. 이는 약한 타입의 언어라고 하며, 비교적 유연하게 개발할 수 있는 환경을 제공하지만, 타입 안정성이 보장되지 않아 런타임 환경에서 쉽게 에러가 발생할 수 있는 단점을 가진다.

    타입스크립트는 자바스크립트에 타입 시스템을 적용하여 대부분의 에러를 컴파일 환경에서 체크할 수 있다.

    설치하기

    타입스크립트를 설치하는 방법에는 크게 두가지가 있다.

    • npm을 이용한 설치
    • TypeScript의 Visual Studio 플러그인 설치

    여기서는 npm을 이용하여 설치하겠다. 우선 전역에 타입스크립트를 설치해 준다.

    $ npm install -g typescript
    

    기존의 자바스크립트의 확장자가 .js이였다면 타입스크립트는 .ts라는 확장자를 가진다. 타입스크립트 작성 후 컴파일러를 통해 자바스크립트 파일로 변환되어 사용하게 된다.

    파일 생성하기

    원하는 디렉토리로 이동하여 타입스크립트 파일을 하나 만들어준다. 여기서는 app.ts로 만들겠다. 만든 뒤 아래 처럼 코드를 입력한다.

    function func(msg) {
        return 'hello' + msg;
    }
    let txt = 'world';
    

    컴파일 하기

    이제 터미널로 가서 아래 명령어를 실행 한다.

    $ tsc app.ts
    

    app.ts 파일을 컴파일 하여 아래처럼 app.js 파일이 생성되었을 것이다.

    function func(msg) {
        return 'hello' + msg;
    }
    var txt = 'world';
    

    로컬에서 작업 하기

    로컬 환경에서 Parcel 번들러를 이용하면 작업을 빠르게 테스트를 할 수 있다.

    Parcel

    Parcel 전역 설치

    $ npm install -g parcel-bundler
    

    프로젝트 파일 생성 및 패키지 파일 생성 후 패키지 및 Parcel를 설치해 준다. 프로젝트 폴더명은 app으로 하겠다.

    $ mkdir app
    $ cd app
    $ npm init -y
    $ npm install -D typescript parcel-bundler
    

    tsconfig.json 파일을 생성하고 아래 옵션을 추가한다.

    {
      "compilerOptions": {
        "strict": true
      },
      "exclude": [
        "node_modules"
      ]
    }
    

    main.ts 파일을 생성하고 아래 코드를 입력한다.

    function add(a: number, b: number) {
      return a + b;
    }
    const sum: number = add(1, 2);
    console.log(sum);
    

    index.html 파일을 생성하고 main.ts 파일을 연결한다. Parcel 번들러가 빌드시에 자동으로 타입스크립트를 컴파일 할 것이다.

    <!doctype html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>typescript</title>
    </head>
    <body>
      <script src="main.ts"></script>
    </body>
    </html>
    

    Parcel 번들러로 빌드해 준다.

    $ npx parcel index.html
    

    http://localhost:1234에서 결과물을 확인할 수 있으며, 이제 코드를 수정할 때마다 타입스크립트가 자동으로 컴파일될 것이다.

    References

    한눈에 보는 타입스크립트(updated)
    TypeScript-Handbook 한글 문서
    타입스크립트, 써야할까?

  • [JavaScript] 비구조화 할당(Destructuring Assignment)

    [JavaScript] 비구조화 할당(Destructuring Assignment)

    정의

    ECMAScript6(2015)에서 새로 추가된 비구조화 할당(Destructuring Assignment)이란 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식(expression)이다.

    기본 문법(배열)

    const animalList = ["CAT", "DOG", "TIGER"];
    const cat = animalList[0];
    const dog = animalList[1];
    const tiger = animalList[2];
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    animalList는 “CAT”, “DOG”, “TIGER” 값을 가지고 있는 배열이다. 이 배열의 값을 각각 변수에 할당 하려면 위처럼 각각 하나씩 지정해 줘야 한다. 번거로운 작업이며, 코드도 복잡해보이는 단점이 있다.

    const [cat, dog, tiger] = ["CAT", "DOG", "TIGER"];
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    비구조화 할당을 이용하면 위처럼 간단하게 작성할 수 있다. 좌항이 호출될 변수명 집합, 우항이 할당할 값이다. 좌항의 각 요소에는 같은 index를 가지는 배열값이 할당된다.

    나머지 패턴

    const [cat, ...rest] = ["CAT", "DOG", "TIGER"];
    console.log(cat); // CAT
    console.log(rest); // ["DOG", "TIGER"]
    

    전개연산자(...)를 활용하면 좌항에서 명시적으로 할당되지 않는 나머지 배열 값을 사용할 수 있다.

    기본 문법(객체)

    const animals = {
      cat: "CAT",
      dog: "DOG",
      tiger: "TIGER"
    };
    const cat = animals.cat;
    const dog = animals.dog;
    const tiger = animals.tiger;
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    객체도 배열과 마찬가지로 일일히 값을 따로 넣어주려면 번거롭다.

    const { cat, dog, tiger } = {
      cat: "CAT",
      dog: "DOG",
      tiger: "TIGER"
    };
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    위와 같이 작성하면 비구조화 할당을 수행하며, 배열의 경우 좌항의 index값에 값에 할당되었다면, 객체는 같은 key에 있는 값이 담긴다.

    나머지 패턴

    const { cat, ...rest } = {
      cat: "CAT",
      dog: "DOG",
      tiger: "TIGER"
    }
    console.log(cat); // CAT
    console.log(rest); // {dog: "DOG", tiger: "TIGER"}
    

    배열과 마찬가지로 객체도 나머지 패턴이 있다.

    원래의 key 대신 다른 변수명 사용

    const { cat: catName, dog: dogName, ...rest } = {
      cat: "CAT",
      dog: "DOG",
      tiger: "TIGER",
      monkey: "MONKEY"
    }
    console.log(catName); // CAT
    console.log(dogName); // DOG
    console.log(rest); // {tiger: "TIGER", monkey: "MONKEY"}
    

    좌항의 변수에 다른이름으로 사용할 변수명을 대입하면 되며, 나머지 값을 뜻하하는 전개연산자는 우항의 key에 영향을 받지 않으므로 ...rest: restName이라는 표현식은 무의미 하며, 에러가 난다.

    우항의 key 값이 변수명으로 사용 불가 경우

    const { 'cat-name', 'dog name' } = {
      'cat-name': "CAT",
      'dog name': "DOG"
    }
    // error
    

    좌항으로 전달 받는 key 값이 'cat-name'같이 사용 불가능한 문자열이 있는 경우 에러를 호출한다. 이럴 경우는 아래와 같은 방식으로 비구조화 할 수 있다.

    const key = 'dog name';
    const { 'cat-name': cat_name, [key]: dog_name } = {
      'cat-name': "CAT",
      'dog name': "DOG"
    }
    console.log(cat_name); // CAT
    console.log(dog_name); // DOG
    

    다만 이 경우 'cat-name'과 매칭할 변수명 cat_name을 작성하지 않으면 에러가 발생한다.

    객체 비구조화시 변수 선언 키워드가 없는 경우

    let cat,
        dog;
    
    // { cat, dog } = { cat: "CAT", dog: "DOG" } // error
    ({ cat, dog } = { cat: "CAT", dog: "DOG" }) // 괄호로 감싸줘야 함
    console.log(cat); // CAT
    console.log(dog); // DOG
    

    객체 비구조화시 변수 선언 키워드가 없을 경우 소괄호를 사용하여 감싸줘야 한다. 감싸주지 않으면 에러가 난다.

    기본값 할당

    배열의 기본값 할당

    const [cat, dog, tiger] = ["CAT", "DOG"];
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // undefined
    

    비구조화의 범위를 벗어나는 값 할당을 시도하면 undefined를 반환한다. 이럴 경우를 방지하기 위해 아래처럼 호출될 변수명에 기본값을 할당할 수 있다.

    const [cat, dog, tiger = "TIGER"] = ["CAT", "DOG"];
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    객체의 기본값 할당

    const { cat, dog, tiger = "TIGER" } = {
      cat: "CAT",
      dog: "DOG"
    };
    console.log(cat); // CAT
    console.log(dog); // DOG
    console.log(tiger); // TIGER
    

    배열과 마찬가지로 객체도 기본값을 지원한다.

    const { monkey: monkey_name = 'MONKEY' } = {};
    console.log(monkey_name); // MONKEY
    

    위 코드처럼 객체의 key에 새로운 변수명을 할당하는 방식에도 기본 기본값 할당을 사용할 수 있다.

    복사

    전개연산자를 사용하여 배열, 객체의 깊은 복사를 할 수 있다.

    배열의 깊은 복사

    let arr = [1, 2, 3];
    let copy1 = arr;
    let [...copy2] = arr;
    let copy3 = [...arr];
    
    arr[0] = 'String';
    console.log(arr); // [ 'String', 2, 3 ]
    console.log(copy1); // [ 'String', 2, 3 ]
    console.log(copy2); // [ 1, 2, 3 ]
    console.log(copy3); // [ 1, 2, 3 ]
    

    얕은 복사인 copy1arr를 참조하기 때문에 0번째 요소가 같이 수정되었지만, 전개연산자를 사용한 copy2copy3은 깊은 복사가 되었기 때문에 0번째 요소가 변경되지 않았다.

    객체의 깊은 복사

    객체 역시 전개연산자로 깊은 복사를 사용할 수 있다. 무엇보다 강력한 점은 복사와 함께 새로운 값을 할당할 수 있다는 점이다.

    let prevState = {
      name: "foo",
      birth: "1995-01-01",
      age: 25
    };
    let state = {
      ...prevState,
      age: 26
    };
    
    console.log(state); // {name: "foo", birth: "1995-01-01", age: 26}
    

    위와 같이 ...prevState를 사용하여 기존 객체를 복사함과 동시에 age에 새로운 값을 할당했다. 리액트의 props나 state처럼 이전 정보를 이용하는 경우 유용하게 사용할 수 있다.

    함수에서의 비구조화 할당

    함수의 파라미터 부분에서도 비구조화 할당을 사용할 수 있다. 이러한 문법은 특히 API 응답값을 처리하는데에 유용하게 사용된다.

    function renderUser({name, age, addr}){
      console.log(name);
      console.log(age);
      console.log(addr);
    }
    const users = [
      {name: 'kim', age: 10, addr:'kor'},
      {name: 'joe', age: 20, addr:'usa'},
      {name: 'miko', age: 30, addr:'jp'}
    ];
    
    users.map((user) => {
      renderUser(user);
    });
    // kim
    // 10
    // kor
    // joe
    // 20
    // usa
    // miko
    // 30
    // jp
    

    users 배열의 map 메서드로 인하여 renderUser 함수에 users의 객체가 각각 전달된다. 각 객체의 key 값이 renderUser함수의 파라미터 받는 부분에서 비구조화 할당을 받았기 때문에 함수 내에서 객체의 key 값을 각각 가져올 수 있게 된다.

    const users = [
      {name: 'kim', age: 10, addr:'kor'},
      {name: 'joe', age: 20, addr:'usa'},
      {name: 'miko', age: 30, addr:'jp'}
    ];
    
    users.map(({name, age, addr}) => {
      console.log(name);
      console.log(age);
      console.log(addr);
    });
    

    마찬가지로 위처럼 map 메서드의 파라미터에도 바로 사용할 수 있다.

    for of 문을 이용한 비구조화 할당

    배열 내 객체들은 for of 문을 사용하여 비구조화 할 수 있다.

    const users = [
      {name: 'kim', age: 10, addr:'kor'},
      {name: 'joe', age: 20, addr:'usa'},
      {name: 'miko', age: 30, addr:'jp'}
    ];
    
    for(let {name : n, age : a} of users){
      console.log(n);
      console.log(a);
    }
    

    중첩된 객체 및 배열의 비구조화

    중첩된 객체 및 배열 역시 비구조화가 가능하다.

    const kim = {
      name: 'kim',
      age: 10,
      addr: 'kor',
      friends: [
        {name: 'joe', age: 20, addr:'usa'},
        {name: 'miko', age: 30, addr:'jp'}
      ]
    };
    
    let { name: userName, friends: [ ,{ name: jpFriend }] } = kim;
    console.log(userName); // kim
    console.log(jpFriend); // miko
    

    References

    자바스크립트 {…} […] 문법 (비구조화 할당/구조분해 할당)
    JavaScript ) 비구조화 할당 알아보기
    구조 분해 할당

  • [JavaScript] 화살표 함수(Arrow Function)

    [JavaScript] 화살표 함수(Arrow Function)

    정의

    ECMAScript6(2015)에서 새로 추가된 화살표 함수(Arrow Function)는 function 키워드 대신 화살표(=>)를 사용하여 보다 간략한 방법으로 함수를 선언할 수 있다.

    기존 함수

    함수 표현식

    var func = function() {
      ...
    };
    

    함수 선언식

    function func() {
      ...
    }
    

    화살표 함수

    const func = () => {
      ...
    };
    

    화살표 함수의 기본 문법

    // 매개 변수가 없을 경우
    const func = () => {
      ...
    };
    
    // 매개변수가 한 개인 경우, 소괄호를 생략할 수 있다.
    const func = x => {
      ...
    };
    
    // 매개변수가 여러 개인 경우, 소괄호를 생략할 수 없다.
    const func = (x, y) => {
      ...
    }
    
    // 간단하게 한줄로 표현할 땐 중괄호를 생략할 수 있으며 암묵적으로 값이 반환된다. 아래 두 표현은 같다.
    const func = (x, y) => x + y;
    const func = (x, y) => { return x + y; }
    
    // 객체를 반환시 중괄호를 생략하면 소괄호를 사용한다. 아래 두 표현은 같다.
    const func = (x) => { return { a: x } };
    const func = (x) => ({ a: x });
    

    화살표 함수의 호출

    화살표 함수는 익명 함수로만 사용할 수 있다. 따라서 함수를 호출하기 위해서는 함수 표현식을 사용한다.

    함수 표현식

    // ES5
    var func = function(x, y) {
      return x + y;
    }
    console.log(func(5, 10)); // 15
    
    // ES6
    const func = (x, y) => {
      return x + y;
    }
    console.log(func(5, 10)); // 15
    

    콜백 함수

    콜백 함수의 경우 함수 표현식보다 표현이 간결하다.

    // ES5
    var arr = [1, 2, 3];
    var result = arr.map(function(x) {
      return x + x;
    });
    console.log(result); // [2, 4, 6]
    
    // ES6
    const arr = [1, 2, 3];
    const result = arr.map(x => x + x);
    console.log(result); // [2, 4, 6]
    

    this

    기존의 function 키워드로 생선된 일반 함수와 화살표 함수의 큰 차이점 중 하나는 this이다.

    function 키워드 함수의 this

    function 키워드로 생성된 함수는 함수가 어떻게 호출되었는지에 따라 this가 바인딩할 객체가 동적으로 결정된다.

    일반 함수의 this

    일반 함수(여기서 일반 함수란 중첩 함수나 객체의 함수인 메서드, 콜백 함수가 아닌 전역 스코프에 있는 함수를 말한다.)를 호출하게 되면 this는 전역 객체인 window를 바인딩 한다.

    function func() {
        console.log(this); // window
    }
    func();
    

    생성자 함수의 this

    하지만 new 키워드를 사용하여 생성자함수 호출 방식으로 obj 객체를 생성하면 함수 안에서의 this는 생성자 함수를 바인딩 한다.

    function Func() {
        console.log(this); // Func {}
    }
    var obj = new Func();
    

    메서드의 this

    function 키워드로 만들어진 메서드의 this는 자신을 포함하는 객체를 바인딩 한다.

    var obj = {
        myName: '나나',
        getName: function() {
            console.log(this); // {myName: "나나", getName: ƒ}
            console.log(this.myName); // 나나
        }
    }
    obj.getName();
    

    obj 객체의 getName 메서드 안에서의 this는 obj 객체를 바인딩 하고 있다. this.myName 호출하면 값이 제대로 출력된다.

    화살표 함수의 this

    화살표 함수는 자신의 this를 바인딩하지 않고 언제나 상위 스코프인 this를 바인딩 한다. 이를 Lexical this 라고 한다.

    일반 함수의 this

    일반 함수의 경우 function 키워드와 마찬가지로 전역 객체인 window를 바인딩 한다.

    const func = () => {
        console.log(this); // window
    }
    func();
    

    생성자 함수의 this

    화살표 함수는 생성자 함수로 사용할 수 없다.

    const Func = () => {
        console.log(this);
    }
    const obj = new Func(); // Uncaught TypeError: Func is not a constructor
    

    기존의 function 키워드로 만든 일반적인 생성자 함수는 prototype 프로퍼티를 가지며, prototype 프로퍼티가 가르키는 프로토 타입 객체의 constructor를 사용한다.

    function Func() {}
    console.log(Func.prototype); // {constructor: ƒ}
    

    하지만 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.

    const Func = () => {}
    console.log(Func.prototype) //undefined
    

    메서드의 this

    var obj = {
        myName: '나나',
        getName: () => {
            console.log(this); // window
            console.log(this.myName); // undefined
        }
    }
    obj.getName();
    

    화살표 함수로 만들어진 메서드의 this는 자신을 포함하는 객체를 바인딩하지 않고 window를 바인딩하기 때문에 화살표 함수는 메서드에 적합하지 않다.

    References

    화살표 함수
    JavaScript – 화살표 함수(Arrow function)
    Arrow Function(화살표 함수)이란? :: 마이구미
    ES6 화살표 함수(arrow function) 변경점 요약 (사용법, this등)