CRA(Create React App)에 tslint, eslint, prettier 적용하기

지금 만드는 토이프로젝트에 tslint, eslint, prettier을 적용한 겸 세팅값을 블로그에도 적어둔다.

CRA로 만든 프로젝트에 따로 webpack config를 eject 하지 않고 위에 3개를 적용할 수 있는 방법임.

Step 1: 온갖 devDependencies를 설치한다.

npm i -D @types/react typescript-eslint/eslint-plugin typescript-eslint/parser eslint eslint-config-prettier eslint-config-react eslint-loader eslint-plugin-prettier prettier tslint typescript

다만 여러분이 이 글을 보는 시점에 https://github.com/prettier/prettier-atom/issues/505#issuecomment-516791742 이 Issue가 해결되지 않았다면 eslint는 5.16.0 버전으로 설치한다(19년 9월 기준으로 v6임)

// eslint를 5.16.0 으로 박아둔 버전
npm i -D @types/react typescript-eslint/eslint-plugin typescript-eslint/parser eslint@5.16.0 eslint-config-prettier eslint-config-react eslint-loader eslint-plugin-prettier prettier tslint typescript

왜냐면 에디터 prettier에서 dependency 에러가 나더라. Failed to load plugin 'prettier' declared in 'CLIOptions': Cannot find module 'eslint-plugin-prettier' 라고 나옴. eslint를 5.16버전으로 다운그레이딩하면 이 에러가 사라진다. 곧 Fix PR 만든다고 했으니 미래에는 에러 해결이 되었길…

내 package.json은 다음과 같다

"devDependencies": {
    "@types/react": "^16.9.2",
    "@typescript-eslint/eslint-plugin": "^2.3.0",
    "@typescript-eslint/parser": "^2.3.0",
    "eslint": "^5.16.0",
    "eslint-config-prettier": "^6.3.0",
    "eslint-config-react": "^1.1.7",
    "eslint-loader": "^3.0.0",
    "eslint-plugin-prettier": "^3.1.1",
    "prettier": "^1.18.2",
    "tslint": "^5.20.0",
    "typescript": "^3.6.3"
  },

Step 2: eslint 셋업을 하는 .eslintrc.json 파일을 만든다

eslint의 세부사항을 셋업할 수 있는 파일을 루트폴더에 생성한다. 잘 보면 타입스크립트와 프리티어 세팅 모두 박혀있다.
.eslintrc.json

{
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "plugins": ["react", "@typescript-eslint", "prettier"],
  "env": {
    "browser": true,
    "jasmine": true,
    "jest": true
  },
  "settings": {
    "react": {
      "pragma": "React",
      "version": "detect"
    }
  },
  "parser": "@typescript-eslint/parser"
}

Step 3: 몇몇 파일에 eslint를 disable시킬 .eslintignore 파일을 만든다

서비스워커 파일이나 tests 폴더나 legacy 소스들에는 eslint를 끄고싶은 분들도 있을거다. ignore할 수 있는 파일을 만든다.

.eslintignore

src/registerServiceWorker.js
src/**/__tests__/**

Step 4: 타입스크립트 린트 세팅 파일인 tslint.json을 만든다

TSlint 셋업을 해준다. 요거 키면 .tsx 파일들에 빨간줄이 무지막지하게 나온다.

tslint.json

{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {
      "quotemark": false
    },
    "rulesDirectory": []
}

Step 5: 예쁘게 코드 포매팅을 해주는 프리티어 세팅 파일인 .prettierrc 를 만든다

이렇게 프리티어 세팅을 해주고 VSCode에서 mac기준 shift + option + f를 누르면 세팅값대로 코드를 예쁘게 성형해준다. 입맛대로 세팅을 해준다. 프리티어 다큐멘테이션 참고하삼 https://prettier.io/docs/en/options.html 아래는 제 세팅

.prettierrc

{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80,
  "arrowParens": "always",
  "orderedImports": true
}

(optional) Step 6: 이제 다했당. package.json에 eslint 바로 고쳐주는 스크립트를 짜둔다

lint:fix 란 이름으로 eslint fix 스크립트를 짜두면 언제든 npm run lint:fix 명령어로 린트 픽스를 할 수 있다. (하지만 난 파일마다 수동으로 단축키를 통해 고치는걸 선호하지)

"scripts": {
    ...
    "lint:fix": "eslint './src/**/*.{ts,tsx}'"
  }

끗!
이제 열시미 js랑 tsx 파일 린트에러를 고치면 됩니다.

적용한 파일은 https://github.com/milooy/TellMe/commit/d2c5520b9d4c0ebfac2629ca2f02066afbeb61b8 참고

[React] External component를 StyledComponent로 감쌌을 때 ‘Warning: Unknown props’ 워닝 해결

Problem

에러가 났을 때 빨간 글자색을 보여주기 위해 StyledComponent에 hasError 프로퍼티를 넘기고

<StyledField hasError={hasError} />

이를 스타일링만 하기 위해 StyledComponent에서만 받아 썼더니

const StyledField = styled(Field)`
  color: ${({ hasError }) => (hasError ? "red" : "black")};
`;

Warning: React does not recognize the errorMessage prop on a DOM element. 에러가 났당. 컴포넌트를 감싼에서만 prop을 쓰고 그 안쪽의 “에서는 errorMessage prop을 안써서 나는 워닝이다.
Screen Shot 2019-09-19 at 4.56.18 PM

Solution

spread operator(...rest)로 hasError를 제외한 나머지 properties만 Field 컴포넌트에 내려보내주면 된다. 코드 드럽네

const StyledField2 = styled(({ hasError, children, ...rest }) => (
  {children}
))`
  color: ${({ hasError }) => (hasError ? "red" : "black")};
`;

Refer

https://github.com/styled-components/styled-components/issues/305

VS Code에 console.log shortcut 추가하기

Background

처음에는 emmet 방식처럼 간략한 텍스트를 입력하고 tab을 누르면 자동완성 되는 방향으로 했는데 반응이 너무 느려서 그냥 명령어 + 탭으로 보이는 경우가 왕왕 있었다. 그래서 스니펫 말고 키보드 쇼트컷 방식을 찾아봤다.

How to

  1. Code > Preferences > Keyboard Shortcuts를 들어간다

2. 상단의 ‘keybindings.json’ 클릭

3. 오른쪽의 Custom 영역에 원하는 키로 덮어쓴다. 나는 console.log("변수이름", 변수")` 식으로 자주 사용해서 snippet에 그렇게 입력해주고 ctrl+shift+l로 단축키를 엮어줬다.

// Place your key bindings in this file to overwrite the defaults
[
    {
        "key": "ctrl+shift+l",
        "command": "editor.action.insertSnippet",
        "when": "editorTextFocus",
        "args": {
            "snippet": "console.log('$1', $2);$0"
        }
    }
]

4. 에디터에서 단축키를 눌러보면 잘 작동하는걸 볼 수 있다. 위에서 $1, $2로 원하는 위치에 숫자를 적어주면 그 곳으로 자동으로 포커스가 이동한다. $1에 원하는 텍스트를 입력하고 tab을 치면 $2로 넘어간다. 끝나면 $0으로 간다.

5. 즐코딩!

+ 단축키 없이 snippet 만들기

Preferences > User Snippets > javascript.json에 아래와 같은 코드를 넣는다

"Print to console": {
		"prefix": "log",
		"body": [
			"console.log('$1');",
			"$2"
		],
		"description": "Log output to console"
	}

lodash fp로 함수형 프로그래밍 하기

왜 함수형으로 코드를 쓰는가

  • Explicit한 코드를 Implicit(암묵적)하게 바꾼다는 이점
  • Explicit하게 짠 제곱함수
function squareAll(numbers) {
    var squared = [];
    for (var i=0; i < numbers.length; i++) {
        squared.push(numbers[i] * numbers[i]);
    }
    return squared;
}

squareAll([1, 2, 3, 4]); // [1, 4, 9, 16]
  • Implicit하게 짠 제곱함수.
function squareAll(numbers) {
    return numbers.map(num => num * num);
}
squareAll([1, 2, 3, 4]);
- 루프를 도는것과 새로운 배열을 만드는걸 `map`메서드에 위임시켰다.
- 읽기 쉬워지고 에러를 낼 가능성이 적어짐.
- 하지만 numbers배열에서 이를 불러내야 함.

– lodash fp로 조금 더 Implicit하게 짠 제곱함수

const map = require('lodash/fp/map');

const squareAll = map(num => num * num); // array.map으로 접근하지 않고 바로 map을 부름. 그리고 함수에 arguments도 없음(커리되어서)

squareAll([1, 2, 3, 4]);
  • lodash/fp의 함수들은 기본적으로 커리되어있다.
  • 그래서 기존에 익숙했던 map(array, function)이 아닌 map(function, array) 순이라고 생각하면 된다.
  • 더이상 numbers argument를 받지 않아도 되며, 이는 point-free프로그래밍의 컨셉이다.
  • 또다른 이점들은 적은 라인의 코드로 읽기도 좋으며, 테스트랑 유지보수 하기도 좋다는 것이다.

커링(Currying)

  • 어떤 함수가 특정한 개수의 인자를 기대하는데, 이보다 적게 인자를 넣었을 때 남은 인자들을 새로운 함수로 받을 수 있는것을 커링(Currying)이라 한다.
  • 모든 인자들이 전달되었을 때 원하는 결과가 반환된다.
const curry = require("lodash/fp/curry");

const sayMessageTo = curry((msg, name) => `${message}, ${name}!`);

sayMessageTo("Hello", "Yurim"); // 원하는 결과 바로 반환. "Hello, Yurim!"
const sayBye = sayMessageTo("Bye"); // message를 받았으니, name을 받을 수 있는 새로운 함수 반환
sayBye("Josh"); // 모든 인자 받았다. "Bye, Josh!"
  • 아까 했던 map도 map(num =&gt; num * num, [1,2,3,4])처럼 한번에 호출할 수 있음

함수 조립하기 (Compose functions)

  • 함수를 조립한다는 컨셉은 작은 함수들로 큰 함수를 만든다는 것이다. 각 함수는 리턴된 값을 다음 함수에 넘긴다.
const flow = require('lodash/fp/flow');
const escape = require('lodash/fp/escape');
const trim = require('lodash/fp/trim');

const sanitise = flow(escape, trim);

sanitise('    <html>    '); // <html>
  • 암묵적이고 선언적으로 함수 짜기. 어떻게 되어야하는지 코드를 짜는게 아니고 어떤 일이 일어나야하는지 선언하기.
  • lodash/fp의 함수들은 기본적으로 커리가 되어있어서 더 적은 인자를 넘기면 함수를 돌려준다.
  • composition이 가능한 함수들은 하나의 argument를 넘기고 하나의 value를 다음 함수로 넘기는 형태여야 한다. 맨 처음 함수는 하나 이상의 argument를 넘길 수 있지만 single value를 리턴해야 하는건 동일하다.

Point-free

  • 포인트 프리 스타일(Tacit이라고도 부름) 프로그래밍은 함수가 그들이 수행해야 하는 arguments를 명시하지 않는 패러다임이다.
// Before
function isSuccess(response) {
  if (response.status === 200) {return true;}
}
isSuccess(response);

// After
const isSuccess = flow(get('status'), isEqual(200));
isSuccess(response);

순수 함수(Pure functions)

  • 외부 스코프에 의존이 없는 함수. 넘긴 인자에만 영향받고 이를 변형하면 안되며 새로운 객체를 넘겨야 한다.

Refer

https://simonsmith.io/dipping-a-toe-into-functional-js-with-lodash-fp/

광광 울며 정리하는 Javascript의 this

전역에서 함수를 할당하면 window로 들어가는구나. 그래서 그 속에서 this를 찍어보면 Window객체가 나온다. new Foo()로 초기화하면 예상했던 대로 Foo { }가 나오고. 면접에서 털리고 광광 울며 정리중

 
js에서 모든 함수는 실행시마다 함수 내부에 this가 추가. arguments라는 유사 배열 객체와 함께 암묵적으로 전달. 그렇기 때문에 함수가 호출된 상황에 따라 그 모습을 달리 함.

  • this: 함수의 현재 실행 문맥. js에선 4가지의 함수 실행 타입이 있기 때문
      1. 함수 실행: alert(‘hi’)
      1. 메소드 실행: console.log(‘hello’)
      1. 생성자 실행: new RegExp(”)
      1. 간접 실행: alert.call(undefined, ‘hello’)
    • 각각의 타입은 서로 다른 문맥을 가진다. 더욱이 strict mode또한 실행 문맥에 영향을 미친다.
  • 즉시실행함수 역시 함수 실행의 종류 중 하나
var greeting = (function(name) {
    return 'Hi' + name;
})('Jay');
console.log(greeting)
  • 함수 실행에서의 this는 전역 객체다. 전역 객체는 실행 환경에 따라 결정. 웹 브라우저에선 window가 전역 객체.
function sum(a, b) {
    console.log(this === window); //true
    this.myNum = 20; // this에 추가했고, 전역인 window에 추가됨
    return a + b;
}

function strictSum(a, b) {
    'use strict';
    console.log(this === undefined); //true
    return a + b;
}
sum(1, 2); // 3. 함수 호출 -> 여기서 this는 전역 객체
window.myNum; // 20. this에 추가했는데 윈도우에 추가됨
  • 엄격 모드에서의 함수 실행 this
    • 위의 예제에서 만약 위에 ‘use strict’;를 적어줬다면, this는 undefined가 된다.
    • 엄격 모드는 현재 스코프 뿐만 아니라 내부 스코프에도 적용됨
var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       return this.numberA + this.numberB; // numbers에 접근 불가
     }
     return calculate();
   }
};
// 이는 객체 내에 있는 메소드를 실행하는 것. 이 실행시의 this는 window
numbers.sum(); // NaN, 엄격 모드였으면 TypeError

refer: http://webframeworks.kr/tutorials/translate/explanation-of-this-in-javascript-1/

body overflow:hidden 이 모바일에서 안 먹을때 javascript로 해결방법

스크린샷 2017-05-09 오후 9.21.17

상황: 모바일에서 사이드 네비게이션이 열린 상태에서 스크롤시 body는 스크롤 되지 않게 하고싶다

Solution A: CSS

html,
body {
    overflow: hidden;
    position: relative;
    height: 100%;
}

PC에선 문제 없이 작동한다. 하지만 iOS 사파리랑 크롬에서는 무용지물.

Solution B: CSS – fix

body {
    position: fixed;
    width: 100%;
    height: 100%;
}

PC, 모바일 모두 되긴 하는데 Side nav를 닫은 후에는 position이 꼬임 -> DOM 클릭해도 이벤트가 활성화 안된다.

Solution C: jQuery

<body>
<div id="side-nav">사이드 네비게이션</div>
<div class="contents">여기에 body의 내용 넣는다</div>
</body>
$(function(){
    $('#side-nav').on('show.bs.offcanvas', function (e) {
      $('#sidenav-overlay').addClass('active');

      /* Disable scroll */
      $('.contents').on('scroll touchmove mousewheel', function(e){
        e.preventDefault();
        e.stopPropagation();
        return false;
      });
    });
    $('#sidenav-overlay').click(function() {
      $('#side-nav').offcanvas('hide');
      $('#sidenav-overlay').removeClass('active');

      /* Enable scroll */
      $('.contents').off('scroll touchmove mousewheel');
    });
  });

jQuery on handler로 스크롤을 막는다.
HTML에서 사이드 네비게이션과 Contents 영역을 분간해두면
사이드 네비게이션 자체에서는 스크롤 가능하게 할 수 있다.

Refer

http://stackoverflow.com/questions/3656592/how-to-programmatically-disable-page-scrolling-with-jquery

[Javascript]원하는 HTML 영역 프린트하기

스크린샷 2017-03-28 오전 10.48.19스크린샷 2017-03-28 오전 10.49.44

상황: 페이지의 일부만 프린트하고 싶다!

구글링 하면 2가지 방법이 나온다.
근데 둘다 내 상황에는 문제점이 있었다.

  1. 팝업으로 DOM을 복사해 프린트: 편하긴 한데 css, js가 떨어짐
  2. 전체 돔, 프린트 영역 원하는 돔 따로 저장한다음에 프린트영역만 살리고 프린트 후 전체 돔 살림: 편하긴 한데 js 이벤트 바인딩이 떨어짐

아래와 같이 display noneblock 을 toggle 해주는걸로 간단히 해결 가능하다.
React 사용중이라 jquery 말고 pure javascript로 코딩했다.

onPrint() {
const html = document.querySelector('html');
const printContents = document.querySelector('.modal-body').innerHTML;
const printDiv = document.createElement(&quot;DIV&quot;);
printDiv.className = &quot;print-div&quot;;

html.appendChild(printDiv);
printDiv.innerHTML = printContents;
document.body.style.display = 'none';
window.print();
document.body.style.display = 'block';
printDiv.style.display = 'none';
}

[javascript] 함수 할당시 실행 없이 인자 넘기기

Problem

var apiCRUD  = {
  downloadExcel: function($http) {
    //랄랄랄
  }
}

rc.downloadExcel = apiCRUD.downloadExcel($http);

apiCRUD.downloadExcel함수를 저렇게 rc.downloadExcel에 할당하면 바로 실행이 되는데,
나는 rc.downloadExcel이 호출되었을 때 apiCRUD의 함수가 실행되길 바란다.

그렇다고

rc.downloadExcel = apiCRUD.downloadExcel;

처럼 하면 $http인자를 못 넘긴다.

Solution

rc.downloadExcel = function() {
    apiCRUD.downloadExcel($http);
}

요러면 됨.
호이스팅 없이 함수 실행 시에 불리움.

Refer

j2p님의 도움 감사합니다.

momentJS를 angularJS 필터에서 사용하기

Problem

스크린샷 2016-01-25 오후 6.55.20

angularJS로 ERP를 만드는 중이다.
ng-repeat로 반복되는 칼럼에 타임스탬프가 안 예쁘게 찍혀서 moment.js로 가독성을 높이려 한다.

javascript에서 가로채서 솰라솰라 하는거 말고 angular로 예쁘게 하는 방법은 없을까?

 

Solution

angularjs filter를 사용한다.

sandiApp.filter('moment', function() {
    return function(dateString, format) {
        return dateString? moment(dateString).format(format): null;
    };
});

이 jsfiddle을 참고하고, 값을 입력 안하면 null이 반환되게 살짝 추가해두었다.

<td>{{ invoice.ordered_time | moment:'YYYY년 MMMM Do dddd a h:mm' }}</td>

원하는 출력 형태를 써주면 filter에서 moment(원래적히던값).format(적어준포맷)으로 모멘트를 통과해서 뿌려준다.

Outcome

스크린샷 2016-01-25 오후 7.03.20

예뻐짐.