※ 이 글은 How to Use Extend with TypeSript 를 번역한 글입니다.

 

출처 - 구글 이미지 검색

mkdir src && cd src
mkdir __tests__
mkdir typings
mkdir example

 

Jest 는 현재 가장 인기 있는 테스팅 라이브러리 중 하나 입니다. 코드의 안정성과 신뢰성을 보장해주기 위해서 테스트 코드의 작성 여부는 선택이 아닌 필수가 되어버렸습니다. 

 

 

 

최근 1년간 NPM 다운로드 현황 (출처 - NPM 트렌드 현황)

 

 

기본적으로 Jest 는 여러 환경에서 테스트 코드를 작성할 수 있는 환경을 제공합니다. 물론 이 환경에서는 비동기 호출이나 다소 복잡했던 로직들도 테스트할 수 있는 환경 역시 구축할 수 있습니다. 그런데 Jest 가 제공하는 환경에서가 아니라 좀 더 구체적으로 내가 작업하고 있는 환경에 맞게 테스트하고 싶은 경우는 어떻게 해야할까요. 저의 경우는 "서버로 데이터를 요청하는 API 요청에 대한 응답값이 JSON 스키마와 항상 일치하는지 어떻게 확신할 수 있을까?" 에 대한 고민이 있었습니다. 왜냐하면 실제 프로덕션 레벨에서 다뤄지는 서버 데이터는 매 요청마다 결과 값이 다를 수 있기 때문에 상황이 복잡합니다. 주로 실시간 데이터인 항공 데이터나 숙박 검색과 같은 경우가 이런 경우입니다. 

 

 

이럴 때 Jestextend 는 커스터마이징 테스트 메소드를 구현할 수 있게 해줍니다.

 

 

 

 

프로젝트 구성하기

작업을 하기에 앞서 먼저 프로젝트를 구성을 시작하겠습니다.

npm init -y

 

 

필요한 패키지 역시 설치해 줍니다.

npm i -D jest ts-jest typescript @types/jest

// package.json
"scripts": {
  "test": "jest"
}

 

 

 

마지막으로, 폴더를 생성하면 끝입니다.

mkdir src && cd src
mkdir __tests__
mkdir typings
mkdir example

 

여기까지 따라왔다면, 디렉토리 구조는 아래와 같이 생성 됐을 것입니다. 간단해 보이지만 (실제로 간단하지만) 여기까지 잘 따라오셨다면 이제 모든 준비가 된 겁니다!

node_modules
src
|--- __tests__
|--- example
|--- typings
package-lock.json
package.json

 

 

 

 

 

설정 파일 작성하기

이 단계에서는, 타입스크립트가 우리가 테스트 코드를 작성할 때, 새롭게 만든 타입 함수들을 사용할 때 그게 뭔지 이해할 수 있도록 하는 사전 작업이 필요합니다. 별 다른 어려움이 없으니 천천히 따라해보세요.

 

 

먼저, 루트폴더에 jest.config.js 파일과 tsconfig.json 파일을 만드시기 바랍니다. 이 파일들은 테스트 코드를 실행할 때 파일 런타임 때 자동으로 읽히게 될 것입니다. 설정 파일을 따로 만들고 싶지 않다면 package.json 파일에 설정을 추가하는 식으로 대신할 수도 있습니다. 좀 더 많은 정보가 필요하다면 Jest 환경 설정을 클릭하세요.

 

touch jest.config.js
cd src && touch tsconfig.json

 

 

 

 

 

 

 

 

 

 

 

 

 

기본 테스트코드 작성

이제 기본적인 테스트 코드를 작성해 봅시다.

 

cd src && cd example
touch math.ts

 

 

 

 

 

그리고 방금 생성한 math.ts 파일을 테스트할 테스트 파일을 만듭니다.

테스트 파일을 만들 때 주의할 점은 반드시 테스트 파일 이름은 *.spec.* 형식으로 지어져야 합니다.

 

cd __tests__
touch math.spec.ts

 

 

 

 

 

mockedAdd 는 spy 함수로 spy 함수내에 파라미터로 전달된 원래 함수(math.add)가 사용됐을 때 추적을 할 수 있도록 하는 아주 유용한 기능의 메소드입니다. 원래 함수가 만약 객체라면 spy 함수는 그 객체의 주소 값을 복사한 값을 갖고 있는 새 변수라고 생각하는 것이 더 쉬울 수도 있겠습니다.

 

const initialObj = { sayHi: () => console.log('hi') };

// initialObj 의 주소 값을 가집니다.
// initialObj 의 변경점에 대해서 실시간으로 같이 공유받을 수 있다는 점을 기억하세요.
const spyObj = initialObj;

// spyObj 와 비슷한 개념으로(그러나 엄연히 다른 개념입니다) 기존 객체의 메소드의 변경 사항을 
// 계속 실시간으로 추적할 수 있다고 생각해보세요.
// 어떤 파라미터로 불렸는지, 몇 번 실행 됐는지 등 다양하게 사용처를 추적할 수 있습니다.
const spyFn = jest.spyOn(initialObj, 'sayHi');

spy 함수에 대해 더 자세한 정보를 보고 싶으시면 Jest 공식 문서를 참조하세요.

 

 

테스트 코드를 보면 expect() 를 통해 테스트하는 부분에서 서로 다른 방식으로 테스트를 하고 있는 부분을 보실 수 있을 겁니다. 위에서 짧게나마 소개한 spy 함수와 원래 함수의 반환 값을 이용해 테스트를 진행할 수도 있고 mock 객체에 포함된 calls, results 같은 원본 함수 추적용 배열을 사용해 테스트를 할 수도 있습니다.

 

 

그런데 테스트 코드를 작성하다보면 기본 테스트 메소드를 사용할 수 없는 경우가 있습니다. 예를 들어 값을 전달 받아 분기 처리를 한 후에 결정해야하는 경우 등이 그렇습니다. 그럴 때마다 그럼 테스트 코드를 이런식으로 작성해야 할까요?

 

if (num % 2 === 1) {
  expect(num).toBe(yourOdd);
} else {
  expect(num).not.toBe(yourOdd);
}

 

 

expect() 를 사용해 저런식으로 가정법과 같이 값에 따라 결과가 불확실한 형태로 테스트 코드를 작성하는 건 권장하고 싶지 않습니다. 테스트 코드 블록의 느낌을 산만하게 만들고 한 눈에 들어오기 점점 더 어려워지기 때문입니다.

 

 

이럴 때 사용하는 기능이 있습니다.

 

 

expect.extend()

Jest 는 테스터들이 좀 더 자유롭게 테스트 함수(이제부터는 matchers 라고 부르겠습니다) 를 입맛에 맞게 작성해서 사용할 수 있는 자율성을 갖고 있습니다. 타입스크립트에서 Jest 를 사용하는 경우, 타이핑 파일에 커스텀 matchers 에 대한 타이핑 정보가 없기 때문에 우리는 타입스크립트가 이해할 수 있도록 타이핑 파일을 먼저 작성하는 것부터 시작하겠습니다.

 

 

expect() 로 넘기는 값이 홀수인지 판별하는 커스텀 matcher 를 만들겠습니다.

 

 

cd src && cd typings
touch index.d.ts

 

 

 

 

 

 

여기까지 따라왔을 경우 폴더 구조는 다음과 같아야합니다.

 

node_modules
src
|--- __tests__
|    |
|    |--- math.spec.ts
|--- example
|    |
|    |--- math.ts
|--- typings
|    |
|    |--- index.d.ts
|
|--- tsconfig.json
jest.config.js
package-lock.json
package.json

 

typings 폴더를 만들고 그 안의 타이핑 파일에 toBeOdd() 에 대한 타입을 정의해주었지만 아직은 테스트 코드에 그 어떤 영향도 미치고 있지 않습니다. 타이핑 파일은 단순히 타입스크립트가 toBeOdd() 라는 메소드가 존재하고 어떤 파라미터를 받고 어떤 형태의 값을 반환해야하는지 알 수 있게해주는 파일입니다. 더 쉽게 말하자면 예를 들어서 window. 같이 window 를 입력하고 . 를 입력했을 때 내부에 포함된 메소드 목록들 중 하나로 나올 수 있게 타입스크립트한테 정보를 전달해주는 파일입니다. 

 

 

이제 테스트 코드로 돌아가서 실제 사용법을 보겠습니다.

 

 

 

 

 

 

 

 

 

 

 

우리가 만든 새로운 matchertoBeOddreceived 라는 파라미터를 제외하고 아무 파라미터도 받지 않는 함수입니다. received 는 테스트 코드에서 expect() 를 호출했을 때 그 안에 넘겨주는 값입니다. 즉 expect(10) 으로 시작했다면 received 의 값은 10 입니다. 위 코드에서는 received 는 각각 2 와 3 입니다. 

 

 

toBeOdd 메소드에 파라미터를 추가로 넘기고 싶다면 타이핑 파일부터 다시 수정해야하는데, 관련된 변경 사항들만 짚어보자면 다음과 같이 될 겁니다.

 

// index.d.ts
declare global {
  ...
  interface matchers<R> { 
    toBeOdd(a: number): R;
  }
}
// math.spec.ts
expect.extend({
  toBeOdd(received, a) {
    .. do something
  }
});
it('..', () => {
  /* received will be 3, and a will be 2 */
  expect(3).toBeOdd(2);
})

 

 

matchermessagepass 라는 값을 포함한 객체를 반드시 반환 값으로 반환해야 합니다.

 

 

message - 테스트가 실패 했을 경우 콘솔에 보여줄 오류 메시지를 반환하는 메소드입니다. 아무 인자도 받지 않습니다.

pass - boolean 타입의 값으로 어떤 형식으로 테스트가 불렸는지에 따라 각각의 message 함수를 리턴할 수 있게 해줍니다.

 

 

예를 들어서 pass 가 true 인 경우, message 함수는 matcherexpect(value).not.toBeOdd() 같은 형식으로 불려진 상황에서 테스트가 실패했을 때 실행됩니다. 반대로 pass 가 false 인 경우는 matcherexpect(value).toBeOdd() 의 형식으로 테스트가 실행된 상태에서 테스트가 실패했을 때 실행되게 됩니다.

 

 

/*
* pass: false
* 2 는 홀수가 아니므로 테스트는 실패합니다
* message 함수가 반환하는 메시지는 
* 'expected 2 not to be an odd number'
*/
expect(2).toBeOdd();
/*
* pass: true
* 1 은 홀수이므로 테스트는 실패합니다
* message 함수가 반환하는 메시지는 
* 'expected 1 to be an odd number'
*/
expect(1).not.toBeOdd();

 

 

 

최종 코드

 

npm run test math

 

 

 

 

 

결론

자바스크립트 테스팅 라이브러리는 프로젝트가 배포되기 전 코드의 품질을 보증할 수 있게 해줍니다. 그래도 테스트 코드를 작성하다보면 어느 테스팅 라이브러리를 사용하고 있든 상황에 맞는 필요한 matcher 를 찾기 힘든 상황이 오기 마련입니다. 그래서 이번 포스팅에서 소개해드린 기능은 여러분이 작성하고 있는 테스트 코드의 유연함을 더욱 증가시켜줄 것입니다.

※ 알림

이 글은 원작자의 허락을 받고 번역한 번역글입니다.

번역의 부드러운 이해를 위해 생략, 의역 및 추가한 부분이 있을 수 있습니다.

 

원본 글 보러가기

 


 

우리가 매일 사용하는 이 아름다운 도구가 실제로는 어떻게 동작하고 있는지 생각해본 적이 있나요? 컴파일러는 실제로 어떻게 돌아가고 있을까요? 입맛에 맞게 컴파일 옵션을 바꾸고 싶진 않으시구요? 이 포스팅이 아마도 제법 도움이 될 거라고 생각합니다.

 

 

바벨(Babel)은 실로 굉장히 멋진 녀석입니다. 페이스북, 구글, 넷플릭스 외에도 수 백개의 다른 기업들이 이용하고 있지요. npm 월 다운로드 수는 이미 7백만을 넘어선지 오래입니다. 바벨은 ES6 뿐만 아니라 ES7, JSX, 폴리필(Polyfill), 플로우(flow) 까지 지원하고 있습니다. 만약 이 것들이 충분하지 않다고 느끼신다면 TC39(JavaScript 를 관리하고 리드하는 기술 위원회)에서 관리하고 있는 아직 정식 버젼에 포함되지도 않은 그야말로 최신 기능까지도 사용할 수 있게 해줄정도로 바벨은 굉장히 멋진 녀석입니다.

 

 

자, 이제 사설은 이쯤에서 마무리짓고, 본론으로 들어가봅시다. 

바벨은 소스 컴파일러입니다. 조금 더 자세하게 말하자면, 아래의 예를 보는게 더 나을 것 같습니다. 

ES6의 애로우 펑션(Arrow Function)입니다.

 

만약, JavaScript에서 이 함수를 사용할 일이 있다고 해보겠습니다.

(foo, bar) => foo + bar;

 

브라우저는 위 코드가 아래와 같은 형식이길 기대합니다.

"use strict"

(function(foo, bar) {
	return foo + bar;
});

 

 

위의 과정은 3 단계로 나뉘어져 있습니다.

1 단계: 파싱(Parsing)

바벨은 먼저 소스 코드를 가지고 추상적인 형태의 코드로 변환하는 과정을 수행하는데, 이를 바벨에선 파싱(Parsing)이라 표현합니다. 그리고 이 추상적인 형태의 코드는 추상구문트리(AST: Abstract Systax Tree) 라고 부릅니다.

 

 

추상구문트리(AST)는 추상화된 코드의 표현체입니다. 각각의 노드는 소스코드의 구조를 의미합니다. 이 추상구문트리는 소스코드의 좀 더 원활한 변환(transformation)을 위해 작성되는 표현체입니다.

 

 

바벨의 수 많은 플러그인 중에선 babel-parser 라고 불리는 플러그인이 있는데, 이 babel-parser 의 또 다른 이름은 바빌론(bablyon) 입니다. 추상구문트리는 바빌론에 의해 작성됩니다. 추상구문트리는 위의 애로우 펑션(Arrow Function)을 다음과 같이 표기합니다.

// AST shortened for clarity
{
    "program": {
        "body": [
            {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "ArrowFunctionExpression",
                    "params": [
                        {
                            "type": "Identifier",
                            "name": "foo"
                        },
                        {
                            "type": "Identifier",
                            "name": "bar"
                        }
                    ],
                    "body": {
                        "type": "BinaryExpression",
                        "left": {
                            "type": "Identifier",
                            "name": "foo"
                        },
                        "operator": "+",
                        "right": {
                            "type": "Identifier",
                            "name": "bar"
                        }
                    }
                }
            }
        ]
    }
}

 

추상구문트리에서 볼 수 있듯이, 이 트리는 소스코드의 각 구문과 각 코드간의 관계를 모두 서술하고 있습니다. 추상구문트리는 아래의 사이트에서 좀 더 이리저리 자유롭게 바꿔가며 실험해보실 수 있습니다.

 

>[추상구문트리 변환 사이트 바로가기]

 

 


[재미로 보는 내용]

바벨(Babel)과 바빌론(Bablyon)은 모두 구약성경의 내용과 깊은 연관이 있습니다. 사실 사전에는, 바벨과 바빌론은 모두 고대 바벨로니아의 도시를 뜻하는 용어라고 표시되어 있습니다. 단지 영어로 표기하면 Bablyon, 히브리어로 표기한다면 Bável 으로 표기됩니다. 사실 우리는 바벨과 바빌론보다는 바벨탑을 조금 더 친숙하게 느끼고 있습니다. 바벨탑은 신에게 닿기 위해 높이 쌓아올린 탑이라고 알려져 있죠. 

 

창세기 11장 4절에는 이런 구문이 있습니다.

또 사람들은 논의하였다. "어서 도시를 세우고 그 가운데 꼭대기가 하늘에 닿게 탑을 쌓아 우리 이름을 날려 사방으로 흩어지지 않도록 하자."

... (중략)

주님께서 온 세상의 말을 거기에서 뒤섞어놓아 사람들을 온 땅에 흩으셨다고 해서 그 도시의 이름을 바벨이라고 불렀다.

 

JavaScript 의 브라우저들이 저마다 각각 다른 호환성을 보장하는 것이 싫어 탑을 높게 쌓아올려(BabelJS) 호환성을 하나로 묶고 싶었던 마음에서였을까요? BabelJS 의 플러그인들에서 보여지는 네이밍들은 구약성경의 바벨과 어딘가 모르게 연관되는 듯한 느낌마저 주는듯 합니다.


 

2단계: 변환(Transformation)

이 단계가 바로 마술이 펼쳐지는 곳입니다. 바벨은 1단계에서 파싱된 추상구문트리를 받아와 각 브라우저의 환경에 맞는 결과로 변환하는 작업을 수행합니다.

 

 

바벨의 플러그인 중 preset/plugin 에 의해 처리되는 곳이기도 합니다. preset 은 특별한 건 없고 plugin 들을 모아놓은 배열입니다. 단지 plugin 을 전부 하나씩 수동으로 등록할 수고를 덜어줄 뿐입니다. 바벨에서 지원하고 있는 preset/plugin 을 사용해도 좋고, 따로 입맛에 맞게 새로 만들어서 사용해도 상관없습니다.

 

 

이 플러그인들은 babel-traverse 를 이용해 원본 추상구문트리를 가로질러가는(traverse) 과정 속에서 각 부분들이 어떻게 바뀌고 어떻게 정의되어야하는지를 기록합니다.

 

 

1단계에서 생성된 애로우 펑션(Arrow Function) 에 대한 추상구문트리는 2단계에 접어들게되면서 babel-traverse 에 의해 재작성됩니다. 이 과정에서 바로 위에서 설명한 것처럼 브라우저 호환 환경에 맞게 적절한 형태로 변환될 수 있도록 안내 가이드같은 것들이 적히는 변형(transformation) 과정을 거치게되고, 이렇게 일련의 변형을 마치면 새로운 추상구문트리로 바뀌게 됩니다. (이 과정은 바벨의 플러그인 코드가 관여합니다)

 

아래는 새롭게 탄생한 추상구문트리입니다. 비슷해보이겠지만, 1단계에서 만들어진 추상구문트리와는 다른 점이 있습니다.

// AST shortened for clarity
{
    "program": {
        "type": "Program",
        "body": [
            {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "Literal",
                    "value": "use strict"
                }
            },
            {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "FunctionExpression",
                    "async": false,
                    "params": [
                        {
                            "type": "Identifier",
                            "name": "foo"
                        },
                        {
                            "type": "Identifier",
                            "name": "bar"
                        }
                    ],
                    "body": {
                        "type": "BlockStatement",
                        "body": [
                            {
                                "type": "ReturnStatement",
                                "argument": {
                                    "type": "BinaryExpression",
                                    "left": {
                                        "type": "Identifier",
                                        "name": "foo"
                                    },
                                    "operator": "+",
                                    "right": {
                                        "type": "Identifier",
                                        "name": "bar"
                                    }
                                }
                            }
                        ]
                    },
                    "parenthesizedExpression": true
                }
            }
        ]
    }
}

 

애로우 펑션(Arrow Function) 플러그인 코드에 대해 좀 더 살펴보실 분들은 링크를 클릭하시기 바랍니다. 그리고 바벨 플러그인 핸드북 가이드 역시 추상구문트리에 대해 좀 더 자세히 이해할 수 있도록 도와줄 것입니다.

 

 

3단계: 코드 생성(Generation)

3단계에서는 2단계에서 생성된 새로운 추상구문트리를 바탕으로 실제 브라우저 환경에 맞는 소스코드로 변환하는 과정이 이뤄집니다. 2단계에서 만들어지는 추상구문트리는 각 브라우저에서 코드 구문이 어떻게 바뀌어야하는지에 대한 내용을 기술한다고 설명했습니다. 이렇게 기술만하는 단계가 2단계라면, 실제로 이 내용을 바탕으로 코드를 생성하는 곳이 3단계입니다.

 

"use strict";
(function (foo, bar) {
  return foo + bar;
});

 

이 과정은 babel-generator 에 의해 이뤄집니다.

 

 

결국, 바벨의 동작 과정에 대한 전체 개요는 아래와 같이 정리할 수 있습니다.

 

 

 

결론

위에서 설명한 이 모든 과정이 바벨이 실제로 우리가 모르게 동작하고 있었던 과정입니다. 이 글이 모든 것을 설명하고 있지는 않습니다. 더 자세한 내용을 보고 싶다면 이 문서를 참고해보는 것도 도움이 될 겁니다.

 

 

번역 한 줄 후기

최근 바벨에 대한 기초가 너무 부족하다고 느꼈습니다. 웹팩으로 환경설정을 할 때도 뭐를 설치해야하고 버젼마다 뭐가 또 다르고.. 너무 복잡하고 끝도 없는 느낌이라 감도 안왔었습니다. 그래서 공식문서도 읽을 수 있을만큼 읽어보려고하고 동영상들도 많이 찾아보려고 했지만 전부 사용 방법에 대해서만 알려줄 뿐이었습니다.

 

 

그래서 이 글은 너무나도 저한테 한 줄기 빛과 같았습니다. 물론 전체를 자세히 다루진 않았다고 확신합니다. 그 것은 이제 각자의 몫이겠죠. 그러나 적어도 숲에 대한 개요를 요약해놓은 글이 있다는 것만으로도 충분히 번역할만한 가치가 있다고 생각했고, 시간을 들여서 기록으로 남기게 되었습니다.

 

 

좋은 글을 남겨준 원작자에게 감사를 표합니다.

 

2019/03/31 - [Web Tech/React] - React hooks (리액트 훅) 사용 방법 시리즈(1) - useState

2019/03/14 - [Web Tech/Git] - Git을 사용할 때 꼭 알아두어야 할 개념들

2019/03/31 - [Web Tech/JavaScript] - JavaScript(jQuery) - position sticky IE polyfill(폴리필) 방법

새로운 Position Sticky

position: sticky 는 정말 유용한 기능으로 고정 픽스 네비게이션 등을 구현할 때 아주 편리합니다.
다음과 같은 코드에 다음과 같이 적용만해주면 바로 적용이 되는데요.

다음과 같은 코드는 스크롤을 내릴 때 nav 엘리먼트가 상단에 고정되어 따라다니게 해줍니다.
참 좋은 기능이죠. 그러나 아주 치명적인 단점이 있습니다.

IE 에서 지원하지 않는 기능입니다.

 

브라우저 지원 현황

이게 얼마나 치명적인 느낌인지 모르시는 분들을 위해..

2018년 한국의 브라우저 사용 점유율

위 사진은 2018년 한 해를 기준으로 한국에서 데스크톱의 경우 각 브라우저의 점유율을 나타내주는 지표입니다.
위 지표를 보러가시려면 이 곳을 클릭하세요.
IE는 점유율이 지속적으로 낮아지고 있지만 아직까지도 전체 사용량의 약 20%를 차지할만큼의 비율을 보여주고 있습니다.
다시 말하자면, 어떤 서비스를 런칭할 때 20%의 비율은 절대 무시할 수 없는 큰 비율입니다.
따라서 반드시 고려해야하는 상황이죠.

position: sticky 의 브라우저 지원 현황

브라우저 지원 현황은 무난한 편입니다. 대신 위에서 언급한 것 처럼 IE 가 전혀 지원을 하지 않는 상황이네요.

Polyfill 구현

그렇다면 이 문제를 해결하는 방법은 아예 없는 걸까요? 그렇지는 않습니다.

 

1) 기존의 스펙 변경

이게 무슨 해결책이냐 할 수도 있지만 어떤면에선 가장 빠르고 확실한 해결책입니다.
문제가 되는건 IE 뿐인데, 이 브라우저만 제외하면 모든 브라우저에선 동작하죠.
position: sticky 는 지원하는 브라우저에서는 동작하지만 지원하지 않는 브라우저에서는 동작하지 않습니다.
만약에 현재 운영중인 어플리케이션을 이용하는 사용자의 IE 비율이 많이 낮거나 무시할정도가 된다면, 재협의를 통해 다시 스펙을 변경하는 것도 좋은 방법이라고 생각합니다.

 

2) StickyBits 라이브러리 사용하기

position: sticky 를 구현할 수 있도록 도와주는 라이브러리가 존재합니다. StickyBits 라고 부르는 라이브러리인데, 제법 유명한 라이브러리인 것 같습니다.
라이브러리 답게 여러가지 추가 기능이 붙어있으며 이슈 제기에도 제법 빠르게 답변해주는 편입니다.
그러나 저는 이 라이브러리를 사용하지 않았습니다.

 

3) 직접 구현하기

2번을 사용을 하지 않았던 이유는 사실 제가 사용했을 땐 IE 에서 여전히 동작하지 않았습니다.
왜 그런지는 코드 내부를 들여다보지 않아서 모르겠지만요.
해당 깃허브에 PR 을 보내 코드 수정을 요청하는 것도 하나의 방법입니다만,
코드 안정성 검사를 받아야하기 때문에 시간이 걸리죠.

사실 귀찮습니다.

우선 마크업이 다음과 같이 있다고 가정합시다.

여기에 적용할 코드는 다음과 같습니다. position: fixed; top: 0;
자바스크립트를 사용해 현재 스크롤의 위치에 따라 네비게이션을 고정하는 방식입니다.

네비게이션의 top 값을 가지고와서 window.pageYOffset 과 비교하면서 position: fixed 의 여부를 결정하는 로직이 메소드 안에 들어있습니다. 그런데 이런 방식은 문제점이 있습니다.

 

Position: fixed 방식의 문제점

이러한 방식은 문제점이 있습니다.
아래의 코드를 첨부하겠습니다.

See the Pen QPLzWy by Munkyu Yang (@moonformeli) on CodePen.

스크롤을 내리다보면 뭔가 이상합니다. 네비게이션이 상단에 고정이되서 붙어 다니긴하는데, 고정이 될 때
다른 글이 팍! 하고 위로 올라가는게 느껴지시나요?

position: fixed 로 요소가 더 이상 그 공간에 종속되지 않으면서 생기는 사이드 이펙트입니다.
그럼 네비게이션 부분만큼이 빠지니까 당연히 그 아래의 컨텐츠들이 위로 올라가는거죠.

이 부분을 고정해줄 무언가가 필요합니다.

사이드 이펙트 해결

position: fixed 로 빠진 부분을 어떻게 채울 수 있을까요.
해결 방법은 똑같은 요소를 만들어서 대신 넣는다 입니다.

cloneNode 의 옵션값을 true 로 넣어주면 자식 요소들까지 복제가 됩니다. cloneNode API 보러가기
자식들까지 복제하는 이유는 자식 요소가 없으면 요소를 복제한다해도 height 값이 0 이기 때문입니다.

네비게이션을 고정할 때 복제한 요소를 넣어주고, 고정을 해제할 때 복제한 요소를 제거하는 방식으로하면 IE 에서도 부드럽게 구현됩니다.

구현 결과는 다음과 같습니다.

See the Pen rbBodd by Munkyu Yang (@moonformeli) on CodePen.

부드럽게 동작하네요!

codepen 은 IE 에서 동작하지 않습니다. 테스트를 원하신다면 아래의 코드를 복사해서 직접 테스트하시기 바랍니다

아래는 각 작업환경에 맞는 버전별 폴리필입니다. 필요하신 부분을 참고하시면 될 것 같네요.

 

ES6

 

ES5 + jQuery

 

HTML + CSS

...더보기

더 보기

참고가 되셨으면 좋겠습니다!

제 블로그에 관심 있으시고 JavaScript를 공부해야겠다고 생각되시는 분들은 읽어보시는 것도 좋을 듯 합니다.


JavaScript를 제대로 이해하지 못하고 있는 분들을 대상으로, 기초 내용부터 약간의 심화까지 담아 보려고 합니다.


1. JavaScript 기초

- 변수 Scope, 호이스팅, JavaScript에서의 함수란, 프리미티브 값

- this

- 클로져(closure)


2. Prototype

- 프로토타입의 기초 이해

- 프로토타입 관계도 실습

- 프로토타입을 이용한 상속


3. 성능 향상 / 최적화

- for loops

- DOM

- Event handlers

- 기타


4. 그 외의 이야기

- ES6

- Framework / Libraries



혹시나 질문이 개인적으로 있으신분들은 방명록에 비밀글로 남겨주시면 답변드릴 수 있는 범위 내에선 답변 드리겠습니다~

간단하면서도 쉬운 메뉴 펼치기를 구현해보도록 하겠습니다. jQuery의 클래스 선택자만 이용하면 아주 간단하게 구현할 수 있는 기능입니다.


우선, jQuery의 선택자에 대해 모르시면 잠시 들렀다 오세요.


그럼 시작해보겠습니다.


먼저 틀이 되는 HTML을 작성해보겠습니다.




기본 코드의 바탕은, 네이버 메인사이트의 [더보기]를 참고했습니다. 

아래 사진을 보실게요.


사진에서처럼 두 개의 span 태그가 존재하고, '더보기' 라는 텍스트는 자식 태그에 삽입이 되어 있는 상태입니다. 아래에 적용된 CSS코드를 보겠습니다.





위의 그림은 background-image에 적용된 이미지의 url에 실제로 저장되어 있는 그림 파일입니다. 그림 파일이 많아질 경우에는 저런 식으로 하나의 파일에 모두 담은 후에, background-position을 이용해 원하는 위치에 있는 내역을 잘라서 가져오기도 합니다.


background-position에 대해 모르시면 잠시 들렀다오세요.


.more 클래스에서는 원하는 위치의 그림을 불러오고 display:block, width, height 속성을 적용해 그림 파일이 온전한 제 영역을 가질 수 있도록 설정하였습니다. block이나 inline-block 둘 중에 하나로는 반드시 설정해야 높이와 너비를 설정할 수 있으니 잊지마세요!


block, inline 속성에 대해 모르시면 잠시 들렀다오세요.


.blind 클래스에서는 기존에 존재하는 텍스트를 지워주기 위해 clip 속성을 이용했는데요. 좌표 값을 상하좌우 모두 0으로 설정해서 결국에는 보여지게 되는 영역이 '없게 되는' 원리를 이용했습니다. 어떻게보면 편법같습니다.


clip 속성에 대해 모르시면 잠시 들렀다오세요.


clip속성을 사용하기 위해선 반드시 position:absolute 를 설정해야한다는 점 기억해두세요!


다음으로는 클릭할 때 '더보기'와 '접기'가 번갈아가면서 나오도록 설정해보도록 하겠습니다.



.close 클래스에 대한 속성은 좌표값만 달라졌을 뿐 .more와 동일하게 적용이 되었습니다. 그리고 마우스가 올라갔다는 것을 보여주기 위해 cursor속성을 pointer로 설정을 해주었습니다. 좀 더 직관적으로 변했죠?


그리고 다음으로는 jQuery를 사용했는데요. .more 클래스가 클릭이 되었을 경우에, 각각 경우에 수를 따집니다. hasClass 속성을 이용해 클래스가 있는지 판별을 먼저 하고 그에 따른 값 변경을 해주게 됩니다. 여기서 뭔가 살짝 이상한데, .more에 클릭이 되었을 경우에 클래스 명이 .close로 변경이 되는데 여전히 클릭 이벤트는 .more가 받는다는 것입니다. 이 부분은 저도 왜 그런것인지 정확하게는 모르겠습니다. 혹시 아시는 분이 있다면 댓글로 정정해주시면 감사하겠습니다.


현재까지 완성된 결과물을 확인해보겠습니다.


See the Pen LLrejd by Munkyu Yang (@moonformeli) on CodePen.


다음으로는 펼쳐져서 보여질 메뉴를 만들어보겠습니다.


네이버에서 더보기를 눌렀을 때 나오는 메뉴의 일부분을 가져와 리스트 형식으로 배치를 시켜놓았습니다. 총 3개의 ul은 float:left가 적용이 되어 가로로 나란히 배열이 되어 있는 상태이고, 넓이와 높이는 적당히 설정해주었습니다.


이제 남은 부분은 바로, jQuery 부분에 메뉴를 접었다 펼쳤다 할 때 같이 유동적으로 보였다가 가려졌다가 하는 부분만 추가하면 끝입니다.


우선 .board에는 visibility:hidden이 적용이 되어 있어야합니다. 왜냐하면 이 부분을 건드릴 것이기 때문이죠.



jQuery를 이용하면 아주 간단히 CSS를 건드릴 수 있습니다. 경우에 수에 맞게끔 visibility를 변경해주면 됩니다.


최종적으로 완성된 결과물입니다.

See the Pen WOydgr by Munkyu Yang (@moonformeli) on CodePen.


이상으로 모든 작업이 끝났습니다.

어떻게보면 매우 간단하게 만들 수 있는 것이지만 모르셨던 분들은 제 포스팅이 도움이 되었길 바랍니다.

+ Recent posts