├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── Back-End └── README.md ├── Front-End ├── Next.js │ ├── README.md │ ├── apis │ │ ├── head.md │ │ ├── image.md │ │ ├── link.md │ │ └── useSWR.md │ ├── assets │ │ ├── link-json-use.jpg │ │ ├── link-json.jpg │ │ ├── next-lifecycle.jpeg │ │ ├── pages.jpg │ │ └── pathCover.jpg │ ├── basicFeatures │ │ ├── CSS.md │ │ ├── Next.js10.md │ │ ├── dataFetching │ │ │ ├── README.md │ │ │ ├── getServerSideProps.md │ │ │ ├── getStaticPaths.md │ │ │ └── getStaticProps.md │ │ ├── environmentVariables.md │ │ ├── fastRefresh.md │ │ ├── pages.md │ │ └── staticFile.md │ ├── basicStructure.md │ ├── buildSystem │ │ ├── autoGenFiles.md │ │ └── customServer.md │ ├── customServer │ │ └── introduce.md │ ├── introduce.md │ ├── lifeCycle.md │ ├── next-redux-wrapper │ │ └── README.md │ ├── next.config │ │ └── README.md │ ├── routing │ │ ├── apiRoutes.md │ │ └── shallowRouting.md │ └── tips │ │ └── You don't know Serverside Rendering.md ├── README.md ├── React │ └── README.md ├── Redux-saga │ ├── NonBlockingCalls.md │ ├── PullingFutureActions.md │ ├── README.md │ └── TakeEffects.md └── tips │ ├── css-grid.md │ ├── micro-frontend │ ├── assets │ │ ├── images │ │ │ └── micro-frontend │ │ │ │ ├── frontend-backend.png │ │ │ │ ├── microservices.png │ │ │ │ ├── monolith-frontback-microservices.png │ │ │ │ ├── monolith.png │ │ │ │ ├── three-teams.png │ │ │ │ ├── verticals-headline.png │ │ │ │ └── verticals.png │ │ └── video │ │ │ └── micro-frontend │ │ │ ├── custom-element-attribute.gif │ │ │ ├── custom-element-attribute.mp4 │ │ │ ├── custom-element.gif │ │ │ ├── custom-element.mp4 │ │ │ ├── data-fetching-reflow.gif │ │ │ ├── data-fetching-reflow.mp4 │ │ │ ├── data-fetching-skeleton.gif │ │ │ ├── data-fetching-skeleton.mp4 │ │ │ ├── model-store-0.gif │ │ │ ├── model-store-0.mp4 │ │ │ ├── server-render.gif │ │ │ └── server-render.mp4 │ └── readme.md │ ├── npm-package.md │ └── web-cache.md ├── JavaScript ├── DataType.md ├── ExecutionContext.md ├── README.md ├── algorithm.md ├── assets │ ├── HayoungProto.png │ ├── PersonPrototype.png │ ├── callStack1.png │ ├── callStack2.png │ ├── callStack3.png │ ├── callStack4.png │ ├── closureOverwrite.png │ ├── closureOverwriteDisable.png │ ├── executionContext.png │ ├── functor.jpg │ ├── instance.png │ ├── memoryAddress1.png │ ├── memoryAddress2.png │ ├── memoryAddress3.png │ ├── memoryAddress4.png │ ├── memoryAddress5.png │ ├── memoryAddress6.png │ ├── memoryAddress7.png │ └── prototype.png ├── callback.md ├── class.md ├── closure.md ├── fp.md ├── hoisting.md ├── jsHistory.md ├── propertyAttribute.md ├── prototype.md ├── this.md └── usefulCode.md ├── LICENSE ├── README.md └── TypeScript └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Road-of-CODEr/js-haters 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Stupid-week Code of Conduct 2 | 3 | 타인에게 모욕감, 불쾌감을 줄 수 있는 모든 발언(종교, 정치, 나이, 인종, 국가, 외모, 성 정체성, 민족 등)은 금지합니다. 4 | 5 | 행동강령은 모든 개인에게 해당하며 위반 시 퇴장 등의 제재를 받을 수 있습니다. 6 | 7 | 행동강령 위반 사례에 대한 내용은 공개될 수 있습니다. 8 | 9 | ## 행동 10 | 11 | 1. 참여자는 다른 모든 참여자에게 안전하고 협조적인 자세로 공동체 환경을 제공한다. 12 | 2. 참여자는 행사에 지장을 주는 언행 및 기여자 혹은 다른 개인의 참여를 방해하는 행위를 하지 않는다. 13 | 3. 참여자는 모든 관련 법률을 준수한다. 14 | 15 | ## 범위 16 | 17 | 모든 기여자(Maintainer, Admin 포함)가 본 행동강령의 원칙을 지킬 것을 기대합니다. 본 행동강령은 주요 이벤트와 모든 관련 이벤트(온/오프라인 사교 모임 포함)에 적용됩니다. 18 | 19 | ## 결과 20 | 21 | 참여자가 본 행동강령을 준수하지 않는 것으로 판단되는 경우 Road-of-CODEr 의 모든 레파지토리 및 슬랙, 관련 행사에 참여가 금지될 수 있습니다. 22 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 기여 가이드라인 2 | 3 | 기여해 주셔서 감사합니다! 어려움이 있으시면 슬랙 혹은 이슈로 남겨주세요. 친절하게 답변 드리겠습니다. 4 | 5 | ## Pull Request 체크리스트 6 | 7 | PR을 날리기 전에 아래 리스트를 확인해보세요. 8 | - [PR 템플릿](https://github.com/Road-of-CODEr/stupid-week/blob/master/.github/PULL_REQUEST_TEMPLATE.md)을 맞춰주셨나요? 9 | - [기여 가이드라인(Contributing guidelines)](https://github.com/Road-of-CODEr/stupid-week/blob/master/.github/CONTRIBUTING.md) 읽어보기. 10 | - [행동강령(Code of Conduct)](https://github.com/Road-of-CODEr/stupid-week/blob/master/.github/CODE_OF_CONDUCT.md) 읽어보기. 11 | - 이 레포의 [LICENSE](https://github.com/Road-of-CODEr/stupid-week/blob/master/LICENSE)를 확인하셨나요? 12 | 13 | ## 어떻게 컨트리뷰터가 되고 여러분의 코드를 제출할까요 14 | 15 | ### 컨트리뷰터 라이센스 계약 16 | 17 | MIT 라이센스로 누구나 직/간접적으로 수정 및 재배포 할 수 있습니다. 소유권에 대한 이슈가 있을만한 내용은 피해주세요. 18 | 19 | ### 코드 기여하기 20 | 21 | 1. 레파지토리를 fork 합니다. 22 | 23 | 2. 개인 깃헙 '닉네임'으로 branch 를 만듭니다. 24 | 25 | 3. 해당 브랜치에서 기여하실 내용을 작성합니다.(분류를 잘 나눠 주세요!) 26 | 27 | 4. 내용을 모두 작성하셨다면 자신의 브랜치에 푸쉬 합니다. 28 | 29 | 5. 깃헙 사이트에서 PR 을 열어주시면 됩니다.(개인 레파지토리 -> 원격지 master) 30 | 31 | 6. 구성원들의 동의하에 머지가 됩니다. 32 | 33 | ### 주의사항 34 | 35 | - 이미지는 가능한 용량을 압축해서 올려주세요! 36 | - 외부 링크 보다는 본문 내용으로 채워주세요! 37 | 38 | ### 감사합니다! 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 아래의 내용을 채워주세요 2 | 3 | 1. 공부한 내용의 분류 4 | 2. 공부한 내용 요약 5 | 3. 참고 자료 / 책 6 | - 7 | 8 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules/ -------------------------------------------------------------------------------- /Back-End/README.md: -------------------------------------------------------------------------------- 1 | # JS for Back-End 2 | 3 | 다양한 백엔드 기술 및 라이브러리들을 보여주고 설명하는 레포 4 | 5 | JS runtime 6 | 7 | - Node.js 8 | - Deno 9 | 10 | Framework 11 | 12 | - Express 13 | - Koa 14 | - NestJS 15 | 16 | DB 17 | 18 | - Sequelize 19 | - TypeORM 20 | -------------------------------------------------------------------------------- /Front-End/Next.js/README.md: -------------------------------------------------------------------------------- 1 | # Next.js 를 알아보자 2 | 3 | [![image](https://user-images.githubusercontent.com/23524849/98015002-abdb9700-1e3f-11eb-8817-ac621306def5.png)](https://youtu.be/jg2ha2RIWN0) 4 | 5 | 위 영상을 먼저 보면 훨씬 이해하기 쉬울 듯 하다. 6 | 7 | `NextJS` 를 이해하고 각 API 들의 사용법과 구조를 이해하기 위한 레파지토리. 8 | 9 | ## Intro 10 | 11 | 1. [NextJS 가 뭔가요?](introduce.md) 12 | 2. [기본 구조](basicStructure.md) 13 | 3. 기본 기능 14 | 1. [Pages](basicFeatures/pages.md) 15 | - Static Generation, Server Side Rendering, Dynamic Route, Build time 16 | 2. [Data Fetching](basicFeatures/dataFetching/README.md) 17 | - getStaticProps, getStaticPaths, getServerSideProps, fallback, [[...slug]], PreviewMode, useSWR 18 | 4. 라우팅 19 | 5. API 라우팅 20 | 6. [redux-saga 와 함께 사용하기](next-redux-wrapper/README.md) 21 | 7. 배포 22 | 8. 고급 기능 23 | 9. Next.js API 살펴보기 24 | 10. `next.config.js` 다이브하기 25 | 26 | -------------------------------------------------------------------------------- /Front-End/Next.js/apis/head.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/apis/image.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/apis/link.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/apis/useSWR.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/assets/link-json-use.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/Next.js/assets/link-json-use.jpg -------------------------------------------------------------------------------- /Front-End/Next.js/assets/link-json.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/Next.js/assets/link-json.jpg -------------------------------------------------------------------------------- /Front-End/Next.js/assets/next-lifecycle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/Next.js/assets/next-lifecycle.jpeg -------------------------------------------------------------------------------- /Front-End/Next.js/assets/pages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/Next.js/assets/pages.jpg -------------------------------------------------------------------------------- /Front-End/Next.js/assets/pathCover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/Next.js/assets/pathCover.jpg -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/CSS.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/Next.js10.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/dataFetching/README.md: -------------------------------------------------------------------------------- 1 | # Next.js 에서 데이터 가져오기 2 | 3 | 이전의 [페이지 문서](pages.md)에서 사전 렌더링을 위해 데이터를 가져오는 두 가지 방법으로 **Static Generation**과 **ServerSide Rendering**이 있다고 했다. 4 | 5 | 이번 장에서는 각 렌더링에서 사용할 수 있는 함수를 알아보자. 6 | 7 | 1. [`getStaticProps`(Static Generation)](getStaticProps.md): 빌드시 데이터를 가져온다. 8 | 2. [`getStaticPaths`(Static Generation)](getStaticPaths.md): 사전 렌더링 할 **동적 경로**를 설정한다. 9 | 3. [`getServerSideProps`(Server Side Rendering)](getServerSideProps.md): 모든 요청에 데이터를 가져온다. 10 | 4. `fetch`, `useSWR`(Client Side): 클라이언트 측에서 데이터를 가져오는 방법. 11 | -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/dataFetching/getServerSideProps.md: -------------------------------------------------------------------------------- 1 | # getServerSideProps 2 | 3 | 이전의 정적 생성과 달리 유저의 요청마다 페이지를 렌더링해서 내려주고 싶다면 `getServerSideProps` 를 설정하면 된다. 4 | 5 | ```javascript 6 | export async function getServerSideProps(context) { 7 | return { 8 | props: {}, // will be passed to the page component as props 9 | } 10 | } 11 | ``` 12 | 13 | `context` 파라미터에는 아래와 같은 값들이 들어가게 된다. 14 | - `params`: 동적 라우팅에서 사용될 id 값들이나 데이터가 들어가게 된다. 15 | - `req`: HTTP request 객체. 16 | - `res`: HTTP response 객체. 17 | - `query`: 요청 쿼리 스트링 18 | - `preview`: 프리뷰 모드 여부.([`getStaticProps` 예시](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getStaticProps.md#example-preview-mode)) 19 | - `previewData`: `setPreviewData`에 설정한 미리보기 데이터. 20 | - `resolvedUrl`: 요청 URL 의 `_next/data` 접두사를 제거해 원래 쿼리 값을 포함하는 URL(예: `localhost/post/123?name=1ilsang` 로 요청하면 `/post/123?name=1ilsang` 이 resolvedUrl 에 담겨온다) 21 | - `locale`: 활성화된 locale 리턴(locale 은 설정 언어이다.) 22 | - `locales`: 지원하는 언어 리스트(next.config 에서 설정해 줄 수 있다.) 23 | - `defaultLocale`: 기본 언어 설정 24 | 25 | `return` 하는 값들(props 는 반드시 있어야 한다.) 26 | - `props`: 컴포넌트에서 사용할 직렬화된 데이터 객체 27 | - `notFound`: 해당 경로에 없으면 404 페이지 및 상태코드를 반환할지 여부(true 면 404 페이지로 간다) 28 | 29 | ```javascript 30 | export async function getServerSideProps(context) { 31 | const res = await fetch(`https://.../data`) 32 | const data = await res.json() 33 | 34 | if (!data) { 35 | return { 36 | notFound: true, 37 | } 38 | } 39 | 40 | return { 41 | props: {}, // will be passed to the page component as props 42 | } 43 | } 44 | ``` 45 | 46 | - `redirect`: 내/외부의 리소스로 리다이렉트 할 수 있는 인자. 47 | 48 | ```javascript 49 | export async function getServerSideProps(context) { 50 | const res = await fetch(`https://...`) 51 | const data = await res.json() 52 | 53 | if (!data) { 54 | return { 55 | redirect: { 56 | destination: '/', 57 | permanent: false, // statusCode 로 응답해도 된다. 58 | // statusCode 는 커스텀 상태 코드를 사용하는 오래된 클라이언트를 위해 적용해 줄 수 있다. 59 | }, 60 | } 61 | } 62 | 63 | return { 64 | props: {}, // will be passed to the page component as props 65 | } 66 | } 67 | ``` 68 | 69 | `getServerSideProps` 는 client-side 의 `bundle`에 포함되지 않으므로 최상위 범위의 모듈을 가져올 수 있다. 70 | 71 | 이는 **Server Side 코드를 바로 사용할 수 있다는 뜻**이다.(데이터베이스에 접근하는 등) 72 | 73 | 이때 API Route(`pages/api/...`) 를 사용한다면 `fetch` 로 가져올 수 없다. 따로 함수형태로 만들어 주어야 한다. 74 | 75 | ### example: API Route for getServerSideProps 76 | 77 | ```javascript 78 | // pages/api/post.js 79 | const getData = async () => Promise.resolve({ awe: 'some' }); 80 | 81 | const handler = async (req, res) => { 82 | const data = await getData(); 83 | res.json({ data }); 84 | }; 85 | 86 | export const getAwesomeData = async () => { 87 | return await getData(); 88 | }; 89 | 90 | export default handler; 91 | ``` 92 | 93 | ```javascript 94 | // pages/post-api.js 95 | import api, { getAwesomeData } from './api/post'; 96 | 97 | const PostApi = ({ data }) => { 98 | console.log('postApi Client', data); 99 | 100 | return ( 101 | <> 102 |
Page: post/api/{data.awe}
103 | 104 | ); 105 | }; 106 | 107 | export const getServerSideProps = async () => { 108 | // Direct fetch call will be throw Error! 109 | // const res = await fetch('http://localhost:3000/api/post'); 110 | const data = await getAwesomeData(); 111 | console.log('GET DATA!', data, data?.awe); 112 | 113 | return { 114 | props: { 115 | data, 116 | }, 117 | }; 118 | }; 119 | 120 | export default PostApi; 121 | ``` 122 | 123 | ## 언제 `getServerSideProps` 를 사용해야 할까? 124 | 125 | 요청 타임마다 반드시 데이터를 가져와야 할 경우 사용하면 좋다. Time to first byte(TTFB) 는 `getStaticProps` 보다 느릴 것이다. 모든 요청마다 서버에서 렌더링후 페이지를 내려주기 때문이다. 또한 CDN 캐쉬도 할 수 없다. 126 | 127 | 페이지별 데이터가 자주 바뀌거나 유저의 요청에 따라서 결과 페이지가 달라질 경우 사용한다. 128 | 129 | 만약 데이터를 사전 렌더링 할 필요가 없다면 클라이언트 사이드 요청을 고려해도 괜찮다. 130 | 131 | ## getServerSideProps 의 기술적 세부 사항 132 | 133 | ### 오직 서버 사이드에서만 실행된다. 134 | 135 | `getServerSideProps` 는 브라우저에서 절대 실행되지 않는다. 136 | 137 | `getServerSideProps` 가 사용되는 경우는 138 | - 페이지 요청을 서버로 바로 하는 경우 `getServerSideProps` 에서 렌더링 한다. 139 | - `next/link`, `next/router` 를 통해 클라이언트 사이드에서 서버사이드렌더링 페이지로 접근할 경우 Next.js 는 API 를 서버로 보내고 `getServerSideProps` 가 실행되어 결과 값을 포함한 JSON 파일을 내려준다. 그 후 JSON 파일은 페이지에서 렌더링 된다.(`props`) 모든 작업은 Next.js 에서 자동으로 관리한다. 따라서 `getServerSideProps` 이외의 것들은 설정할 필요가 없다. 140 | 141 | ### 오직 페이지에서만 사용 가능하다. 142 | 143 | `getServerSideProps` 는 컴포넌트 및 prototype 으로 사용할 수 없다. 오직 `pages` 밑의 page 들만 사용 가능하다. 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/dataFetching/getStaticPaths.md: -------------------------------------------------------------------------------- 1 | # getStaticPaths 2 | 3 | 만약 동적 라우트를 사용하면서 `getStaticProps` 를 사용한다면 빌드 시점에 HTML 렌더링을 위해 정의된 `path` 가 필요하다. 이것을 위한 함수. 4 | 5 | ```javascript 6 | export async function getStaticPaths() { 7 | return { 8 | paths: [ 9 | { params: { ... } } // See the "paths" section below 10 | ], 11 | fallback: true or false // See the "fallback" section below 12 | }; 13 | } 14 | ``` 15 | 16 | 페이지에서 비동기 함수 `getStaticPaths` 를 `export` 하게 되면 빌드시 다이나믹 라우팅을 정적으로 사전 렌더링 해줄 수 있게 된다. 17 | 18 | ## The `paths` key(required) 19 | 20 | `paths` 인자에 들어오는 키값으로 어떤 경로를 사전 렌더링할지 결정한다. 만약 `pages/posts/[id].js` 라고 페이지를 만들었다면 `paths` 에서 설정해 주어야 하는 경로 키값은 `id` 가 된다. 21 | 22 | ```javascript 23 | return { 24 | paths: [ 25 | { params: { id: '1' } }, 26 | { params: { id: '2' } } 27 | ], 28 | fallback: ... 29 | } 30 | ``` 31 | 32 | 위와 같이 해주게 되면 Next.js 는 정적 생성으로 `posts/1`, `posts/2` 페이지를 빌드 시점에 만들어 주게 된다. 33 | 34 | **이때 `id` 와 같이 매개 변수의 값은 페이지 이름에 사용된 값과 일치해야 한다**. 35 | 36 | - `pages/posts/[postId]/[commentId].js` 일 경우 `paths: [{params: { postId: 1, commentId: 2}}]` 37 | - 만약 모든 경로를 처리하고 싶다면 `pages/[...slug]` 와 같이 해주면 된다. 이 경우 `params` 는 배열 형태의 `slug` 값을 포함하게 된다. 만약 `slug: ['foo', 'bar']` 라면 `/foo/bar` 경로에 해당한다는 뜻. 38 | 39 | ```javascript 40 | // pages/com/[...slug].js 41 | const com = ({ slug }) => { 42 | if (!Array.isArray(slug)) return

com

; 43 | 44 | // localhost/100/101 - 사전 렌더링으로 만들어진 페이지 45 | // localhost/100/101/102 - 이하는 fallback 으로 만들어 질 수 있는 페이지 46 | // localhost/222/7227/102 - fallback 은 밑에서 다룬다. 47 | // ...etc 모두 가능 48 | return ( 49 | <> 50 | {slug.map((e, i) => ( 51 |

52 | {i}: {e} 53 |

54 | ))} 55 | 56 | ); 57 | }; 58 | 59 | export const getStaticPaths = () => { 60 | const paths = [{ params: { name: 'user', slug: ['100', '101'] } }]; 61 | return { 62 | paths, 63 | fallback: true, 64 | }; 65 | }; 66 | 67 | export const getStaticProps = ({ params }) => { 68 | const { slug } = params; 69 | return { 70 | props: { slug }, 71 | }; 72 | }; 73 | 74 | export default com; 75 | ``` 76 | 77 | - 만약 선택적으로 모든 경로를 잡아주고 싶다면 `pages/[[...slug]]` 로 표현해 줄 수 있게 된다. `slug` 에 `null`, `[]`, `undefined`, `false` 의 값을 넣어 주면 `pages/` 로 루트 처리가 된다. 78 | - `pages/[...slug]` 로 하게 될 경우 반드시 slug 값이 있어야 하는 것과의 차이다. 79 | 80 | ## The `fallback` key(required) 81 | 82 | `getStaticPaths` 는 반드시 `fallback` 인자를 반환해야한다. 83 | 84 | ### fallback: false 85 | 86 | 만약 `fallback: false` 일 경우 `paths` 에 정의해주지 않은 다른 모든 경로는 404 page 로 떨어지게 된다. 이 기능을 통해 다른 동적 경로를 막고 원하는 경로만 정의해줄 수 있다. 87 | 88 | 만약 `paths` 에 다 일일이 정의해줄 수 없을 만큼 동적 경로가 많은데, 이 경로 이외의 동적 재생성을 원하지 않는다면 아래와 같이 처리해줄 수 있다. 89 | 90 | ```javascript 91 | // pages/posts/[id].js 92 | function Post({ post }) { 93 | // Render post... 94 | } 95 | 96 | export async function getStaticPaths() { 97 | const res = await fetch('https://.../posts') 98 | const posts = await res.json() 99 | 100 | // KEY! 각 포스트의 id 값을 뽑아내 paths 로 만들어 버렸다. 101 | const paths = posts.map((post) => ({ 102 | params: { id: post.id }, 103 | })) 104 | 105 | return { paths, fallback: false } 106 | } 107 | 108 | export async function getStaticProps({ params }) { 109 | // 각 Path 키 값에 해당하는 데이터를 가져온다. 110 | const res = await fetch(`https://.../posts/${params.id}`) 111 | const post = await res.json() 112 | 113 | return { props: { post } } 114 | } 115 | 116 | export default Post 117 | ``` 118 | 119 | ### fallback: true 120 | 121 | 만약 `fallback: true` 를 해주게 되면 `getStaticProps` 의 행동은 아래와 같이 변한다. 122 | 123 | - 빌드 시점에 `getStaticPaths` 에서 반환되는 경로가 `getStaticProps` 에 의해 사전 렌더링 된다. 124 | - **빌드시 생성되지 않은 경로는 404 페이지로 되지 않는다**. 대신 Next.js 는 각 경로의 페이지들에게 [`fallback` 상태](#fallback-pages) 임을 제공해 준다. 125 | - 요청이 들어오면 `getStaticProps` 를 백그라운드 작업으로 실행해 HTML 과 JSON 파일을 **정적 생성**(서버사이드렌더링이 아님!) 한다. 126 | - 정적 생성이 완료되면 브라우저는 정적 생성된 경로의 JSON 파일을 받게 된다. 이는 나중에 자동으로 페이지 렌더링에 필요한 props 로 사용 된다.([예시](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getStaticProps.md#html-%EA%B3%BC-json-%EC%9D%84-%EB%AA%A8%EB%91%90-%EC%A0%95%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%83%9D%EC%84%B1)) 사용자 시점에서 페이지가 fallback 상태였다가 전체 페이지로 전환 되는 것처럼 보이게 된다.(위의 `fallback 상태` 링크 설명) 127 | - 동시에, Next.js 는 사전 렌더링 페이지 리스트에 해당 경로를 추가한다. 요청을 완료한 이후 동일한 경로로 접근하게 될 경우 정적 생성된 페이지를 받게 된다.(빌드 시점에 사전 렌더링된 다른 정적 페이지와 같아지는 것) 128 | - `fallback: true` 로 설정하면 `next export` 를 사용할 수 없다.(동적 경로를 허가했기 때문에 정적 export 를 할 수 없는 것: 경로를 사전에 알 수가 없다.) 129 | 130 | ## Fallback Pages 131 | 132 | 페이지에서 `fallback` 상태는 133 | - 페이지의 props 가 비어있음 134 | - `router` 를 사용해 폴백 렌더링 상태인지 인지할 수 있다. 135 | - `router.isFallback` 이 `true` 인 경우. 136 | 137 | ```javascript 138 | // pages/posts/[id].js 139 | import { useRouter } from 'next/router' 140 | 141 | function Post({ post }) { 142 | const router = useRouter() 143 | 144 | // fallback 처리 중일 경우 로딩처럼 사용할 수 있다. 145 | // 페이지가 만들어지면 false 로 값이 변경된다. 146 | // paths 에 없는 /post/3 와 같이 접근할 때 실행된다. 147 | if (router.isFallback) { 148 | return
Loading...
149 | } 150 | 151 | // Render post... 152 | } 153 | 154 | export async function getStaticPaths() { 155 | return { 156 | paths: [{ params: { id: '1' } }, { params: { id: '2' } }], 157 | fallback: true, 158 | } 159 | } 160 | 161 | export async function getStaticProps({ params }) { 162 | const res = await fetch(`https://.../posts/${params.id}`) 163 | const post = await res.json() 164 | 165 | return { 166 | props: { post }, 167 | } 168 | } 169 | 170 | export default Post 171 | ``` 172 | 173 | ### 언제 `fallback: true` 를 사용하는 것이 유용한가? 174 | 175 | 만약 아주 많은 정적 페이지가 데이터에 의존해 존재할 경우 유용하다. 수많은 모든 페이지를 정적 생성하게 될 경우 빌드타임이 상당히 오래 걸리게 된다. 176 | 177 | 그러므로 유저가 최초 진입시 `fallback` 으로 생성하게 한다면, 유저가 접근하지 않는 페이지들은 생성하지 않기에 유리하며, 기존 빌드타임에서 필요한 `getStaticProps` 가 실행되기 때문에 사전 렌더링과 차이가 없다. 178 | 179 | 즉 이후의 요청에 대해선 정적 생성된 페이지가 내려갈 뿐 재생성하지 않아 성능적 이점이 크다. 180 | 181 | 이를 통해 빠른 빌드와 정적 생성의 이점을 모두 취할 수 있기 때문에 유저에게 좋은 경험을 준다. 182 | 183 | 여기서 또 한가지 중요한 점은 `fallback: true` 는 ***정적 페이지를 업데이트 하지 않는다***! 정적 생성 업데이트와 동적 경로를 모두 사용하고 싶다면 [Incremental Static Regeneration](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getStaticProps.md#%EC%A6%9D%EB%B6%84-%EC%A0%95%EC%A0%81-%EC%9E%AC%EC%83%9D%EC%84%B1incremental-static-regeneration) 를 사용하라. 184 | 185 | ### fallback: blocking 186 | 187 | `fallback: blocking`은 10 버전에 새로 생긴 기능으로, 위의 예시처럼 `loading...` 과 같은 fallback 페이지를 보여주는 것이 아닌 사전 렌더링이 완료될 때까지 "기다리게" 하는 옵션이다. 188 | 189 | blocking 상태일 때 `getStaticProps` 는 `fallback: true` 일때와 동일하게 동작한다. 190 | - 다른 점은 유저가 요청시 "loading/fallback" 상태를 주지 않는다. 191 | 192 | ## 언제 `getStaticPaths` 를 사용해야 하는가? 193 | 194 | 동적 경로를 사전 렌더링 하고 싶을 때 사용한다. 195 | 196 | ## getStaticPaths 의 기술적 세부 사항 197 | 198 | ### `getStaticProps` 와 같이 사용 199 | 200 | 동적 라우팅 페이지에서 `getStaticProps` 를 사용할 경우 반드시 `getStaticPaths` 와 함께 써야한다. 201 | 202 | `getStaticPaths` 를 `getServerSideProps` 와 사용할 수 없다. 203 | 204 | ### 오직 빌드 시점에만 서버에서 실행된다. 205 | 206 | ### 오직 Pages 폴더 아래에서만 가능하다. 207 | 208 | 컴포넌트에서 사용 불가능 및 컴포넌트에서 `property` 로 사용하면 안된다. 209 | 210 | ### development 모드에서는 모든 요청에 실행된다. 211 | 212 | `next dev` 에서는 빌드타임에서만이 아니라 모든 요청에 실행된다. 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/dataFetching/getStaticProps.md: -------------------------------------------------------------------------------- 1 | # getStaticProps 2 | 3 | ```javascript 4 | export async function getStaticProps(context) { 5 | return { 6 | props: {}, // will be passed to the page component as props 7 | } 8 | } 9 | ``` 10 | 11 | `getStaticProps`는 정적 페이지 빌드시에 사용되며 Next.js 가 주는 값을 `context`에 담아오고 외부 데이터를 요청하거나 재가공하여 리액트에 내려주게 된다.(`props`) 12 | 13 | - 개발모드에선 모든 요청에 실행되지만 프로덕션 모드에선 **build 타임에 한 번만 실행**된다. 14 | - 브라우저용 JS 번들에도 포함되지 않으므로 디비 쿼리 같은 코드를 작성해도 된다. 15 | 16 | `context` 의 변수들 17 | 18 | - `params`: [다이나믹 라우트](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/pages.md#%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4-2-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B2%BD%EB%A1%9C%EA%B0%80-%EC%99%B8%EB%B6%80-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%9D%98%EC%A1%B4%EC%A0%81%EC%9D%B8-%EA%B2%BD%EC%9A%B0)를 사용할 때 route 파라미터들을 가져올 수 있다.(예: `[id].js` -> `{ id: ... }`) 19 | 20 | ### example: Preview Mode 21 | 22 | ```javascript 23 | // pages/api/... 24 | export default function handler(req, res) { 25 | // ... 26 | res.setPreviewData({ ... }); 27 | // ... 28 | } 29 | ``` 30 | 31 | ```javascript 32 | export async function getStaticProps(context) { 33 | const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`) 34 | // ... 35 | } 36 | ``` 37 | 38 | - `preview`: [Preview 모드](https://nextjs.org/docs/advanced-features/preview-mode)일 경우 `true` 가 넘어오며 아닐 경우에는 `undefined` 가 된다.([예시](https://www.datocms.com/blog/live-preview-with-next-js)) 39 | - `previewData`: Preview 모드에서 정의한 `setPreviewData` 데이터가 넘어온다. 40 | - `locale`: 활성화 된 경우 locale 정보를 가져온다.(`ko`, `en` 등의 언어 설정) 41 | - `locales`: 제공해줄 수 있는 언어 셋을 가져온다. 42 | - `defaultLocale`: 설정된 기본 언어 locale 을 가져온다. 43 | 44 | `return` 하는 값들(props 를 제외하면 모두 optional 이다.) 45 | 46 | - `props`: 페이지 컴포넌트에 넘겨줄 [직렬화 가능](https://velog.io/@raram2/serialization)한 데이터(예: JSON) 47 | - [`revalidate`](#증분-정적-재생성incremental-static-regeneration): 페이지 재생성이 발생할 시간(초) 설정([예시](https://github.com/chibicode/reactions/issues/1), [추가설명](https://github.com/vercel/next.js/discussions/11427)) 48 | - `notFound`: 해당 경로에 없으면 404 페이지 및 상태코드를 반환할지 여부(true 면 404 페이지로 간다) 49 | 50 | ```javascript 51 | export async function getStaticProps(context) { 52 | const res = await fetch(`https://.../data`) 53 | const data = await res.json() 54 | 55 | if (!data) { 56 | return { 57 | notFound: true, 58 | } 59 | } 60 | 61 | return { 62 | props: {}, // will be passed to the page component as props 63 | } 64 | } 65 | ``` 66 | 67 | > 만약 [`fallback: false`](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getStaticPaths.md#fallback-blocking) 일 경우 `notFound` 는 불필요 하다. 사전 렌더링에서 모두 처리하기 때문 68 | 69 | - `redirect`: 내/외부의 리소스로 리다이렉트 할 수 있는 인자. 70 | 71 | ```javascript 72 | export async function getStaticProps(context) { 73 | const res = await fetch(`https://...`) 74 | const data = await res.json() 75 | 76 | if (!data) { 77 | return { 78 | redirect: { 79 | destination: '/', 80 | permanent: false, // statusCode 로 응답해도 된다. 81 | // statusCode 는 커스텀 상태 코드를 사용하는 오래된 클라이언트를 위해 적용해 줄 수 있다. 82 | }, 83 | } 84 | } 85 | 86 | return { 87 | props: {}, // will be passed to the page component as props 88 | } 89 | } 90 | ``` 91 | 92 | > 현재 빌드 타임에 리다이랙트는 허용되지 않음. 만약 빌드타임에 리다이랙트 위치를 알아야 한다면 [`next.config.js`에 추가](https://nextjs.org/docs/api-reference/next.config.js/redirects)해 주어야 한다. 93 | 94 | `getStaticProps` 는 client-side 의 `bundle`에 포함되지 않으므로 최상위 범위의 모듈을 가져올 수 있다.([예시 보기](https://next-code-elimination.now.sh/)) 95 | 96 | 이는 **Server Side 코드를 바로 사용할 수 있다는 뜻**이다.(데이터베이스에 접근하는 등) 97 | 98 | 이때 API Route(`pages/api/...`) 를 사용한다면 `fetch` 로 가져올 수 없다. 따로 함수형태로 만들어 주어야 한다.([Discussion](https://github.com/vercel/next.js/discussions/16068)) 99 | 100 | ### example: API Route for getStaticProps 101 | 102 | ```javascript 103 | // pages/api/post.js 104 | const getData = async () => Promise.resolve({ awe: 'some' }); 105 | 106 | const handler = async (req, res) => { 107 | const data = await getData(); 108 | res.json({ data }); 109 | }; 110 | 111 | export const getAwesomeData = async () => { 112 | return await getData(); 113 | }; 114 | 115 | export default handler; 116 | ``` 117 | 118 | ```javascript 119 | // pages/post-api.js 120 | import api, { getAwesomeData } from './api/post'; 121 | 122 | const PostApi = ({ data }) => { 123 | console.log('postApi Client', data); 124 | 125 | return ( 126 | <> 127 |
Page: post/api/{data.awe}
128 | 129 | ); 130 | }; 131 | 132 | export const getStaticProps = async () => { 133 | // Direct fetch call will be throw Error! 134 | // const res = await fetch('http://localhost:3000/api/post'); 135 | const data = await getAwesomeData(); 136 | console.log('GET DATA!', data, data?.awe); 137 | 138 | return { 139 | props: { 140 | data, 141 | }, 142 | }; 143 | }; 144 | 145 | export default PostApi; 146 | ``` 147 | 148 | ## 언제 `getStaticProps`를 사용해 주어야 할까? 149 | 150 | - 페이지를 렌더링 할 때 필요한 데이터를 사용자의 요청보다 앞선 빌드 타임에서 사용할 수 있는 경우 151 | - 데이터가 [Headless CMS](https://simsimjae.medium.com/headless-cms%EB%9E%80-49569dc86daa) 에서 제공될 때 152 | - 유저 상관 없이 퍼블릭하게 캐시될 수 있는 데이터일 때 153 | - 페이지가 반드시 사전 렌더링 되어야(for SEO) 하며 빨라야 하는 경우 154 | 155 | `getStaticProps`는 HTML 과 JSON 파일로 내려주기 때문에 CDN 에 캐시해 높은 성능을 보여줄 수 있다. 156 | 157 | 158 | ## 증분 정적 재생성(Incremental Static Regeneration) 159 | 160 | - [Demo](https://reactions-demo.now.sh/) 161 | 162 | `getStaticProps`를 사용하면 정적 컨텐츠 또한 동적일수 있으므로 동적 콘텐츠에 대한 의존을 멈출 필요가 없다. 증분 정적 재생성을 사용하면 백그라운드에서 기존의 페이지를 다시 렌더링하여 페이지를 업데이트 할 수 있다. 163 | 164 | [Stale-While-Revalidate](https://tools.ietf.org/html/rfc5861) 헤더를 사용해 서비스 중단 없이 백그라운드에서 재생성하여 완료된 이후 배포한다.([SWR 설명 더 보기](https://vercel.com/docs/edge-network/caching#stale-while-revalidate)) 165 | 166 | ```javascript 167 | function Blog({ posts }) { 168 | return ( 169 | 174 | ) 175 | } 176 | 177 | export async function getStaticProps() { 178 | const res = await fetch('https://.../posts') 179 | const posts = await res.json() 180 | 181 | return { 182 | props: { 183 | posts, 184 | }, 185 | // 요청이 들어오면 매초마다 Next.js 가 페이지를 다시 생성하려고 할 것이다. 186 | revalidate: 1, // 기준: 초 187 | } 188 | } 189 | 190 | export default Blog 191 | ``` 192 | 193 | 위와 같이 코드를 작성했다면 해당 게시물은 초당 한 번씩 재검증 된다. 만약 새로운 게시물을 추가한다면 ***앱을 다시 만들거나 배포를 다시 하지 않아도 즉시 사용*** 할 수 있다. 194 | 195 | 이는 `fallback:true` 와 완벽하게 통한다. 항상 최신의 게시물을 가질 수 있게 되었으므로 많은 게시물을 추가하거나 자주 업데이트 하든 상관 없이 정적 페이지를 만들 수 있게 되었다.(다이나믹 페이지로 페이지가 생성될 때마다 최신의 데이터를 사용할 수 있다는 뜻) 196 | 197 | ## Static content at scale 198 | 199 | 기존의 전통적인 SSR과 달리 증분 정적 재생성을 사용하면 정적 이점을 그대로 사용할 수 있다. 200 | 201 | - latency 에 스파크가 일어나지 않는다 202 | - 렌더링에 따른 순단 현상이 없으므로 페이지가 지속적으로 빠르게 제공된다. 203 | - 절대 오프라인 상태로 되지 않는다. 204 | - 백그라운드에서 페이지 재생성이 실패해도 이전 페이지는 그대로 유지되고 있기 때문 205 | - 백엔드 및 데이터베이스 콜이 낮다. 206 | - 페이지들은 최대 한 번에 동시에 재계산된다. 207 | 208 | [Incremental Static Regeneration 초기 모델과 의견들 더 읽어보기](https://github.com/vercel/next.js/discussions/11552) 209 | 210 | ## getStaticProps의 기술적 세부 사항 211 | 212 | ### 빌드시에만 실행됨 213 | 214 | `getStaticProps`는 빌드 시간에 실행되기 때문에 query 파라미터나 HTTP headers 등 유저의 요청시에만 사용할 수 있는 값은 받을수 없다. 215 | 216 | ### 서버사이드 코드를 바로 작성 217 | 218 | `getStaticProps`는 오직 서버사이드에서만 실행된다. 이는 절대 클라이언트 사이드에서 실행되지 않음을 뜻한다.(심지어 브라우저를 위한 JS 번들 파일에도 포함되지 않는다) 이것은 DB 쿼리 같은 민감 코드를 바로 작성해도 안전하다는 뜻이다. 219 | 220 | `getStaticProps`에서 API 라우트(`pages/api`)를 `fetch` 로 사용할 수 없다. 그대신 [위](#example-api-route-for-getstaticprops)에서 설명한 것처럼 함수로 바로 사용하라. 221 | 222 | ### HTML 과 JSON 을 모두 정적으로 생성 223 | 224 | 빌드시 `getStaticProps`가 포함된 페이지가 사전 렌더링 될 경우 페이지 HTML 외에 `getStaticProps`를 실행한 결과를 포함한 `fileName.json` 파일을 생성한다. 225 | 226 | 이 JSON 파일은 `next/link` 혹은 `next/router` 를 통한 **클라이언트 사이드 라우팅**에서 사용된다. 227 | 228 | `getStaticProps`를 사용하여 미리 랜더링된 페이지에 라우팅 접근을 하게 될 경우 Next.js 는 이 JSON 파일을(`next/link or router` 가 사용되는 페이지에 미리 생성되어 있음) 사용해 페이지를 그린다. 229 | 230 | ![Link json](../../assets/link-json.jpg) 231 | 232 | 만약 `next/link` or `next/router` 를 사용해 Static Generation 페이지에 접근할 경우, 해당 페이지에서 위의 스샷과 같이 JSON 파일을 가지고 있게 된다. 233 | 234 | ![Link json use](../../assets/link-json-use.jpg) 235 | 236 | 따라서 해당 Static Generation 페이지에 접근할 경우 바로 JSON 파일을 사용해 데이터를 보여줄 수 있다. 237 | 238 | ### page 에서만 사용할 수 있음 239 | 240 | `getStaticProps` 는 컴포넌트에서 사용할 수 없다. 오직 `pages` 밑의 page 들만 사용 가능하다. 241 | 242 | 그 이유는 리액트가 그려지기 이전에 페이지가 모든 데이터를 들고 있어야 하기 때문이다. 243 | 244 | 또한 `export async function getStaticProps() { ... }` 혹은 `export const getStaticProps = async () => { ... }` 의 형태로 사용해야 한다. 만약 페이지 컴포넌트의 `property` 로 `getStaticProps`를 추가하면 정상적으로 동작하지 않을 것이다. 245 | 246 | ### Development 환경에선 모든 요청에 getStaticProps 가 실행된다. 247 | 248 | `next dev` 실행 혹은 커스텀 서버에서 `next({ dev: true })` 일 경우 모든 요청마다 `getStaticProps`가 실행된다. 249 | 250 | 실제 환경을 테스트 해보고 싶다면 `next build && next start(or node custom-server.js)` 를 해주어야 한다. 251 | 252 | ### Preview Mode 253 | 254 | 경우에 따라 일시적으로 정적 생성을 우회하여 빌드 대신 요청마다 페이지를 렌더링 할 수도 있다. 255 | 256 | 예를 들자면 Headless CMS 를 사용할 때 작업물을 발행하기 전 미리보기를 원하는 경우가 있을 수 있다. 257 | 258 | 이때 [Preview Mode](#example-preview-mode) 를 사용하여 처리해 줄 수 있다. 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/environmentVariables.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/fastRefresh.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/pages.md: -------------------------------------------------------------------------------- 1 | # Page 2 | 3 | ![cover](./../assets/pages.jpg) 4 | 5 | Pages 아래의 `.js`, `.jsx`, `.ts`, `.tsx` 파일들은 파일 이름에 따른 URL 경로와 매핑된다. 6 | 7 | ```javascript 8 | // pages/about.js 9 | function About() { 10 | return
About
11 | } 12 | export default About 13 | ``` 14 | 15 | 위와 같이 `pages` 밑에 `about.js` 파일이 존재한다면 `${domain}:${port}/about` 으로 접근시 해당 컴포넌트를 렌더링 하게 된다. 16 | 17 | ## 사전 렌더링 18 | 19 | 기본적으로 Next.js 는 모든 페이지를 **미리 렌더링** 한다. 즉 기존의 React(CSR) 방식과 달리 각 페이지에 대해 미리 HTML 을 생성해 내려준다. 20 | 21 | 이는 더 나은 성능(리액트가 페이지에 그리는 시간을 줄임)과 SEO를 가져올 수 있다. 22 | 23 | 생성 된 각 HTML 은 해당 페이지에 필요한 최소한의 JavaScript 코드와 연결된다. 24 | 25 | ### 사전 렌더링(Pre-rendering)의 두 가지 형태 26 | 27 | Next.js 에는 `정적 생성(Static Generation)` 및 `서버 사이드 렌더링(Server Side Rendering)` 두 가지 형태의 사전 렌더링이 존재한다. 28 | 29 | 두 형태의 차이는 해당 페이지의 ***HTML 생성 시점***이다. 30 | 31 | - 정적 생성: 빌드시 HTML이 생성되며 각 요청에 재사용된다. 32 | - 서버 사이드 렌더링: 각 요청에 대한 HTML이 생성된다. 33 | 34 | Next.js 는 각 페이지에 사용할 사전 렌더링 양식을 선택할 수 있다. 35 | 36 | 정적 생성을 성능상의 이점(CDN 캐시, 렌더링 시간 절약 등)으로 추천하지만 경우에 따라 서버 사이드 렌더링만 가능할 수 있다. 37 | 38 | ## 정적 생성 39 | 40 | 페이지가 정적 생성을 사용하는 경우 HTML은 **빌드시**(`next build`) 생성된다. 이 HTML 은 각 요청에서 재사용 되며 CDN 에 캐시 될 수 있다. 41 | 42 | ### 데이터가 없는 정적 생성 43 | 44 | ```javascript 45 | // pages/about.js 46 | function About() { 47 | return
About
48 | } 49 | export default About 50 | ``` 51 | 52 | 위의 코드에서 외부 데이터에 의존적인 부분이 없다. 이 경우 빌드시 단일 HTML 파일을 생성한다. 53 | 54 | ### 데이터를 사용한 정적 생성 55 | 56 | 일부 페이지는 사전 렌더링을 위해 외부 데이터를 가져와야 한다. 두 가지 시나리오가 있으며 하나 혹은 둘 다 사용될 수 있다. 57 | 58 | 1. **콘텐츠**가 외부 데이터에 종속적인 경우: `getStaticProps` 59 | 2. **경로**가 외부 데이터에 종속적인 경우: `getStaticPaths`(보통 `getStaticProps`와 함께 씀) 60 | 61 | #### 시나리오 1. 페이지 콘텐츠가 외부 데이터에 종속적인 경우 62 | 63 | ```javascript 64 | // TODO: Need to fetch `posts` (by calling some API endpoint) 65 | // before this page can be pre-rendered. 66 | function Blog({ posts }) { 67 | return ( 68 | 73 | ) 74 | } 75 | 76 | export default Blog 77 | ``` 78 | 79 | `posts` 를 map 으로 돌리고 있기 때문에 사전 렌더링을 하려면 해당 데이터가 필요한 상황이다. 이때 `getStaticProps`를 사용하면 된다. 80 | 81 | ```javascript 82 | function Blog({ posts }) { 83 | // Render posts... 84 | } 85 | 86 | // 빌드 타임에서 실행되는 함수(이후 실행되지 않음) 87 | export async function getStaticProps() { 88 | // 외부 API 를 호출한다. 89 | const res = await fetch('https://.../posts') 90 | const posts = await res.json() 91 | 92 | // 이렇게 데이터를 내려주면 위의 Blog 함수에서 posts 데이터를 빌드 시점에서 사용할 수 있다. 93 | return { 94 | props: { 95 | posts, 96 | }, 97 | } 98 | } 99 | 100 | export default Blog 101 | ``` 102 | 103 | #### 시나리오 2. 페이지 경로가 외부 데이터에 의존적인 경우 104 | 105 | Next.js 를 사용하면 **동적 경로**의 페이지를 만들 수 있다. 106 | 107 | 예: `pages/posts/[id].js` 108 | 109 | 이 경우 `localhost/posts/1`, `localhost/posts/2`, `localhost/posts/3` 모두 가능해진다. 110 | 111 | 이때 만약 `id` 에 따른 데이터가 달라진다고 하면(Post 의 id 마다 디비에서 다른 값을 가져오는 등의 작업) **사전에 `id` 값들을 가져와 페이지를 만들어야 한다**. 112 | 113 | ```javascript 114 | export async function getStaticPaths() { 115 | const res = await fetch('https://.../posts') 116 | const posts = await res.json() 117 | 118 | // Get the paths we want to pre-render based on posts 119 | const paths = posts.map((post) => `/posts/${post.id}`) 120 | 121 | // We'll pre-render only these paths at build time. 122 | // { fallback: false } 를 해주면 paths 외의 것들은 404로 떨어진다. 123 | return { paths, fallback: false } 124 | } 125 | ``` 126 | 127 | 이렇게 사전 path 를 정하고 나면 해당 path 에 맞는 page 에 데이터를 내려줘야 한다. 이 경우 `getStaticProps`와 함께 사용하면 된다. 128 | 129 | ```javascript 130 | function Post({ post }) { 131 | // Render post... 132 | } 133 | 134 | export async function getStaticPaths() { 135 | // 위와 동일 136 | } 137 | 138 | export async function getStaticProps({ params }) { 139 | // params contains the post `id`. 140 | // If the route is like /posts/1, then params.id is 1 141 | const res = await fetch(`https://.../posts/${params.id}`) 142 | const post = await res.json() 143 | 144 | // id 에 맞는 데이터를 내려준다. 145 | return { props: { post } } 146 | } 147 | 148 | export default Post 149 | ``` 150 | 151 | 요약하자면, `getStaticPaths`를 통해 만들어 줘야할 동적인 페이지를 Path 를 만들고 `getStaticProps`로 해당 path 에 맞는 값들을 `props`로 내려준다고 보면 된다. 152 | 153 | 그 결과 각각의 path(`id`)에 맞는 데이터가 Post 컴포넌트에 올바르게 내려(props)간다. 154 | 155 | ### 정적 생성은 언제 사용해야 할까? 156 | 157 | 페이지를 한 번 빌드하고 CDN에서 쭉 제공할 수 있으면 정적 생성을 사용하는게 좋다. 모든 요청마다 서버에서 렌더링을 하면 훨씬 비용이 많이 들게 된다. 158 | 159 | 예시 160 | - 마케팅 페이지 161 | - 블로그 게시물 162 | - 제품 목록 163 | - 도움말 및 문서 164 | 165 | 위의 예시들만 봐도 한번 만들어지고 잘 안바뀌며, 유저에 상관없이 공적으로 보이는 페이지라는 느낌이 온다. 166 | 167 | > 즉, 사용자의 요청에 앞서 페이지를 미리 렌더링 할 수 있는가? 를 고민해 보면 된다.(사용자에 상관없이) 168 | 169 | 만약 페이지가 자주 업데이트 되고 유저마다 보여지는 데이터가 다르다면 정적 생성은 좋은 예시가 아니다. 170 | 171 | 이 경우 기존의 CSR 혹은 SSR 에서 수행하면 된다. 172 | 173 | ## 서버 사이드 렌더링 174 | 175 | 서버 사이드 렌더링은 HTML 이 각 요청마다 렌더링된다. 176 | 177 | 페이지에 대한 렌더링은 `getServerSideProps` 가 담당하며 `getStaticProps`와 동일하지만 시점이 다르다고 이해하면 편하다. 178 | 179 | ```javascript 180 | function Page({ data }) { 181 | // Render data... 182 | } 183 | 184 | // 모든 요청에 대해서 실행되는 함수. 185 | export async function getServerSideProps() { 186 | const res = await fetch(`https://.../data`) 187 | const data = await res.json() 188 | 189 | return { props: { data } } 190 | } 191 | 192 | export default Page 193 | ``` 194 | 195 | `getStaticProps` 는 빌드시 한 번만 실행 되지만 `getServerSideProps`는 모든 요청에 대해서 실행 된다는 점이 매우 큰 차이다 196 | 197 | # 요약 198 | 199 | - 정적 생성(Static Generation): 빌드시 HTML 을 만들고 각 요청에 **재사용** 한다. 사전 렌더링이 필요할 경우 `getStaticProps`, `getStaticPath`와 함께 사용해 페이지 `Path` 및 `Data` 를 구성해 줄 수 있다. CSR 과 함께 사용하여 추가 데이터를 가져올 수도 있다. 200 | - 서버 사이드 렌더링: **각 요청마다 HTML 이 생성**된다. 즉 모든 요청에 `getServerSideProps`가 응답한다. 서버 사이드 렌더링은 정적 생성보다 성능이 안좋기 때문에 블로그 등의 정적 페이지라면 정적 생성을 사용하는게 옳다. -------------------------------------------------------------------------------- /Front-End/Next.js/basicFeatures/staticFile.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/basicStructure.md: -------------------------------------------------------------------------------- 1 | # Next.js 의 기본 구조 2 | 3 | path-description -------------------------------------------------------------------------------- /Front-End/Next.js/buildSystem/autoGenFiles.md: -------------------------------------------------------------------------------- 1 | # Next.js 에서 자동으로 생성해주는 파일들 살펴보기 2 | 3 | Next.js 에서 build 단계에서 생성된 파일들의 존재 이유와 동작 방식을 알아보자. 4 | 5 | - 자동으로 생성되는 스크립트들의 역할 6 | - 빌드 산출물 7 | 8 | > 해당 글은 Next.js v10.0.1 SSR production 을 기준으로 작성 되었습니다. 9 | 10 | 11 | 12 | > next build 13 | 14 | 15 | 16 | > next start 17 | 18 | 알아보고자 하는 것 19 | 20 | 1. `head` 에서 `preload` 로 가져오는 `main, webpack, framework, pages` script 21 | 2. `head` 에서 `prefetch` 로 가져오는 `pages/*` script 22 | 3. `body` 의 `
...
` tag 23 | 4. `body` 의 `__NEXT_DATA__` script 24 | 5. `body` 의 `_buildManifest, _ssgManifest` 25 | 6. `build` 산출물의 `server/pages`, `static/chunks/pages` 차이점 26 | 27 | ### 1. head 에서 preload 로 가져오는 main, webpack, framework, pages 의 존재 이유 28 | 29 | preload 는 브라우저에게 현재 페이지에서 사용될 것이 명확한 리소스들을 지정하고, 빠르게 가져오기 위해 사용된다. 30 | 31 | Next.js 는 pages 단위의 강력한 Code splitting 기능을 지원한다. `main, webpack, framework, pages` 는 각 페이지에서 필요한 기본 뼈대들으로, preload 를 통해 우선적으로 가져올 리소스에 해당한다. 32 | 33 | - main, webpack, framework: 런타입 웹팩과 관련된 청크 파일. 34 | - framework: react 나 react-router-dom 등 Next.js 에서 사용되는 중복되는 프레임워크를 분리한 chunk 파일 35 | - [Code Ref](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/build/webpack-config.ts#L463), [Webpack docs](https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkscachegroups) 36 | - `pages/_app-...`, `pages/link-...`: 컴포넌트에서 사용될 js 파일. 현재 페이지가 link 이므로 link 컴포넌트를 가져온 모습. 37 | 38 | ### 2. head 에서 prefetch 로 가져오는 pages/* 의 존재 이유 39 | 40 | prefetch 는 이후 사용될 것으로 예상되는 리소스를 알려주는 역할. 브라우저가 사용될 리소스를 가져와 캐시에 저장한다. 41 | 42 | Production 모드에서는 라우팅으로 갈 수 있는 페이지들을 Pre-rendering 한다. 43 | 44 | 따라서 현재의 link 페이지에서 클릭으로 이동할 수 있는 잠재적 페이지들을 prefetch 해주어 빠른 페이지 로딩을 노리고 있다. 45 | 46 | ### 3. body 의 `
` 생성 이유 47 | 48 | 49 | 50 | [_document](https://nextjs.org/docs/advanced-features/custom-document) 에서 __next 를 [명시적으로 리턴](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/pages/_document.tsx#L533) 하고 있으므로 next 프로젝트는 __next 를 최상위로 가지게 된다. 따라서 마크업 개발자 분들에게 최상단 __next div 의 존재를 전파하여야 함. 51 | 52 | _document 에서 만들어진 __next 는 이후 [렌더링 작업](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/client/index.tsx#L148)에서 사용 된다. 53 | 54 | > p.s _document 는 [서버 사이드에서만 렌더링](https://nextjs.org/docs/advanced-features/custom-document#caveats) 되므로 클라이언트 로직(ex. eventlistener)이 있으면 안된다. 55 | 56 | ### 4. body 의 NEXT_DATA script 의 생성 이유 57 | 58 | ![image](https://user-images.githubusercontent.com/23524849/108622933-617a3680-747f-11eb-893c-5910587fc7a2.png) 59 | 60 | Next.js 는 클라이언트 사이드와 서버 사이드간 데이터 교환을 하는 객체가 존재하는데, 그것이 `__NEXT_DATA__` 이다. 61 | 62 | SSR 이동시 서버와 클라이언트 사이의 context 를 유지시켜 주기 위해 데이터를 클라이언트에 넘겨줄 필요성이 있다. 이때 `get...Props` 를 사용하여 `__NEXT_DATA__` 객체에 데이터를 담을 수 있다. 63 | 64 | 65 | 66 | 이때, **props 와 pageProps 의 차이가 있다!** `_app` 에서 넘겨준 데이터는 props 에 들어가며 하위 페이지에서 넘겨준 데이터는 pageProps 로 들어가게 된다. 67 | 68 | ### 5. body 의 _buildManifest, _ssgManifest 생성 이유 69 | 70 | 71 | 72 | chunk 단위로 분리된 페이지의 build 시에 맵핑을 어떻게 해줄건지 설명해주는 파일이다. 73 | 74 | ![image](https://user-images.githubusercontent.com/23524849/108623068-3fcd7f00-7480-11eb-8434-e40d699d9e97.png) 75 | 76 | 서버가 가지고 있는 `build-manifest.json` 은 빌드시 생성 되는 [모든 파일에 대한 맵핑](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/build/webpack/plugins/build-manifest-plugin.ts#L218)에 해당한다. 따라서 클라이언트에게 보낼 때는 `_buildManifest.js` 로 분할해 전송하게 된다. 77 | 78 | ### 6. build 산출물의 server/pages 와 static/chunks/pages 의 차이점 79 | 80 | - server 에는 _document 가 있지만 static(client) 에는 _document 가 존재하지 않음. 81 | - 당연하게도 _document 는 클라이언트에서 렌더링되지 않기 때문! 82 | - 각 pages 마다 차이점이 있다기 보다는 SSR 로 페이지를 그려야할 데이터가 server/pages 에 존재하고 이후 클라이언트 사이드에서 렌더링 해야하는 데이터가 static/chunks/pages 에 존재하는 구조. 83 | - 따라서 서버와 클라이언트 모두 각각의 페이지가 있어도 이상하지 않다. 서버 사이드에서 페이지를 렌더링한 이후 클라이언트 사이드에서 페이지를 다시 렌더링 한다고 볼 수 있다. 84 | -------------------------------------------------------------------------------- /Front-End/Next.js/buildSystem/customServer.md: -------------------------------------------------------------------------------- 1 | # 빌드된 Next.js 프로젝트와 커스텀 서버 연결하기 2 | 3 | ```javascript 4 | const express = require('express'); 5 | const next = require('next'); 6 | const app = next({ dev: process.env.NODE_ENV === 'production' }); 7 | const handle = app.getRequestHandler(); 8 | const PORT = 3000; 9 | 10 | app 11 | .prepare() // JUST DEV mode 12 | .then(() => { 13 | const server = express(); 14 | 15 | server.get('*', (req, res) => { 16 | handle(req, res); // Next.js Route HANDLER! 17 | }); 18 | 19 | server.listen(PORT, (err) => { 20 | if (err) throw err; 21 | console.log('Server is running...!', PORT); 22 | }); 23 | }) 24 | .catch((e) => { 25 | console.error(e); 26 | process.exit(1); 27 | }); 28 | ``` 29 | 30 | 위의 코드는 Next.js 의 보편적인 커스텀 서버 예제이다. 31 | 32 | 이 장에서는 해당 코드에서 가장 핵심적인 `app.getRequestHandler()` 함수가 어떻게 동작하는지 알아보고 커스텀 서버가 어떻게 **"자동으로"** 빌드된 Next.js 프로젝트를 찾아가는지 알아보자. 33 | 34 | > 해당 내용은 Next.js v10.0.1 을 기준으로 하고 있다. 35 | 36 | ## 1. getRequestHandler 살펴보기 37 | 38 | ```javascript 39 | const handle = app.getRequestHandler(); 40 | ... 41 | server.get('*', (req, res) => { 42 | handle(req, res); // Next.js Route HANDLER! 43 | }); 44 | ``` 45 | 46 | 알아두기 47 | 48 | 1. 위의 코드에서 커스텀 서버는 모든 라우팅을 Next.js 에 위임하고 있다. 49 | 2. 이때 handle 함수는 getRequestHandler 함수의 리턴 *함수* 이다. 50 | 51 | [getRequestHandler 함수의 구현체](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L449)를 살펴보면 `handleRequest` 함수를 리턴하는 것을 볼 수 있다. 52 | 53 | 해당 함수의 [마지막 부분](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L441)을 보면 `return await this.run(req, res, parsedUrl)` 을 통해 내부적인 로직이 실행([router](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L1032), [render](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L833) 등)되고 렌더링된 혹은 SSR 페이지를 리턴해 주는 것을 알 수 있다. 54 | 55 | 우리가 궁금한 점은 이때 router 함수에서 **어떻게** `.next` 파일을 찾아갈 수 있는지 이다. 56 | 57 | ## 2. Next-server constructor 살펴보기 58 | 59 | ```javascript 60 | const next = require('next'); 61 | const app = next({ dev: process.env.NODE_ENV === 'production' }); 62 | ``` 63 | 64 | 위 질문의 답은 생성자에 있다. 65 | 66 | `require('next')` 로 가져오는 `next-server` 의 [기본값](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L157)이 `.` 으로 이루어져 있으며, `nextConfig` 의 `distDir` [기본 값](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/config.ts#L16)이 `.next` 이기 때문이다. 67 | 68 | 따라서 생성자에서 router 에게 넘겨주는 `this.distDir` 값을 [만들어](https://github.com/vercel/next.js/blob/v10.0.1/packages/next/next-server/server/next-server.ts#L169) 자동으로 Next 빌드 프로젝트를 찾아갈 수 있도록 해준다. 69 | 70 | ## ++ 그렇다면 build path 를 변경하고 싶으면 어떻게 해야할까? 71 | 72 | `next.config.js` 파일에서 `distDir` 값만 수정하면 된다. `next build` 단계에서 자동으로 해당 path 로 빌드된 파일을 밀어주고, Custom server 에서도 next.config.js 파일의 distDir 을 기준으로 바라보게 되기 때문이다. -------------------------------------------------------------------------------- /Front-End/Next.js/customServer/introduce.md: -------------------------------------------------------------------------------- 1 | # CustomServer 2 | 3 | ## 커스텀 서버란? 4 | 5 | 커스텀 서버는 Next 에서 제공해주는 기본 서버가 아닌 독자적인 서버를 통해 SSR 혹은 API 작업을 한다. 6 | 7 | `Express`, `Koa` 와 같은 서버에서 특정 Route Path 를 Next 에 연결하고 싶거나 서버 자체의 스펙을 모두 정의하 싶다면 고려해볼 수 있는 옵션이다. 8 | 9 | [공식 문서](https://nextjs.org/docs/advanced-features/custom-server)의 내용을 보면 Next.js 의 라우터가 앱의 모든 요구사항을 수용할 수 없을 때 사용하길 권장한다. 10 | 11 | 그 이유는 커스텀 서버를 사용하게 될 경우 `Serverless` 및 `Automatic Static Optimization` 같은 기능에 제한이 있기 때문이다. 12 | 13 | 만약 SSR 으로 서버를 운영하고자 한다면 해당되지 않는다. 14 | 15 | ## 커스텀 서버 사용하기 16 | 17 | ```javascript 18 | import { createServer } from 'http'; 19 | import { parse } from 'url'; 20 | import next from 'next'; 21 | 22 | const app = next({ dev: process.env.NODE_ENV !== 'production' }); 23 | const handle = app.getRequestHandler(); 24 | const PORT = 3000; 25 | 26 | createServer((req, res) => { 27 | if (!req.url) return res.end(); 28 | const parsedUrl = parse(req.url, true); 29 | const { pathname, query } = parsedUrl; 30 | 31 | if (pathname === '/a') { 32 | app.render(req, res, '/a', query); 33 | } else { 34 | handle(req, res); 35 | } 36 | }).listen(PORT, () => { 37 | console.log(`> Ready on http://localhost:${PORT}`); 38 | }); 39 | ``` 40 | 41 | [공식 예제](https://github.com/vercel/next.js/tree/canary/examples/custom-server)에서는 `app.prepare()` 를 사용하지만, [실제 코드는 비어있기 때문](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/next-server.ts#L555)에 생략해 주었다. 42 | 43 | 커스텀 서버에 요청이 오게 될 경우 `handle` 함수를 통해 `req`, `res` 객체를 넘겨 Next.js 에서 설정하게 한다.([generateRoutes](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/next-server.ts#L581), [renderToHTML](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/next-server.ts#L1706)) 44 | 45 | ## 유의점 46 | 47 | - 커스텀 서버와 Next 는 따로가기 때문에 `next.config` 의 `babel`, `webpack` 을 거치지 않는다. 48 | - 따라서 노드 버전을 맞춰주어야 한다. 49 | 50 | ## 실행 51 | 52 | ```javascript 53 | "scripts": { 54 | "dev": "node server.js", 55 | "build": "next build", 56 | "start": "NODE_ENV=production node server.js" 57 | } 58 | ``` 59 | 60 | - [JavaScript 예시](https://github.com/vercel/next.js/tree/canary/examples/custom-server) 61 | 62 | ```javascript 63 | "scripts": { 64 | "dev": "nodemon", 65 | "build": "next build && tsc --project tsconfig.server.json", 66 | "start": "cross-env NODE_ENV=production node dist/index.js" 67 | }, 68 | ``` 69 | 70 | - [Typescript 예시](https://github.com/vercel/next.js/tree/canary/examples/custom-server-typescript) 71 | - `nodemon`은 느리므로 `ts-dev` 로 포팅하는 것을 추천한다. 72 | 73 | 74 | -------------------------------------------------------------------------------- /Front-End/Next.js/introduce.md: -------------------------------------------------------------------------------- 1 | # NextJS 가 뭔가요? 2 | 3 | TL;DR! 4 | - Server Side Rendering 5 | - 화면이 깜빡이지 않아요. 6 | - Static Exporting 7 | - CSS-in-JS 8 | - Stylesheet 로부터 자유로워요 9 | - Zero Setup 10 | - 설치 후 바로 컴포넌트만 만들면 돼요. 11 | - Fully Extensible 12 | - Ready for Production 13 | 14 | [공식 문서](https://nextjs.org/docs/getting-started)의 내용이 많이 압축되어 있습니다. 15 | 16 | Next.js 는 공식 홈페이지에서도 언급하듯 **프로덕션 환경을 위한 리액트 프레임워크**이다. 17 | 18 | 즉 여타 다른 프레임워크 처럼 *복잡한 초기 설정* 혹은 *기초적이고 반복적인 작업*을 숨기고 바로 개발에 몰두할 수 있도록 하는 것에 초점을 두었다. 19 | 20 | ## Next.js 의 강점 21 | 22 | ### 1. 직관적이고 간편한 세팅 23 | 24 | [Next.js 의 기본 구조](./basicStructure.md)를 보면 디렉토리가 매우 직관적으로 구성되어 있는 것을 알 수 있으며 *Zero Setup* 이라고 말하는 것 처럼 아무런 환경 설정을 하지 않아도 간단한 페이지는 바로 작업할 수 있다. 25 | 26 | ### 2. Static Generation 과 Server Side Rendering 27 | 28 | ![Static generation](https://nextjs.org/static/images/learn/data-fetching/static-generation.png) 29 | 30 | - Static Generation은 빌드 시간에 HTML을 생성하는 사전 렌더링 방법이다.(이때 필요한 데이터를 사전 요청 함) 31 | - pre-fetching 32 | - serverless 33 | - json-data 34 | 35 | ![Server Side Rendering](https://nextjs.org/static/images/learn/data-fetching/server-side-rendering.png) 36 | 37 | - Server Side Rendering 은 각 요청에 따라 HTML을 생성하는 렌더링 방법이다. 38 | 39 | ### 3. Custom Server 40 | 41 | ```javascript 42 | // server.js 43 | const { createServer } = require('http') 44 | const { parse } = require('url') 45 | const next = require('next') 46 | 47 | const dev = process.env.NODE_ENV !== 'production' 48 | const app = next({ dev }) 49 | const handle = app.getRequestHandler() 50 | 51 | app.prepare().then(() => { 52 | createServer((req, res) => { 53 | // Be sure to pass `true` as the second argument to `url.parse`. 54 | // This tells it to parse the query portion of the URL. 55 | const parsedUrl = parse(req.url, true) 56 | const { pathname, query } = parsedUrl 57 | 58 | if (pathname === '/a') { 59 | app.render(req, res, '/a', query) 60 | } else if (pathname === '/b') { 61 | app.render(req, res, '/b', query) 62 | } else { 63 | handle(req, res, parsedUrl) 64 | } 65 | }).listen(3000, (err) => { 66 | if (err) throw err 67 | console.log('> Ready on http://localhost:3000') 68 | }) 69 | }) 70 | ``` 71 | 72 | Next.js 에 서버를 자유롭게 구성하여 원하는 방식으로 만들어 줄 수 있다.(Koa, Express 등) 73 | 74 | [공식 예제](https://github.com/vercel/next.js/tree/canary/examples/custom-server-express)가 잘 되어 있어 초기 뼈대에 참고하기도 좋다. -------------------------------------------------------------------------------- /Front-End/Next.js/lifeCycle.md: -------------------------------------------------------------------------------- 1 | # Next.js 의 동작 방식 2 | 3 | 4 | 5 | > 이미지 출처: https://twitter.com/tpreusse/status/846773290786078721 6 | -------------------------------------------------------------------------------- /Front-End/Next.js/next-redux-wrapper/README.md: -------------------------------------------------------------------------------- 1 | # Redux Wrapper for Next.js 2 | 3 | [공식 문서](https://github.com/kirill-konshin/next-redux-wrapper) 4 | 5 | `next-redux-wrapper` 모듈의 기본적인 사용법과 동작 방식을 이해한다. `redux-saga` 연동을 메인으로 설명하려고 한다. 6 | 7 | ## 왜 이 라이브러리가 필요한가요? 8 | 9 | 단순한 SPA 의 경우 모든 페이지에서 제공되는 하나의 Redux 스토어를 통해 관리하면 된다. 하지만 Next.js 의 [Static Generation](../basicFeatures/dataFetching/getStaticProps.md) 또는 [Server-Side Rendering](../basicFeatures/dataFetching/getServerSideProps.md)을 사용할 경우 서버에서 스토어 인스턴스가 필요하기 때문에 상황이 복잡해진다.(서버는 스토어가 없으므로 렌더링시 필요한 스토어 데이터가 없다!) 10 | 11 | 이 경우에 `next-redux-wrapper`를 사용해 간단히 처리할 수 있다. 서버 단계에서 자동으로 스토어 인스턴스를 생성하고 초기화를 해준다. 12 | 13 | 뿐만 아니라 `getStaticProps`, `getServerSideProps` 와 함께 `page` 단계에서 사용해 쉽게 사용할 수 있다. 14 | 15 | 이 라이브러리를 통해 Next.js 의 라이프 사이클에 관계 없이 균일하게 인터페이스를 제공해 줄 수 있다. 16 | 17 | ## 간단한 사용법(with Redux-Saga) 18 | 19 | ```javascript 20 | // store.ts 21 | import {createStore, AnyAction} from 'redux'; 22 | import {MakeStore, createWrapper, Context, HYDRATE} from 'next-redux-wrapper'; 23 | 24 | export interface State { 25 | tick: string; 26 | } 27 | 28 | // create your reducer 29 | const reducer = (state: State = {tick: 'init'}, action: AnyAction) => { 30 | switch (action.type) { 31 | case HYDRATE: 32 | // Attention! This will overwrite client state! Real apps should use proper reconciliation. 33 | return {...state, ...action.payload}; 34 | case 'TICK': 35 | return {...state, tick: action.payload}; 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | // create a makeStore function 42 | const makeStore: MakeStore = (context: Context) => createStore(reducer); 43 | 44 | // export an assembled wrapper 45 | export const wrapper = createWrapper(makeStore, {debug: true}); 46 | ``` 47 | 48 | Redux 만 사용한다면 위와 같이 store 를 만들어 주고 원하는 페이지에서 바로 사용하면 된다. 49 | 50 | - [Redux-Wrapper Usage with Redux Saga](https://github.com/kirill-konshin/next-redux-wrapper#usage-with-redux-saga) 51 | - [Next with Saga example](https://github.com/vercel/next.js/tree/canary/examples/with-redux-saga) 52 | 53 | ## 동작 방식 54 | 55 | [HOW IT WORKS](https://github.com/kirill-konshin/next-redux-wrapper#how-it-works) 가 핵심임ㅎㅎㅋ 56 | 57 | - PHASE 1: `getXXXProps` 58 | 1. 서버라우터 타고옴 59 | 2. 각 props 에서 서버사이드 스토어를 생성 60 | 3. 스토어 상태를 초기화 후 반환 61 | - PHASE 2: `SSR` 62 | 1. `HYDRATE` 액션을 통해 Phase1 에서 만든 스토어에 디스패치 63 | 2. 스토어는 `_app` 이나 `page` 컴포넌트의 프로퍼티로 이동 64 | - PHASE 3: `Client` 65 | 1. 스토어 생성 66 | 2. 스토어 디스패치 67 | 68 | ## 코드 단위로 이해하기 69 | 70 | 71 | -------------------------------------------------------------------------------- /Front-End/Next.js/next.config/README.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Front-End/Next.js/routing/apiRoutes.md: -------------------------------------------------------------------------------- 1 | # API Routes 2 | 3 | - https://nextjs.org/docs/api-routes/introduction 4 | 5 | API 라우터는 우리의 Next.js 에 API 를 제공할 수 있도록 해준다. 6 | 7 | 어떤 파일이든 `pages/api` 안에 넣으면 `/api/*` 로 매핑되며 페이지가 아닌 API 엔드페인트로 제공된다. **오직 서버사이드에서만 번들**되기 때문에 클라이언트 사이즈 번들 사이즈에 영향을 주지 않는다. 8 | 9 | 만약 아래와 같은 `pages/api/users.js` API 라우터를 타면 `json` 값이 `200` 코드와 함께 리턴된다. 10 | 11 | ```js 12 | export default function handler(req, res) { 13 | res.status(200).json({ name: '1ilsang' }); 14 | } 15 | ``` 16 | 17 | API 라우터가 작동하기 위해서는 default 함수(request handler 라 불리는)를 export 해야한다. 각각은 아래와 같은 파라미터를 가진다. 18 | 19 | - `req`: [http.incomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) 인스턴스이다. 미들웨어로 적용하고 싶다면 [Api-middlewares](https://nextjs.org/docs/api-routes/api-middlewares)를 살펴보라. 20 | - `res`: [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) 인스턴스이다. [Response helper 함수](https://nextjs.org/docs/api-routes/response-helpers) 를 살펴보면 더 좋다. 21 | 22 | 서로 다른 HTTP 메서드를 API 라우터에서 다루기 위해 `req.method` 를 request handler 안에서 사용할 수 있다. 23 | 24 | ```js 25 | export default function handler(req, res) { 26 | if(req.method === 'POST') { 27 | // POST request 처리 28 | } else { 29 | // 어떤 것이든 다른 HTTP method 30 | } 31 | } 32 | ``` 33 | 34 | ## Use cases 35 | 36 | 새로운 프로젝트인 경우, API 라우터를 사용해 전체 API 를 빌드할 수 있다. 만약 API 가 이미 존재한다면, API 라우터로 API 를 호출할 필요는 없다. API 라우터의 다른 사례는 다음과 같다. 37 | 38 | - 외부 URL 을 마스킹 할 수 있다.(https://domain.com/secret-url -> /api/secret) 39 | - 서버의 환경변수를 사용해 외부 서비스에 안정적으로 접근할 수 있다. 40 | 41 | 42 | ## 주의사항 43 | 44 | - API 라우터는 [CORS 헤더를 지정하지 않는다](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). 이는 기본적으로 오직 `same-origin` 만 있음을 뜻한다. 동작을 수정하고 싶다면 [CORS 미들웨어](https://nextjs.org/docs/api-routes/api-middlewares#connectexpress-middleware-support) 를 request handler 에 래핑하면 된다. 45 | - API 라우터는 `next export` 에서 사용할 수 없다. 46 | 47 | -------------------------------------------------------------------------------- /Front-End/Next.js/routing/shallowRouting.md: -------------------------------------------------------------------------------- 1 | Shallow Routing 2 | --- 3 | 4 | [공식 문서](https://nextjs.org/docs/routing/shallow-routing) 5 | 6 | Shallow Routing 은 [getServerSideProps](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getServerSideProps.md), [getStaticProps](https://github.com/Road-of-CODEr/we-hate-js/blob/master/Front-End/Next.js/basicFeatures/dataFetching/getStaticProps.md), getInitialProps 과 같은 데이터 fetching 메서드를 다시 실행하지 않고 URL 을 바꿔주는 라우팅이다. 7 | 8 | Shallow 라우팅을 사용해 URL 을 업데이트 하면 `pathname`, `query` 값의 상태를 잃지 않고 [Router 객체](https://nextjs.org/docs/api-reference/next/router#router-object)를 통해 받을 수 있게 된다. 9 | 10 | 즉 불필요한 서버 연산을 최소화 하고 필요한 상태 값(pathname, query)을 라우터에 넣어서 전달하게 되는 것이다. 11 | 12 | Shallow 라우팅을 적용하려면 아래와 같이 작성하면 된다. 13 | 14 | ```javascript 15 | import { useEffect } from 'react' 16 | import { useRouter } from 'next/router' 17 | 18 | // Current URL is '/' 19 | function Page() { 20 | const router = useRouter() 21 | 22 | useEffect(() => { 23 | // Always do navigations after the first render 24 | router.push('/?counter=10', undefined, { shallow: true }); 25 | // OR 26 | // router.push( 27 | // { 28 | // pathname: "/users", 29 | // query: { ...values, page: 1 } 30 | // }, 31 | // undefined, 32 | // { shallow: true } 33 | // ); 34 | }, []) 35 | 36 | useEffect(() => { 37 | // The counter changed! 38 | }, [router.query.counter]) 39 | } 40 | 41 | export default Page 42 | ``` 43 | 44 | 최초 렌더링 이후 라우터는 `/?counter=10` 으로 업데이트 될 것이다. 이때 페이지는 새로 렌더링 되지 않으며 상태 라우터만 변경된다.(shallow) 45 | 46 | ```javascript 47 | import { useRouter } from 'next/router' 48 | import Link from 'next/link' 49 | import { format } from 'url' 50 | 51 | let counter = 0 52 | 53 | export async function getServerSideProps() { 54 | counter++ 55 | return { props: { initialPropsCounter: counter } } 56 | } 57 | 58 | export default function Index({ initialPropsCounter }) { 59 | const router = useRouter() 60 | const { pathname, query } = router 61 | 62 | const reload = () => { 63 | router.push(format({ pathname, query })) 64 | } 65 | const incrementCounter = () => { 66 | const currentCounter = query.counter ? parseInt(query.counter) : 0 67 | const href = `/?counter=${currentCounter + 1}` 68 | 69 | router.push(href, href, { shallow: true }) 70 | } 71 | 72 | return ( 73 |
74 |

This is the Home Page

75 | 76 | About 77 | 78 | 79 | 80 |

"getServerSideProps" ran for "{initialPropsCounter}" times.

81 |

Counter: "{query.counter || 0}".

82 |
83 | ) 84 | } 85 | ``` 86 | 87 | 위의 코드로 실행하게 되면 reload 할때마다 URL 쿼리의 counter 가 늘어나지만 `get...Props` 를 타지 않기 때문에 서버의 리소스를 아낄 수 있다는 장점이 있다. 88 | 89 | 또한 [next/link](https://nextjs.org/docs/api-reference/next/link)에서 `shallow` 기능과 동일하다.(link 의 default 는 false 이다) 90 | 91 | -------------------------------------------------------------------------------- /Front-End/Next.js/tips/You don't know Serverside Rendering.md: -------------------------------------------------------------------------------- 1 | # You don't know Serverside Rendering 2 | 3 | Next.js (특히,`page` 부분)를 이용하여 Serverside Rendering의 개념을 다시한번 되짚어 보았다. 4 | 5 | 그리고 더 나아가 Next.js에서 사용하는 `getStaticPaths, getStaticProps, getServerSideProps` 함수들의 차이를 이해해보자! 6 | 7 | ## Pre-rendering 8 | 9 | Next.js는 무조건 pre-rendering을 한다. pre-rendering을 하는 방식은 두가지가 있다. 10 | 11 | 언제 HTML을 생성하느냐(=언제 pre-rendering을 하느냐)에 따라 ***Static Generation, Server-side Rendering*** 두가지로 나눌 수 있다. 12 | 13 | CSR(Client Side Rendering)과 반대되서 비교되는 SSR(Servier Side Rendering)이 곧 Next.js에서 이야기하는 pre-rendering 개념이라고 보면 된다. 14 | 15 | 즉, 흔히 우리가 이야기해왔던 ***서버사이드 렌더링***은 두 가지로 나눌 수 있다는 것이다. 16 | 17 | ### Static Generation 18 | 19 | - build 타임에 HTML이 생성 됨. 20 | - 같은 request에 대해 재사용이 가능한 애들. 21 | - gatsby, jsp, .. 와 같은 애들 22 | 23 | ### Server-side rendering 24 | 25 | - request가 오면 HTML이 생성 됨. 26 | - next.js가 얘의 기능만 있다고 생각하는 경우가 많다.(=ME!!) 27 | 28 | ## Hybrid Next.js app 29 | 30 | Next.js 에서 `Static generation`과 `Server-side rendring` 두 가지를 함께 사용할 수 있다. 👍🏻 31 | 32 | static generation으로 생성된 페이지는 CDN에 의해서 캐싱되므로 이벤트 페이지나, 소개 페이지를 개발시 이용하면 효과적일 것이다. 그렇다면 이것을 제외한 나머지의 경우 request data에 맞게 페이지를 다르게 보여줘야 하는 경우 Server-side renderign을 채택해서 개발하면 된다. 33 | 34 | ## getStaticPaths, getStaticProps, getServerSideProps 비교 35 | 36 | 먼저, 결론부터 이야기하자면 🤔이름에서 알 수 있듯이 37 | 38 | `getStaticPaths`와 `getStaticProps`는 ***Static generation***을 위한 함수이다. 그리고 `getServerSideProps` 은 ***Server-side rendering***을 위한 함수 이다. 39 | 40 | getStaticPaths 함수를 이용해서 미리 정적페이지를 생성 할 수 있고, 41 | 42 | getStaticProps를 통해서 정적 페이지를 생성하기 위해 필요한 data를 fetch 후 props에 매핑해 줄 수 있다. 43 | 44 | getServerSideProps을 통해서는 Server-side rendering시, 페이지에 필요한 데이터를 fetch 후 props에 매핑하는 역할을 함수 내부에 작성해주면 된다. 45 | 46 | 사용자의 요청에 앞서 페이지를 미리 렌더링 할 수 있는가? = 어떤 사용자가 접속해도 똑같은 페이지 인가?를 기준으로 ***Static generation***을 위한 함수를 써야할지, ***Server-side rendering***을 위한 함수를 쓰면 될지 자-알 판단하면 될 것 같다. 47 | -------------------------------------------------------------------------------- /Front-End/README.md: -------------------------------------------------------------------------------- 1 | # JS for Front-End 2 | 3 | 다양한 프론트엔드 기술 및 라이브러리들을 보여주고 설명하는 레포 4 | 5 | ## UI Library 6 | 7 | - [React](React/README.md) 8 | - Vue 9 | - Angular 10 | - Svelte 11 | - CSS-in-JS 12 | 13 | ## Production Framework 14 | 15 | - [Next.js](Next.js/README.md) 16 | - Nuxt.js 17 | - Electron 18 | - React-Native 19 | 20 | ## Browser 21 | 22 | - 크롬은 어떻게 자바스크립트를 이해할까? 23 | - WebAssembly 24 | 25 | ## Modules 26 | 27 | - npm 28 | - babel 29 | - Webpack 30 | - socket.io 31 | - Redux 32 | - [Redux-saga](Redux-saga/README.md) 33 | -------------------------------------------------------------------------------- /Front-End/React/README.md: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | ## 리액트에 대해서 알아보자 4 | 5 | - 리액트가 뭔가요? 6 | - Virtual DOM 7 | 8 | ## 책 정리 9 | 10 | - [Do it! 리액트 프로그래밍](https://github.com/Road-of-CODEr/do-it-react) 11 | -------------------------------------------------------------------------------- /Front-End/Redux-saga/NonBlockingCalls.md: -------------------------------------------------------------------------------- 1 | # Non-blocking calls 2 | 3 | [원본](https://redux-saga.js.org/docs/advanced/NonBlockingCalls.html) 4 | 5 | ```javascript 6 | function* loginFlow() { 7 | while (true) { 8 | yield take('LOGIN') 9 | // ... perform the login logic 10 | yield take('LOGOUT') 11 | // ... perform the logout logic 12 | } 13 | } 14 | ``` 15 | 16 | 위와 같은 `loginFlow` 가 있다고 하자. 인증에 성공하면 토큰을 반환하고 로그아웃하면 토큰을 삭제한다. 17 | 18 | 이전 내용 복습 19 | 1. `take` 는 store 에서 특정 작업을 기다릴 수 있습니다. 20 | 2. `call` effect 를 사용하여 비동기 호출을 할 수 있습니다. 21 | 3. `put` effect 는 작업을 store 에 전달할 수 있습니다. 22 | 23 | ## 첫 번째 시도 24 | 25 | ```javascript 26 | import { take, call, put } from 'redux-saga/effects' 27 | import Api from '...' 28 | 29 | function* authorize(user, password) { 30 | try { 31 | const token = yield call(Api.authorize, user, password) 32 | yield put({type: 'LOGIN_SUCCESS', token}) 33 | return token 34 | } catch(error) { 35 | yield put({type: 'LOGIN_ERROR', error}) 36 | } 37 | } 38 | 39 | function* loginFlow() { 40 | while (true) { 41 | const { user, password } = yield take('LOGIN_REQUEST') 42 | const token = yield call(authorize, user, password) 43 | if (token) { 44 | yield call(Api.storeItem, { token }) 45 | yield take('LOGOUT') 46 | yield call(Api.clearItem, 'token') 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | > 위 코드는 미묘한 문제가 있다. 53 | 54 | `authorize` 는 API 호출을 수행하고 store 에 밀어준다. 55 | 56 | `loginFlow` 은 내부의 전체 흐름을 `while(true)` 로 묶어 처리하고 있다. 57 | 58 | 1. `take` 함수를 통해 로그인 요청이 올때까지 대기하다 그 후 `call` 함수를 통해 인증을 요청한다. 59 | 2. 정상적으로 토큰이 있다면 LOGOUT 요청이 올때까지 기다리고, 요청이 오게 되면 clearItem 을 콜한다. 60 | 61 | 여기서 `call` 은 **Promise 를 반환하는 함수가 아니다**. 62 | 63 | ### 위의 접근 방식에는 여전히 미묘한 문제가 있다. 64 | 65 | ```javascript 66 | function* loginFlow() { 67 | while (true) { 68 | // ... 69 | try { 70 | const token = yield call(authorize, user, password) 71 | // ... 72 | } 73 | // ... 74 | } 75 | } 76 | ``` 77 | 78 | 위의 코드에서 `call` 은 blocking 이기 때문에 문제가 생긴다. Generator 호출이 종료 될 대까지 다른 것을 수행할 수 없다. 79 | 80 | 만약 authorize 호출 단계에서 에러가 발생할 경우 LOGOUT `take` 로직까지 갈 수 없게 된다. 81 | 82 | 따라서 LOGOUT 을 놓치지 않으려면 `call` 이 아닌 `fork` 를 해야한다. 83 | 84 | ```javascript 85 | import { fork, call, take, put } from 'redux-saga/effects' 86 | 87 | function* loginFlow() { 88 | while (true) { 89 | ... 90 | try { 91 | // non-blocking call, what's the returned value here ? 92 | const ?? = yield fork(authorize, user, password) 93 | ... 94 | } 95 | ... 96 | } 97 | } 98 | ``` 99 | 100 | `fork` 작업은 백그라운드에서 시작되기 때문에 결과를 얻을 수 없다! 따라서 `authorize` 의 토큰 저장 로직을 변경해야한다. 101 | 102 | ```javascript 103 | import { fork, call, take, put } from 'redux-saga/effects' 104 | import Api from '...' 105 | 106 | function* authorize(user, password) { 107 | try { 108 | const token = yield call(Api.authorize, user, password) 109 | yield put({type: 'LOGIN_SUCCESS', token}) 110 | yield call(Api.storeItem, {token}) 111 | } catch(error) { 112 | yield put({type: 'LOGIN_ERROR', error}) 113 | } 114 | } 115 | 116 | function* loginFlow() { 117 | while (true) { 118 | const { user, password } = yield take('LOGIN_REQUEST') 119 | yield fork(authorize, user, password) 120 | yield take(['LOGOUT', 'LOGIN_ERROR']) 121 | yield call(Api.clearItem, 'token') 122 | } 123 | } 124 | ``` 125 | 126 | `yield take(['LOGOUT', 'LOGIN_ERROR'])` 작업을 통해 로그인 에러와 로그아웃 작업을 기다리게 된다. 127 | 128 | 하지만 위의 코드에서도 문제점은 있다. LOGIN_REQUEST의 결과 와 관련 없이 LOGOUT 디스패치가 오면 LOGIN_REQUEST 작업을 취소해야한다. 위의 코드에서는 취소할 방법이 없다! 129 | 130 | 그러므로 [Task Object](https://redux-saga.js.org/docs/api/index.html#task)와 [cancel](https://redux-saga.js.org/docs/api/index.html#canceltask) 을 사용한다. 131 | 132 | ```javascript 133 | import { take, put, call, fork, cancel } from 'redux-saga/effects' 134 | 135 | // ... 136 | function* loginFlow() { 137 | while (true) { 138 | const {user, password} = yield take('LOGIN_REQUEST') 139 | // fork return a Task object 140 | const task = yield fork(authorize, user, password) 141 | const action = yield take(['LOGOUT', 'LOGIN_ERROR']) 142 | if (action.type === 'LOGOUT') { 143 | yield cancel(task) 144 | } 145 | yield call(Api.clearItem, 'token') 146 | } 147 | } 148 | ``` 149 | 150 | 만약 LOGOUT 작업이 들어오게 되면 LOGIN_REQUEST 를 `cancel` 하는 모습을 볼 수 있다. 이때 `authorize` 작업이 cancel 을 통해 정상적으로 마무리 되었는지, 마무리 된 이후 작업을 해야한다면 finally 를 사용하면 된다. 151 | 152 | ```javascript 153 | import { take, call, put, cancelled } from 'redux-saga/effects' 154 | import Api from '...' 155 | 156 | function* authorize(user, password) { 157 | try { 158 | const token = yield call(Api.authorize, user, password) 159 | yield put({type: 'LOGIN_SUCCESS', token}) 160 | yield call(Api.storeItem, {token}) 161 | return token 162 | } catch(error) { 163 | yield put({type: 'LOGIN_ERROR', error}) 164 | } finally { 165 | if (yield cancelled()) { 166 | // ... put special cancellation handling code here 167 | } 168 | } 169 | } 170 | ``` 171 | 172 | `cancelled` 작업을 통해 마무리 작업을 해줄 수 있다.(store 에 값을 초기화 한다거나 UI 로직을 위한 값을 넣어주는 등) 173 | 174 | 단순한 API call 작업도 생각외로 고려해야할 점이 많았다. 175 | 176 | 어렵당... 177 | -------------------------------------------------------------------------------- /Front-End/Redux-saga/PullingFutureActions.md: -------------------------------------------------------------------------------- 1 | # Pulling future actions 2 | 3 | > [원문](https://redux-saga.js.org/docs/advanced/FutureActions.html) 4 | 5 | `takeEvery` 이펙트를 사용하게 되면 각각의 액션에 대해 새로운 테스크를 만들어 작업하게 된다. 6 | 7 | 사실, `takeEvery` 는 단지 로우-레벨 끝에 있는 내부적인 헬퍼 함수와 더 강력한 API 함수를 감싼 이펙트에 불과하다. 8 | 9 | 이 장에서 우리는 `take` 에 대해서 알아본다. 이 이펙트는 감시 프로세스의 제어를 가능하게 하며 복잡한 데이터 컨트롤 플로우를 설계할 수 있게 해준다. 10 | 11 | ## A basic logger 12 | 13 | Saga 의 간단한 예시를 살펴보자. 이 사가는 스토어로 디스패치 되는 모든 액션을 watch 하여 콘솔 로그를 보여준다. 14 | 15 | `takeEvery('*')` 를 사용하여 우리는 액션의 타입과는 무관하게 모든 디스패치 액션을 캐치할 수 있게 된다. 16 | 17 | ```javascript 18 | import { select, takeEvery } from 'redux-saga/effects'; 19 | 20 | function* watchAndLog() { 21 | yield takeEvery('*', function* logger(action) { 22 | const state = yield select(); 23 | 24 | console.log('action', action); 25 | console.log('state after', state); 26 | }) 27 | } 28 | ``` 29 | 30 | 이제 어떻게 `take` 이펙트를 사용해 동일 로직을 실행 시키는지 확인해보자. 31 | 32 | ```javascript 33 | import { select, take } from 'redux-saga/effects'; 34 | 35 | function* watchAndLog() { 36 | while (true) { 37 | const action = yield take('*'); 38 | const state = yield select(); 39 | 40 | console.log('action', action); 41 | console.log('state after', state); 42 | } 43 | } 44 | ``` 45 | 46 | `take` 는 이전에 본 `call`, `put` 과 비슷하다. 이는 특정한 액션을 기다리기 위해 미들웨어에 알려주는 명령 오프젝트를 생성한다. 47 | 48 | `call` 이펙트는 미들웨어가 Generator 의 Promise `resolve` 를 기다리게 한다. `take` 의 경우 미들웨어가 매칭되는 액션의 제너레이터가 dispatch 될 때까지 기다린다. 위의 예에서, `watchAndLog` 는 모든 액션의 디스패치를 기다리게 된다. 49 | 50 | `while(true)` 문을 어떻게 실행시키는지 확인해보라. 제너레이터 함수라는 것을 잊지말자! 제너레이터 함수는 완료를 목표로 하는 일반 함수(run-to-completion)가 아니다. 우리가 만든 제너레이터는 한 번 박복될 때마다 액션이 일어나길 기다릴 것이다. 51 | 52 | `take` 를 사용하는 것은 우리의 코드에 작은 영향을 준다. `takeEvery` 의 경우, 실행된 테스크가 다시 실행될 때에 대한 관리 방법이 없다. 계속해서 매칭되는 액션을 실행시킬 뿐이다. 또한 언제 감시(observation)를 멈처 주어야 할지도 관리할 수 없다. 53 | 54 | `take` 의 경우 컨트롤의 방향이 정반대이다. 핸들러 태스크에 *pushed* 되고 있는 액션 대신 사가는 스스로 액션들을 *pulling* 한다. 이것은 Saga 가 일반 함수를 콜 하는 것 처럼 보인다. 액션이 dispatch 되었을 때 resolve 하는 것 처럼.(`action = getNextAction()`) 55 | 56 | 이러한 컨트롤 방향의 전환은 특별한 플로우를 수행할 수 있게 한다. 전통적인 액션의 push 접근법을 해결하게 된다. 57 | 58 | 간단한 예로 유저의 액션을 watch 하고 그 결과에 따른 축하 메시지를 띄워보자. 59 | 60 | ```javascript 61 | import { take, put } from 'redux-saga/effects' 62 | 63 | function* watchFirstThreeTodosCreation() { 64 | for (let i = 0; i < 3; i++) { 65 | const action = yield take('TODO_CREATED') 66 | } 67 | yield put({type: 'SHOW_CONGRATULATION'}) 68 | } 69 | ``` 70 | 71 | `while(true)` 대신 3번만 반복하는 for 문을 만들었다. 처음 세 번의 `TODO_CREATED` 액션 후에 `watchFirstThreeTodosCreation` 는 어플리케이션에 축하 메시지를 뛰우도록 요청하고 종료될 것이다. 72 | 73 | 이는 제너레이터가 **가비지 콜렉션**이 되고 **더 이상 불필요한 감시가 사라진다**는 뜻이 된다! 74 | 75 | 이때 중요한 점은 `take` 이펙트를 통해 추적이 가능하다는 점이다. `takeEvery` 로 했다면 실행 횟수를 트래킹 하기 위해 또다른 작업이 필요했을 것이다. 76 | 77 | *pull* 접근의 또 다른 이점은 우리가 친숙한 **동기적(synchronous) 스타일로 컨트롤 풀로우를 표현**할 수 있다는 점이다. 78 | 79 | 만약 `LOGIN` 액션과 `LOGOUT` 액션을 통해 로그인 플로우를 만들어 준다고 해보자. `takeEvery` 를 사용했다면 `LOGIN` 과 `LOGOUT` 두 개의 테스크를 작성해야 했을 것이다. 80 | 81 | 그 경우 제너레이터의 결과물이 두 개로 나뉘어 졌을 것이고 두 개의 핸들러 소스를 읽고 연결해 생각해야한다. 이는 다양한 위치에 있는 코드들을 머리 속으로 재정렬 후 플로우 모델을 이해할 수 있게 된다는 것이다. 82 | 83 | 풀 모델을 사용해 우리는 동일한 액션을 반복해서 핸들링 하지 않고 같은 곳에서 플로우를 작성할 수 있게 된다. 84 | 85 | ```javascript 86 | function* loginFlow() { 87 | while (true) { 88 | yield take('LOGIN') 89 | // ... perform the login logic 90 | yield take('LOGOUT') 91 | // ... perform the logout logic 92 | } 93 | } 94 | ``` 95 | 96 | 굉장히 직관적으로 `LOGIN` 과 `LOGOUT` 의 순서를 알 수 있게 되었다. 97 | 98 | **순서의 보장**을 하게 되므로, 우리는 언제나 `LOGIN` 이후에 `LOGOUT` 이 일어나는 것을 알게 된다. 99 | -------------------------------------------------------------------------------- /Front-End/Redux-saga/README.md: -------------------------------------------------------------------------------- 1 | # Redux-Saga 2 | 3 | 공식 홈페이지: https://redux-saga.js.org/ 4 | 5 | > `redux-saga` 는 side-effect를 관리하기 쉽고 효율적이며 테스트하기 쉽고, 오류를 더 잘 처리하는 것을 목표로 하는 라이브러리다. 6 | 7 | - [Pulling Future Actions](PullingFutureActions.md) 8 | - [Non Blocking Calls](NonBlockingCalls.md) 9 | 10 | 리덕스는 기본적으로 **동기**방식이기 때문에 비동기 처리가 어렵고 복잡해진다. 11 | 12 | 따라서 리덕스사가는 리덕스의 미들웨어로 들어가 액션에서 스토어 변경할 때 비동기 로직을 더 쉽게 처리할 수 있도록 해주는 역할을 한다. 13 | 14 | ## 기본 코드 15 | 16 | ```javascript 17 | import { createStore, applyMiddleware } from 'redux'; 18 | import createSagaMiddleware, { takeLatest, put } from 'redux-saga'; 19 | import helloSaga from './helloSaga'; 20 | 21 | const REQ = 'REQ'; 22 | const HIT = 'HIT'; 23 | const MISS = 'MISS'; 24 | 25 | const reducer = (state = {}, action) => { 26 | const { type, payload } = action; 27 | // REQ 타입이 없다는 것에 유의 28 | switch(type) { 29 | case HIT: 30 | return { 31 | ...state, 32 | loading: true, 33 | hit: payload 34 | } 35 | case MISS: 36 | return {} 37 | } 38 | } 39 | 40 | const sagaMiddleware = createSagaMiddleware(); 41 | const store = createStore( 42 | reducer, 43 | applyMiddleware(sagaMiddleware) 44 | ); 45 | sagaMiddleware.run(helloSaga); 46 | 47 | const action = type => store.dispatch({ type }); 48 | ``` 49 | 50 | 흔히 작성하는 `redux` 코드에서 `createSagaMiddleware` 가 추가된 것을 알 수 있다. 51 | 52 | 이 함수를 호출해 사가 미들웨어를 리덕스에 미들웨어로 연결한다. 53 | 54 | 그 후 `sagaMiddleware.run(helloSaga);` 을 통해 `root` 사가를 실행하면 `helloSaga` 내부에 정의된 액션에 따라 중간에서 처리할 수 있게 된다. 55 | 56 | ```javascript 57 | function* helloSaga() { 58 | // function* 는 제네러이터를 뜻한다. 59 | // 최초 root 호출 시 콘솔이 찍힌다. 60 | console.log('Hello Sagas!'); 61 | // all 으로 가로챌 미들웨어들을 달아준다. 62 | yield all[watchHit(), watch2(), ...], 63 | } 64 | 65 | function* watchHit() { 66 | // takeLatest 는 가장 최근 것만 처리한다. 모든 액션에 대해서 처리하고 싶다면 takeEvery 를 사용하면 된다. 67 | yield takeLatest(REQ, fetchHit); 68 | } 69 | 70 | function* fetchHit() { 71 | try { 72 | // call 은 인자로 받은 함수를 실행시켜준다.(ex. 외부 API 호출) 73 | const fetchData = yield call(api.fetch); 74 | // put 은 액션을 스토어로 디스패치 해주는 역할을 한다. 75 | yield put({ type: HIT, payload: [1, 2, 3] }); 76 | } catch(error) { 77 | yield put({ type: MISS }); 78 | } 79 | } 80 | ``` 81 | 82 | 루트사가에 해당하는 `helloSaga` 에 watch 할 액션들을 달아준다. `takeLatest(REQ, fetchHit)` 은 REQ 타입의 액션이 실행되면 fetchHit 제너레이터를 실행시켜 준다. 83 | 84 | 플로우는 아래와 같다. 85 | 86 | 1. 컴포넌트에서 `REQ` 액션 실행 87 | 2. 사가 `fetchHit` 제너레이터 실행 88 | 3. 정상적으로 실행 되었으면 payload 를 담아 `HIT` 액션 디스패치를 발행 89 | 4. 리듀서에서 `HIT` 타입에 따른 스토어 갱신 90 | 91 | 컴포넌트가 액션을 디스패치(요청)하면 사가는 스토어에 들어오는 액션을 감시하다 자신의 액션이 들어오면 제너레이터를 통한 로직을 처리한다. 그후 데이터를 저장하는 액션을 발행, 리듀서는 이 액션을 받아 스토어를 갱신하게 된다. 92 | 93 | -------------------------------------------------------------------------------- /Front-End/Redux-saga/TakeEffects.md: -------------------------------------------------------------------------------- 1 | # TakeEffects 2 | 3 | `take`, `takeEvery`, `takeLatest`, `takeLeading` 의 차이를 알아보자. 4 | 5 | ## Take 6 | 7 | `take(pattern | channel)` 8 | 9 | `take` 는 `call`, `put` 과 비슷하다. 이는 특정한 액션을 기다리기 위해 미들웨어에 알려주는 명령 오프젝트를 생성한다. 10 | 11 | ```javascript 12 | // take 13 | function* watchAndLog() { 14 | while (true) { 15 | const action = yield take('*'); 16 | const state = yield select(); 17 | 18 | console.log('action', action); 19 | console.log('state after', state); 20 | } 21 | } 22 | 23 | // take[Something] 24 | function* watchAndLog() { 25 | yield takeEvery('*', function* logger(action) { 26 | const state = yield select(); 27 | 28 | console.log('action', action); 29 | console.log('state after', state); 30 | }); 31 | } 32 | ``` 33 | 34 | 들어오는 디스패치 액션 을 가지고 처리해야하는 `take` 와 달리 다른 `take...` effect 들은 콜백으로 바로 처리하는 것을 알 수 있다. 35 | 36 | 따라서 특정 액션에 대한 처리를 바로 해주어야 한다면 `take...` 로 작성하면 조금 더 깔끔하게 코드를 작성해 줄 수 있다. 37 | 38 | [이 글](PullingFutureActions.md)에서 용법의 차이를 느낄 수 있다. 39 | 40 | 만약 Pull 방식의 동기적 접근이 필요하다면 `take` 를 사용하고 액션 단위의 원자적 처리를 목표로 한다면 `take...` 를 사용하면 된다. 41 | 42 | ### takeEvery 43 | 44 | `takeEvery(pattern | channel, saga, ...args)` 45 | 46 | 들어오는 모든 액션(액션이 dispatch 될 때마다)에 새로운 task 를 생성해 처리한다. **모든 액션에 task 를 fork** 하기 때문에 **동시에 호출**될 수 있으며 **실행 순서가 보장 되지 않**는다. 47 | 48 | 그러므로 **task 가 동시에 실행되어도 상관없는 로직에 적합**하다. 49 | 50 | takeEvery 를 비롯한 이후의 `take...` 는 task 를 핸들링 할 수 없다.([ref](PullingFutureActions.md)) 51 | 52 | ### takeLatest 53 | 54 | `takeLatest(pattern | channel, saga, ...args)` 55 | 56 | 진행중이던 액션이 있다면 취소하고 마지막의 액션만 실행(fork)하게 된다. 그러므로 항상 최신의 액션이 처리되는 것을 보장하게 된다. 57 | 58 | 따라서 최신의 데이터/액션이 중요한 경우에 `takeLatest` 를 사용하면 된다. 59 | 60 | ### takeLeading 61 | 62 | `takeLeading(pattern | channel, saga, ...args)` 63 | 64 | **액션이 dispatch 된다면, 해당 액션이 마무리 될 때 까지 동일한 액션을 감지하지 않는다(무시한다)**. 65 | 66 | 이는 이전의 액션이 반드시 먼저 처리됨을 의미한다. 처리 순서가 중요하거나 **중복으로 호출되면 안되는 경우** 사용하는 것이 적절하다. 67 | 68 | 유저가 로그인 버튼을 눌렀다면 로딩을 띄우고 마무리 될때까지 버튼이 눌리고 싶지 않을때 적절한 effect 이다. 69 | 70 | ### Ref 71 | - [API docs](https://redux-saga.js.org/docs/api/) 72 | -------------------------------------------------------------------------------- /Front-End/tips/css-grid.md: -------------------------------------------------------------------------------- 1 | # CSS Grid 알아보기 2 | 3 | ## History of Float, Flex and Grid 4 | 5 | 1. Float-based layout 6 | 7 | > The CSS float property was originally introduced to float an image inside a column of text on the left or right (something you often see in newspapers). Web developers in the early 2000s took advantage of the fact that you could float not just images, but any element, meaning you could create the illusion of rows and columns by floating entire divs of content. But again, floats weren’t designed for this purpose, so creating this illusion was difficult to pull off in a consistent fashion. - from Modern CSS Explained for Dinosaurs 8 | 9 | float은 신문처럼 이미지를 텍스트가 둘러쌀수 있도록 하기 위해서 등장했다. 원래 layout을 목적으로 등장한 것은 아니지만 이를 활용해서 rows, columns 이 있는 것과 같은 효과를 낼 수는 있어서 초기에 많이 사용되었던 방법이다. 하지만 float으로 layout을 설정할때는 clearfix와 같은 부자연스러운 기법을 사용했어야했다(clear: both 설정). 또한 아래 이미지와 같은 형태의 단순한 layout을 배치하는(Holy Grail이라고 불리는 layout)것이 float로 해결하려면 상당히 복잡했고, 이 후 flex가 등장했다. 10 | 11 | from Modern CSS Explained for Dinosaurs 12 | 13 | 2. Flex-based layout 14 | 15 | > The flexbox CSS property was first proposed in 2009, but didn’t get widespread browser adoption until around 2015. Flexbox was designed to define how space is distributed across a single column or row, which makes it a better candidate for defining layout compared to using floats. This meant that after about a decade of using float-based layouts, web developers were finally able to use CSS for layout without the need for the hacks needed with floats. - from Modern CSS Explained for Dinosaurs 16 | 17 | > It’s important to note again that flexbox was designed to space elements within a single column or row — it was not designed for an entire page layout! - from Modern CSS Explained for Dinosaurs 18 | 19 | flex는 한 columns이나 rows를 기준으로 공간을 배치하기 위해서 만들어진 것이다. float보다는 진보한 방식이긴 하나, 여전히 한 row나 colum내에서 배치를 하기 위해서 고안된것이라는 한계가 있다. layout을 설정할 아이템들을 container로 감싸고 `display: flex` 속성을 줘서 내부 아이템들을 flex items로 만들어 layout을 변경할 수 있다. 하지만 flex또한 2차원 공간인 웹화면에서 layout을 구성하기 위해 등장한 것이 아니라는 한계가 있었다. 20 | 21 | - 아래는 flex를 사용할때 참고할 수 있는 자료이다. 22 | - [how to use it](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) 23 | - [how to use it(ko)](https://heropy.blog/2018/11/24/css-flexible-box/) 24 | 25 | 3. **Grid-based layout** 26 | 27 | > CSS grid was first proposed in 2011 (not too long after the flexbox proposal), but took a long time to gain widespread adoption with browsers. As of early 2018, CSS grid is supported by most modern browsers (a huge improvement over even a year or two ago). - from Modern CSS Explained for Dinosaurs 28 | 29 | > Once you get used to the grid syntax, it clearly becomes the ideal way to express layout in CSS. The only real downside to a grid-based layout is browser support, which again has improved tremendously over the past year. It’s hard to overstate the importance of CSS grid as the first real tool in CSS that was actually designed for layout. - from Modern CSS Explained for Dinosaurs 30 | 31 | grid는 정말로 layout을 위한 장치로 2차원으로 layout을 배치할 수 있다. 하지만 이 기법이 flex를 대신하는 것은 아니고, grid와 flex를 적절이 섞어서 사용하는 것이 좋다. [can I use grid](https://caniuse.com/?search=css%20grid) 를 통해서 32 | 33 | - 아래는 grid를 사용할때 참고할 수 있는 자료이다. 34 | - [how to use it](https://css-tricks.com/css-grid-one-layout-multiple-ways/) 35 | - [how to use it(ko)](https://heropy.blog/2019/08/17/css-grid/) 36 | 37 | ## 간단하게 grid 속성 정리 38 | 39 | - display: grid; 40 | 41 | - grid를 적용할 대상에 적용. 이크기에 맞춰서 grid가 생긴다. 그리고 자식요소들은 Grid Items가 된다 42 | - body에 grid 속성을 줄수도 있다. 43 | 44 | - grid-template: 45 | 46 | - grid-template-columns: 47 | - 명시적으로 열의 크기를 정한다. `grid-template-columns: 1열의 크기 2열의 크기 ...` 이다. 48 | - grid-template-rows: 49 | - 명시적으로 행의 크기를 정한다. `grid-template-rows: 1행의 크기 2행의 크기 ...` 50 | 51 | - fr: 사용가능한 공간에 대한 비율 52 | 53 | - repeat: 행/열의 크기 정의를 반복 54 | 55 | - grid-template과 fr, repeat을 같이 사용하는 방법 56 | 57 | - column과 row를 사용가능한 공간에 대한 비율일 1fr단위로 나타내면 아래의 예시를 기준으로 6개의 칼럼과 3개의 행이 생긴다. 58 | 59 | ```css 60 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; 61 | grid-template-rows: 1fr 1fr 1fr; 62 | ``` 63 | 64 | - 반복되는 1fr단위를 repeat을 활용하면 아래와같이 간단해진다. 65 | ```css 66 | grid-template-columns: repeat(6, 1fr); 67 | grid-template-rows: repeat(3, 1fr); 68 | ``` 69 | 70 | - grid-column / grid-row 71 | 72 | - `grid-template-*` 이것들은 grid 적용하는 container에 적용하는 것이고 grid-column과 grid-row는 grid-item에 적용해서 위치를 지정할 수 있다. 73 | 74 | - grid-gap 75 | 76 | - grid-item간의 gap을 지정할 수 있다. 77 | 78 | # 참고한 자료 79 | 80 | - [floats, flexbox, grid - the progression of CSS layouts](https://www.youtube.com/watch?v=R7gqJkdc5dM&ab_channel=KevinPowell) 81 | - [Modern CSS Explained for Dinosaurs](https://medium.com/actualize-network/modern-css-explained-for-dinosaurs-5226febe3525) 82 | - [Why CSS grid-area is the best property for laying out content](https://www.youtube.com/watch?v=duH4DLq5yoo&ab_channel=KevinPowell) 83 | - [CSS Grid 완벽 가이드](https://heropy.blog/2019/08/17/css-grid/) 84 | - [Build a Mosaic Portfolio Layout with CSS Grid](https://www.youtube.com/watch?v=plRcoRqLriw&list=PL4-IK0AVhVjPv5tfS82UF_iQgFp4Bl998&ab_channel=KevinPowell) 85 | -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/frontend-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/frontend-backend.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/microservices.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/monolith-frontback-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/monolith-frontback-microservices.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/monolith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/monolith.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/three-teams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/three-teams.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/verticals-headline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/verticals-headline.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/images/micro-frontend/verticals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/images/micro-frontend/verticals.png -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element-attribute.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element-attribute.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element-attribute.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element-attribute.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/custom-element.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-reflow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-reflow.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-reflow.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-reflow.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-skeleton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-skeleton.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-skeleton.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/data-fetching-skeleton.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/model-store-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/model-store-0.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/model-store-0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/model-store-0.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/server-render.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/server-render.gif -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/assets/video/micro-frontend/server-render.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/Front-End/tips/micro-frontend/assets/video/micro-frontend/server-render.mp4 -------------------------------------------------------------------------------- /Front-End/tips/micro-frontend/readme.md: -------------------------------------------------------------------------------- 1 | # Micro Frontend 2 | - https://micro-frontends.org/ 를 번역한 글 3 | - 해당 레포에 컨트리뷰션은 따로 진행중 4 | 5 | --- 6 | **여러 팀이 각자 독립적으로 기능(features)을 제공**할 수 있게 **모던 웹 앱 구축**을 위한 기술(techniques), 전략(strategies), 레시피(recipes)에 관한 이야기. 7 | ## Micro Frontends란 무엇인가? 8 | **Micro Frontends**라는 용어는 2016년 말에 [ThoughtWorks Technology Radar](https://www.thoughtworks.com/radar/techniques/micro-frontends)에서 처음 등장했다. 그것은 마이크로 서비스의 개념을 프론트엔드 세계로 확장한다. 현재 추세는 기능이 풍부하고 파워풀한 브라우저 애플리케이션, 즉 마이크로 서비스 아키텍처 위에 단일 페이지 앱(SPA)을 구축하는 것이다. 시간이 지남에 따라, 프론트엔드는 여러 분리된 팀에 의해 개발되는 경우가 자주 생기다보니 프론트엔드 계층은 커지고 유지 관리가 더 어려워지고 있다. 이것을 [프론트엔드 모노리스(Frontend Monolith)](https://www.youtube.com/watch?v=pU1gXA0rfwc) 라고 부른다. 9 | 10 | Micro Frontends는 개별 팀들이 소유하고있는 웹사이트나 웹앱을 하나의 기능으로 보고, **그 기능(features)을 구성(composition)** 하자는 방식의 아이디어 이다. 각 팀 별로 팀에서 담당하는 뚜렷한 **비즈니스 영역** 혹은 **미션**이 있을 것이다. 각 팀내 에서는 데이터베이스에서 사용자 인터페이스에 이르기까지 **상호 기능(cross functional)** 을 통해 **end-to-end** 기능을 개발한다. 11 | 12 | 그러나, 이 아이디어는 새롭게 등장한 것이 아니다. [Self-contained Systems](http://scs-architecture.org/) 컨셉과 굉장히 공통점이 많다. 과거에는 이러한 접근 방식을 [수직화(Verticalised) 된 시스템을 위한 Frontend Integration](https://dev.otto.de/2014/07/29/scaling-with-microservices-and-vertical-decomposition/)라는 이름으로 불리기도 했다. 하지만 Micro Frontends는 확실히 더 친근하고 덜 부담스러운 용어이다. 13 | 14 | __Monolithic Frontends__ 15 | ![Monolithic Frontends](./assets/images/micro-frontend/monolith-frontback-microservices.png) 16 | 17 | 18 | __Organisation in Verticals__ 19 | ![End-To-End Teams with Micro Frontends](./assets/images/micro-frontend/verticals-headline.png) 20 | 21 | ## 모던 웹 앱이란 무엇인가? 22 | 23 | 가장 첫 라인에 "모던 웹 앱 구축" 이라는 문구를 사용했다. 이 용어와 관련된 가정을 정의해보자. 24 | 25 | [Aral Balkan](https://ar.al/)은 이것을 더 넓은 시각으로 표현하기 위해 [Documents‐to‐Applications Continuum](https://ar.al/notes/the-documents-to-applications-continuum/) 라는 포스팅을 썼다. 그는 슬라이딩 스케일(sliding scale)이라는 컨셉의 사이트를 고안했는데, 포스팅의 이미지를 살펴보면 **왼쪽**에는 링크로 연결된 **정적(static) 사이트**, **오른쪽**에는 순수 행동 중심(behaviour-centric)의 온라인 사진 편집기와 같은 **컨텐츠가 없는 웹앱**이 스펙트럼이 그려져 있다. 26 | 27 | **이 스펙트럼의 왼쪽**에 당신의 프로젝트를 배치하고 싶은 경우 **웹 서버단에서 통합**이 잘 맞는다. 이 모델을 사용하여 서버는 사용자가 요청한 페이지를 구성하기 위한 모든 컴포넌트에서 **HTML 문자열을 수집하고 연결**한다. 업데이트는 서버에서 페이지를 다시 리로드하거나 Ajax를 통해 페이지 일부를 교체하는 방식으로 수행된다. [Gustaf Nilsson Kotte](https://twitter.com/gustaf_nk/)는 이 주제에 대해 [포괄적인 아티클](https://gustafnk.github.io/microservice-websites/)을 썼다. 28 | 29 | 이 아티클의 초점은 다음과 같다. 신뢰할 수 없는 네트워크 연결에서도 사용자 인터페이스가 **즉각적으로 피드백**을 제공해야 하는 경우 순수 서버 사이드 렌더링 사이트로는 더 이상 충분하지 않다. [Optimistic UI](https://www.smashingmagazine.com/2016/11/true-lies-of-optimistic-user-interfaces/) 또는 [Skeleton Screens](http://www.lukew.com/ff/entry.asp?1797)과 같은 기술을 구현하려면 **device 자체에서** UI를 **업데이트** 할 수 있어야 한다. 구글의 용어인 [Progressive Web Apps](https://developers.google.com/web/progressive-web-apps/)은 앱과 같은 성능을 제공하는 동시에 웹(progressive enhancement)의 우수한 시민이 되는 **균형잡힌 행위**를 적절하게 묘사한다. 이러한 종류의 애플리케이션은 **site-app-continuum의 중간쯤**에 있다. 여기서는 서버 기반 솔루션만으로는 더 이상 충분하지 않고, 우리는 **브라우저로 통합**을 해야 한다. 30 | 31 | ## Micro Frontends의 핵심 아이디어 32 | 33 | * __기술 불가지론자(agnostic)__
각 팀은 다른 팀과 조정하지 않고도 기술 스택을 선택하고 업그레이드할 수 있어야 한다. [Custom Elements](#DOM은-API다)는 구현 세부 정보를 숨기면서 다른 사용자에게 중립적인 인터페이스를 제공하는 좋은 방법이다. 34 | 35 | * __팀 코드 분리__
모든 팀이 동일한 프레임워크를 사용하더라도 런타임은 공유하지 말자. 공유 상태(shared state) 또는 전역 변수에 의존하지 말고 자체 독립 앱을 구축 하자. 36 | 37 | * __팀 접두사 설정__
명명 규칙(naming conventions)을 아직 분리할 수 없다는 것에 동의 한다. 충돌을 피하고 소유권을 명확히 하기 위해 CSS, Events, Local Storage, Cookies에 namespace를 사용하자. 38 | 39 | * __Custom APIs보다 기본 브라우저 기능 선호__
글로벌 PubSub 시스템을 구축하는 대신 [통신을 위한 브라우저 이벤트(Browser Events for communication)](#parent-child-communication--dom-modification)를 사용하자. 만약 당신이 정말로 크로스 팀 API를 구축해야 한다면, 가능한 간단하게 하도록 노력 하자. 40 | 41 | * __탄력있는 사이트 구축__
JavaScript에서 에러가 나거나 아직 실행되지 않은 경우에도 기능(feature) 사용이 가능 해야 한다. 눈에 띄는 성능 향상을 위해 [범용 렌더링(Universal Rendering)](#serverside-rendering--universal-rendering) 과 [점진적 향상(Progressive Enhancement)](https://developer.mozilla.org/ko/docs/Glossary/Progressive_Enhancement)을 사용하자. 42 | --- 43 | 44 | ## DOM은 API다. 45 | 46 | [Custom Elements](https://developers.google.com/web/fundamentals/getting-started/primers/customelements)는 상호운용성 측면에서 브라우저의 통합에 훌륭한 기본 요소 이다. 각 팀은 **선택한 웹 기술**(React, Vue 등등)을 사용하여 컴포넌트를 구축하고 이를 **custom elements에 넣는다.**(예: ``). 특정 element(tag-name, attributes & events)의 DOM specification은 다른 팀에게 계약(contract) 또는 공개 API 역할을 한다. 구현 방법을 알 필요 없이 컴포넌트와 기능을 사용할 수 있다는 것이 장점이다. 그들은 단지 DOM과 상호작용을 할 수 있으면 된다. 47 | 48 | 그러나 Custom Elements 만으로는 모든 요구 사항을 해결할 수 없다. [점진적 향상(Progressive Enhancement)](https://developer.mozilla.org/ko/docs/Glossary/Progressive_Enhancement), [범용 렌더링(Universal Rendering)](#serverside-rendering--universal-rendering) 또는 라우팅 문제를 해결하기 위해서는 추가 소프트웨어가 필요하다. 49 | 50 | 이 페이지는 크게 두 가지 영역으로 나뉜다. 첫번째는 [Page Composition](#page-composition) - 여러 팀이 소유한 컴포넌트로 페이지를 구성하는 방법, 두번째로는 클라이언트단에서 [Page Transition](#page-transition)을 구현하는 예제에 대해 알아봅시다. 51 | 52 | ## Page Composition 53 | 서로 다른 프레임워크로 작성된 **클라이언트**와 **서버**의 통합 외에도 토론되어야 할 많은 사이드 토픽들이 있다. **js 분리(isolate)** 매커니즘, **css 충돌 제거**, 필요한 만큼의 **리소스 로드**, 팀 간의 **공통 리소스 공유 방법**, **데이터 fetching**, 사용자를 위한 좋은 **로딩 상태(states)**. 우리는 이 주제들을 하나씩 다뤄볼 것이다. 54 | 55 | ### 베이스 프로토타입 56 | 57 | 이 모델 트랙터 상점의 제품 페이지는 다음 예를 위한 기초가 될 것이다. 58 | 59 | 세 가지 트랙터 모델을 변경할 수 있는 **상품 변경** 기능을 가지고 있다. 제품 이미지를 변경하면 이름, 가격 및 추천 상품이 업데이트 된다. 또한 선택한 항목을 바구니에 추가하는 **구입 버튼**과 그에 따라 상단에 업데이트되는 **장바구니**가 있다. 60 | 61 | [![Example 0 - Product Page - Plain JS](./assets/video/micro-frontend/model-store-0.gif)](./0-model-store/) 62 | 63 | [try in browser](./0-model-store/) & [inspect the code](https://github.com/neuland/micro-frontends/tree/master/0-model-store) 64 | 65 | 모든 HTML은 클라이언트 단에서 **순수 JavaScript**와 **dependency가 없는** ES6 Template Strings를 이용하여 생성됬다. 이 코드는 간단한 상태(state)와 마크업 분리를 했고, 모든 변경사항에서 클라이언트 쪽 전체 HTML을 다시 렌더링한다. - DOM 간의 비교 하지 않고, **[범용 렌더링(universal rendering)](#serverside-rendering--universal-rendering)은 하지 않는다.** 또한 [코드](https://github.com/neuland/micro-frontends/tree/master/0-model-store)는 **구분 없이** 하나의 js/css 파일에 기록되어 있다. 66 | 67 | 68 | ### Clientside Integration 69 | 70 | 예제에서 이 페이지는 세개의 팀이 별도로 소유한 components/fragments로 분할 된다. 이제 **구매 버튼**과 **장바구니**와 같은 구매 프로세스와 관련된 모든 책임은 **Checkout팀 (파란색)** 이 가지고 있다고 하자. **Inspire팀 (녹색)** 은 이 페이지의 **추천 제품** 을 관리한다. 페이지 자체는 **Product팀 (빨간색)** 이 소유하고 있다. 71 | 72 | [![Example 1 - Product Page - Composition](./assets/images/micro-frontend/three-teams.png)](./1-composition-client-only/) 73 | 74 | [try in browser](./1-composition-client-only/) & [inspect the code](https://github.com/neuland/micro-frontends/tree/master/1-composition-client-only) 75 | 76 | **Product팀**은 어떤 기능이 포함되어 있는지, 그리고 그것이 레이아웃에서 어디에 위치하는게 좋을지 결정한다. 페이지에는 Product팀 자체에서 제공할 수 있는 제품 이름, 이미지 및 선택 가능한 옵션들과 같은 정보가 들어 있다. 그러나 페이지에는 다른 팀의 fragments(Custom Elements)도 포함되어 있다. 77 | 78 | ### Custom Element는 어떻게 만들까? 79 | 80 | **구매 버튼**을 예로 들어 보자. Product팀은 마크업에서 원하는 위치에 `` 버튼을 간단히 추가할 수 있다. 구매 기능이 작동하려면 Checkout팀은 `blue-buy` element를 페이지에 등록해야 한다. 81 | 82 | class BlueBuy extends HTMLElement { 83 | connectedCallback() { 84 | this.innerHTML = ``; 85 | } 86 | 87 | disconnectedCallback() { ... } 88 | } 89 | window.customElements.define('blue-buy', BlueBuy); 90 | 91 | 이제 브라우저가 `blue-buy` 태그를 만날 때마다 `connectedCallback`가 호출된다. `this`는 custom element의 root DOM 노드에 대한 참조다. `innerHTML` 또는 `getAttribute()`와 같이 표준 DOM element의 모든 properties와 methods를 사용할 수 있다. 92 | 93 | ![Custom Element in Action](./assets/video/micro-frontend/custom-element.gif) 94 | 95 | custom element를 명명할 때 스펙에서 요구하는 유일한 사항은 새로 등장하는 HTML 태그와의 호환성을 유지하기 위해 **대시(-)를 포함**해야 한다는 것이다. 다음 예제에서 naming convention으로 [team_color]-[feature]를 사용했다. 팀 네임 스페이스는 충돌에 대비하는 역할을 하고, DOM을 보는 것만으로 어떤 팀이 관리하는 feature 인지 명백히 알 수 있다. 96 | 97 | 98 | ### Parent-Child Communication / DOM Modification 99 | 100 | 사용자가 **상품 변경**에서 다른 트랙터를 선택하면 그에 따라 **구매 버튼을 업데이트**해야 한다. Product팀(red)은 쉽게 DOM에서 기존 element를 **제거**하고 새로운 element를 **추가** 할 수 있다. 101 | 102 | container.innerHTML; 103 | // => ... 104 | container.innerHTML = ''; 105 | 106 | 이전 element의 `disconnectedCallback`는 동기적으로 호출되어 element에서 이벤트 리스너와 같은 것을 정리할 수 있는 기회를 제공한다. 그 다음 새로 생성된 `t_fendt` element의 `connectedCallback`이 호출된다. 107 | 108 | 성능을 더 고려한 방법은 기존 element의 `sku` attribute를 업데이트 하는 것이다. 109 | 110 | document.querySelector('blue-buy').setAttribute('sku', 't_fendt'); 111 | 112 | 만약 Product팀(red)이 React와 같은 DOM diffing을 특징으로하는 템플링 엔진을 사용한다면 자동으로 알고리즘이 수행될 것이다. 113 | 114 | ![Custom Element Attribute Change](./assets/video/micro-frontend/custom-element-attribute.gif) 115 | 116 | 이를 지원하기 위해 custom element는 `attributeChangedCallback`을 구현할 수 있고 callback이 트리거되어야 하는 속성 리스트를 `observedAttributes`으로 지정할 수 있다. 117 | 118 | 119 | const prices = { 120 | t_porsche: '66,00 €', 121 | t_fendt: '54,00 €', 122 | t_eicher: '58,00 €', 123 | }; 124 | 125 | class BlueBuy extends HTMLElement { 126 | static get observedAttributes() { 127 | return ['sku']; 128 | } 129 | connectedCallback() { 130 | this.render(); 131 | } 132 | render() { 133 | const sku = this.getAttribute('sku'); 134 | const price = prices[sku]; 135 | this.innerHTML = ``; 136 | } 137 | attributeChangedCallback(attr, oldValue, newValue) { 138 | this.render(); 139 | } 140 | disconnectedCallback() {...} 141 | } 142 | window.customElements.define('blue-buy', BlueBuy); 143 | 144 | To avoid duplication a `render()` method is introduced which is called from `connectedCallback` and `attributeChangedCallback`. This method collects needed data and innerHTML's the new markup. When deciding to go with a more sophisticated templating engine or framework inside the Custom Element, this is the place where its initialisation code would go. 145 | 146 | `connectedCallback`와 `ChangedCallback` 속성에서 모두 호출되는 중복코드를 없애기 위해 `render()`메서드를 사용 했다. render 메서드는 필요한 데이터와 innerHTML에 변경될 새로운 마크업을 함수안에 들고 있다. 더 정교한 템플릿 엔진이나 custom element 내의 프레임워크와 함께 개발할 때 render 함수에서 초기화 코드가 사용된다. 147 | 148 | ### 브라우저 지원 149 | 150 | 위의 예에서는 현재 [Chrome, Safari 및 Opera에서 지원](http://caniuse.com/#feat=custom-elementsv1)되는 Custom Element V1 Spec을 사용한다. 그러나 [document-register-element](https://github.com/WebReflection/document-register-element)를 사용하면 가볍고 battle-tested polyfill을 모든 브라우저에서 사용할 수 있다. Under the hood, [널리 지원](http://caniuse.com/#feat=mutationobserver)되는 Mutation Observer API를 사용하기 때문에 백그라운드에서 해킹된 DOM 트리가 발견될 경우는 없다. 151 | 152 | ### Framework 호환성 153 | 154 | Custom Elements는 웹 표준이기 때문에 Angular, React, Preact, Vue 또는 Hyperapp와 같은 주요 JavaScript 프레임워크를 모두 지원한다. 그러나 세부 사항을 살펴보면 일부 프레임워크에서는 여전히 몇 가지 구현 문제가 있다. [Rob Dodson](https://twitter.com/rob_dodson)은 [Custom Elements Everywhere](https://custom-elements-everywhere.com/) 에서 해결되지 않은 호환성 테스트 핵심 문제 세트를 모아두었다. 155 | 156 | ### 자식(Child)-부모(Parent) or 형제(Siblings) 간의 Communication / DOM Events 157 | 158 | 그러나 attribute들을 아래로 전달하는 것만으로는 모든 인터렉션을 위해 충분하지 않다. 이 예에서는 사용자가 구매 버튼을 클릭하면 **장바구니는 꼭 refresh 되어야 한다**. 159 | 160 | Checkout팀(blue)이 fragments를 장바구니와 구매버튼 두 개 모두 소유하고 있으므로 구매 버튼을 클릭 했을 때, 장바구니가 알 수 있게 내부 JavaScript API를 구축할 수 있다. 그러나 이렇게 하려면 component 인스턴스가 서로 알고 있어야 하며 격리 위반(isolation violation)이 될 수도 있다. 161 | 162 | 더 깨끗한 방법은 PubSub 메커니즘을 사용하는 것이다. component는 메시지를 publish 할 수 있고 다른 컴포넌트 들은 특정 메세지를 subscribe 할 수 있다. 다행히도 브라우저들은 이 기능(feature)이 내장되어 있다. `click`, `select`, `mouseover`와 같은 브라우저 이벤트들이 바로 이런 방식으로 작동한다. 네이티브 이벤트 외에도, `new CustomEvent(...)`와 같이 더 높은 수준(level)의 이벤트를 만들 수도 있다. 이벤트는 항상 created/dispatched 된 DOM 노드에 묶여 있다. 대부분의 네이티브 이벤트는 버블링을 특징으로 가지고 있다. 이것은 DOM의 특정 하위트리(sub-tree)에서 모든 이벤트를 listen 할 수 있게 한다. 페이지의 모든 이벤트를 듣고 싶은 경우 window element에 이벤트 리스너를 연결하면 된다. 아래는 `blue:basket:changed` 이벤트가 어떻게 생겼는지 보여주는 예제이다. 163 | 164 | // 구매 버튼 165 | class BlueBuy extends HTMLElement { 166 | [...] 167 | connectedCallback() { 168 | [...] 169 | this.render(); 170 | this.firstChild.addEventListener('click', this.addToCart); 171 | } 172 | addToCart() { 173 | // maybe talk to an api 174 | this.dispatchEvent(new CustomEvent('blue:basket:changed', { 175 | bubbles: true, 176 | })); 177 | } 178 | render() { 179 | this.innerHTML = ``; 180 | } 181 | disconnectedCallback() { 182 | this.firstChild.removeEventListener('click', this.addToCart); 183 | } 184 | } 185 | 186 | 이제 장바구니는 `window`에서 이 이벤트를 subscribe하고 그것의 데이터를 새로고침 해야 할 때 알림을 받을 수 있다. 187 | 188 | // 장바구니 189 | class BlueBasket extends HTMLElement { 190 | connectedCallback() { 191 | [...] 192 | window.addEventListener('blue:basket:changed', this.refresh); 193 | } 194 | refresh() { 195 | // fetch new data and render it 196 | } 197 | disconnectedCallback() { 198 | window.removeEventListener('blue:basket:changed', this.refresh); 199 | } 200 | } 201 | 202 | 이러한 방법으로 장바구니는 `자신의 스코프 밖(window)`에 있는 DOM element에 리스너를 추가한다. 이 방법은 많은 애플리케이션에게 괜찮아야 하지만, 만약 이 방법이 불편할 경우 페이지 자체(Product 팀, red)가 이벤트를 listen하고 DOM element의 `refresh()`를 호출하므로써 장바구니에게 알림을 주는 방식으로 구현할 수도 있다. 203 | 204 | 205 | // page.js 206 | const $ = document.getElementsByTagName; 207 | 208 | $('blue-buy')[0].addEventListener('blue:basket:changed', function() { 209 | $('blue-basket')[0].refresh(); 210 | }); 211 | 212 | DOM 메소드를 호출하는 것은 매우 드물지만, [video element api](https://developer.mozilla.org/de/docs/Web/HTML/Using_HTML5_audio_and_video#Controlling_media_playback)에서 예시를 볼 수 있다. 가능하면 선언적(declarative) 접근법인 속성 변경하는 방식의 사용을 선호해야 한다. 213 | ## Serverside Rendering / Universal Rendering 214 | 215 | Custom Elements는 브라우저 내의 컴포넌트를 통합하는 데 유용하다. 그러나 웹에서 접속 가능한 사이트를 구축할 때 초기 load performance가 문제될 가능성이 있고 모든 js 프레임워크가 다운로드되고 실행될 때까지 사용자는 흰색 화면을 볼 수 있다. 또한 JavaScript가 실패하거나 차단될 경우 사이트에 어떤 일이 발생할지 생각해 보는 것이 좋다. [Jeremy Keith](https://adactio.com/)는 그의 ebook/팟캐스트에서 [탄력있는 웹 디자인(Resilient Web Design)](https://resilientwebdesign.com/)에 대해 중요성을 설명한다. 따라서 서버에서 중요 컨텐츠(core content)를 렌더할 수 있는 능력이 중요하다. 안타깝게도 웹 컴포넌트 spec은 서버 렌더링에 대해 전혀 언급하지 않는다. JavaScript에도 없고 custom Eeements에도 언급은 없다. :( 216 | 217 | ### Custom Elements + Server Side Includes = ❤️ 사랑입니다 218 | 219 | 서버 렌더링을 가능하게 하기 위해 이전 예제를 리펙토링 했다. 팀별로 express 서버를 가지고 있고 Custom Element의 `render()` 메서드를 url을 통해 접근할 수 있다. 220 | 221 | $ curl http://127.0.0.1:3000/blue-buy?sku=t_porsche 222 | 223 | 224 | Custom Element 태그 이름은 path name 으로 사용된다. 그리고 attribute는 쿼리 파라메터가 된다. 이제 모든 컴포넌트의 콘텐츠를 서버 렌더링하는 방법이 있다. 거의 **Universal Web Component**에 가까운 Custom Elements인 ``의 컴비네이션을 통해 할 수 있다. 225 | 226 | 227 | 228 | 229 | 230 | #include로 시작하는 주석은 대부분의 웹 서버에서 사용할 수 있는 기능인 [Server Side Includes](https://en.wikipedia.org/wiki/Server_Side_Includes)의 일부다. 그렇다. 이것은 예전에 우리 웹사이트에 현재 날짜를 임베드 시키기 위해 사용했던 것과 같은 기술이다. [ESI](https://en.wikipedia.org/wiki/Edge_Side_Includes), [nodesi](https://github.com/Schibsted-Tech-Polska/nodesi), [compoxure](https://github.com/tes/compoxure), [tailor](https://github.com/zalando/tailor)와 같은 몇 가지 대체 기술도 있지만, 우리 프로젝트의 SSI는 간단하고 믿을 수 없을 정도로 안정적인 해결책 임이 증명 되었다. 231 | 232 | `#include` 코멘트는 웹 서버가 브라우저로 완성된 페이지를 보내기 전에 `/blue-buy?sku=t_porsche`의 응답으로 대체되었다. nginx의 구성은 다음과 같다. 233 | 234 | upstream team_blue { 235 | server team_blue:3001; 236 | } 237 | upstream team_green { 238 | server team_green:3002; 239 | } 240 | upstream team_red { 241 | server team_red:3003; 242 | } 243 | 244 | server { 245 | listen 3000; 246 | ssi on; 247 | 248 | location /blue { 249 | proxy_pass http://team_blue; 250 | } 251 | location /green { 252 | proxy_pass http://team_green; 253 | } 254 | location /red { 255 | proxy_pass http://team_red; 256 | } 257 | location / { 258 | proxy_pass http://team_red; 259 | } 260 | } 261 | 262 | `ssi: on;` 지시어(directive)는 SSI 기능을 가능하게 한다. 그리고 모든 팀이 `/blue`로 시작하는 URL로 접속시 정확히 어플리케이션(`team_blue:3001`)으로 라우팅 되게 하기 위해서 `upstream`과 `location` 블럭이 추가 되었다. 또한 `/` route는 home과 product 페이지를 관리하는 red팀에 매핑 된다. 263 | 264 | 이 애니메이션은 **JavaScript가 비활성화(disabled)** 된 트랙터 스토어 사이트를 보여준다. 265 | 266 | [![Serverside Rendering - Disabled JavaScript](./assets/video/micro-frontend/server-render.gif)](./assets/video/micro-frontend/server-render.mp4) 267 | 268 | [inspect the code](https://github.com/neuland/micro-frontends/tree/master/2-composition-universal) 269 | 270 | 271 | 상품 선택 셀렉터는 이제 실제 링크이며 클릭할 때마다 페이지가 다시 로드된다. 오른쪽 터미널은 product 페이지를 제어하는 red 팀으로 페이지 요청이 라우팅되는 과정을 보여 주며, 이후 blue팀과 green 팀의 fragment로 마크업이 보완된다. 272 | 273 | JavaScript를 다시 switching하면 오직 첫 번째 요청에 대한 서버 로그 메시지만 표시된다. 이후의 모든 트랙터 변경은 첫 번째 예제와 마찬가지로 클라이언트 측에서 처리된다. 다음 예시 에서는 product 데이터가 JavaScript와 필요에 따라 REST api를 통해 추출되어 로드된다. 274 | 275 | 이 샘플 코드를 로컬 컴퓨터에서 재생할 수 있다. [Docker Compose](https://docs.docker.com/compose/install/)만 설치하면 된다. 276 | 277 | git clone https://github.com/neuland/micro-frontends.git 278 | cd micro-frontends/2-composition-universal 279 | docker-compose up --build 280 | 281 | Docker는 포트 3000에서 nginx를 시작하고 각 팀에 대해 node.js 이미지를 구축한다. 브라우저에서 [http://127.0.0.1:3000/](http://127.0.0.1:3000/)을 열면 빨간색 트랙터를 볼 수 있다. [Docker Compose](https://docs.docker.com/compose/install/)의 결합(combined) 된 로그는 네트워크에서 무슨 일이 일어나고 있는지 쉽게 알 수 있게 해준다. 슬프게도 출력되는 색상을 제어할 방법이 없으므로 blue 팀이 green팀 으로 강조되어 보일 수 있다는 사실은 이해해줘야 한다. :) 282 | 283 | src 파일은 개별 컨테이너에 매핑되며 코드를 변경하면 노드 어플리케이션이이 다시 시작된다. `nginx.conf`를 변경한 것을 확인하려면 `docker-compose`를 다시 시작해야 한다. 그러니 마음껏 만지작거리고 피드백을 주길 바란다. 284 | 285 | ### Data Fetching & Loading States 286 | 287 | SSI/ESI 접근 방식의 단점은 **가장 느린 fragment가 전체 페이지의 응답 시간을 결정**한다는 것이다. 따라서 fragment의 response가 캐시될 수 있을 때 좋다. 제작 비용이 많이 들고 캐싱하기 어려운 response는 종종 초기 렌더링에서 제외하는 것이 좋다. 이러한 파일은 브라우저에서 비동기식으로 로드할 수 있다. 우리의 예제에서, 개인에 따라 맞춤 추천을 보여주는 `green-recos` 이에 대한 후보이다. 288 | 289 | 가능한 해결책 중 하나는 red 팀이 SSI 포함시키지 않는 것이다. 290 | 291 | **Before** 292 | 293 | 294 | 295 | 296 | 297 | **After** 298 | 299 | 300 | 301 | *중요한 Side-note: Custom Element는 [self-closing이 지원되지 않으므로](https://developers.google.com/web/fundamentals/architecture/building-components/customelements#jsapi), ``로 코드 작성시 제대로 동작하지 않을 수 있다.* 302 | 303 | 304 | Reflow 305 | 306 | 렌더링 작업은 브라우저에서만 수행된다. 하지만, 애니메이션에서 볼 수 있듯이, 이 변화는 이제 페이지의 **상당한 반향(substantial reflow)**을 가져왔다. 추천 영역이 처음에는 비어있다. green 팀의 JavaScript가 로드되고 실행된다. 개인화된 권장 사항을 가져오기 위한 API 호출이 수행된다. 그리고 추천 영역 마크업이 렌더되고 관련 이미지가 요청된다. 그 후, fragment는 더 많은 공간이 필요해 페이지의 레이아웃에 밀어넣는다. 307 | 308 | 이와 같은 성가신 반향(reflow)을 피할 수 있는 다른 방법들이 있다. 페이지를 제어하는 red 팀이 **추천 영역의 컨테이너 높이**를 고정할 수 있다. 반응형 웹 사이트에서는 화면 크기에 따라 사이즈가 달라질 수 있기 때문에 높이를 결정하는 것이 종종 쉽지 않을 수 있다. 그러나 더 중요한 문제는 이런 종류의 팀 간 합의가 red 팀과 green 팀 간의 **타이트한 커플링(tight coupling)을 만든다.** 만약 green팀이 reco element에 추가적인 sub-headline를 도입하려면, 새로운 height에 대해 red팀과 협력해야 한다. 두 팀 모두 레이아웃이 깨지는 것을 피하기위해 변경 사항을 동시에 출시해야 할 것 이다. 309 | 310 | 더 좋은 방법은 skeleton screen이라고 불리는 기술을 사용하는 것이다. red 팀은 마크업에서 녹색-Recos SSI Include를 남긴다. 또한 green 팀은 서버측 렌더링 방법을 변경하여 컨텐츠의 개략적인 버전을 생성한다. skeleton 표시는 실제 콘텐츠의 레이아웃 스타일 일부를 재사용할 수 있다. 이렇게 하면 필요한 공간을 확보할 수 있고 실제 콘텐츠의 채우기가 점프를 유도하지 않는다. 311 | 312 | Skeleton Screen 313 | 314 | Skeleton Screen은 **클라이언트 렌더링에** 매우 유용하다. custom element가 user action으로 인해 DOM에 삽입되면 서버에서 필요한 데이터가 도착할 때까지 **즉시 skeleton을 렌더**할 수 있다. 315 | 316 | *상품 변경 선택자* 같이 **attribute 변경**에서도 새 데이터가 도착할 때까지 skeleton으로 전환할 수 있다. 이렇게 하면 사용자는 fragment에서 어떤 일이 일어나고 있다는 것을 알 수 있다. 그러나 endpoint가 빠르게 응답할 경우 이전 데이터와 새 데이터 사이의 짧은 **skeleton 깜박임**이 성가실 수 있다. 이전 데이터를 보존하거나 똑똑하게 timeout을 317 | 사용하는 것이 도움이 될 수 있다. 따라서 이 기술을 현명하게 사용하고 user 피드백을 얻도록 노력하라. 318 | ## Navigating Between Pages 319 | 320 | __to be continued soon ... (I promise)__ 321 | 322 | watch the [Github Repo](https://github.com/neuland/micro-frontends) to get notified 323 | -------------------------------------------------------------------------------- /Front-End/tips/npm-package.md: -------------------------------------------------------------------------------- 1 | # NPM Package 만들기 2 | 3 | TypeScript 를 사용하는 NPM package를 개발할때 필요한 내용을 간단하게 정리해보았다. 4 | 5 | ## ts-loader 6 | - [docs](https://github.com/TypeStrong/ts-loader) 7 | 8 | webpack에서 typescript 파일을 처리하도록 하기 위해서 ts-loader를 사용할 수 있다. 9 | ts-loader를 사용하면 tsconfig.json을 참고해서 typescript 문법을 검사 하기 때문에 tsconfig.json 파일을 반드시 생성해야한다. 10 | 11 | ```javascript 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.tsx?$/, 16 | use: "ts-loader", 17 | exclude: /node_modules/, 18 | }, 19 | ], 20 | }, 21 | ``` 22 | 23 | node_modules를 제외하고 처리하도록 설정한다. 24 | 25 | ## types .d.ts파일 생성하기 26 | - [docs](https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html) 27 | 28 | .d.ts 파일은 tsconfig 설정을 통해서 생성하도록 할 수 있다. 29 | 30 | - 예시 31 | ```javascript 32 | { 33 | "compilerOptions": { 34 | "outDir": "./lib", 35 | "noImplicitAny": true, 36 | "module": "esnext", 37 | "target": "es5", 38 | "jsx": "react", 39 | "allowJs": true, 40 | "sourceMap": true, 41 | "isolatedModules": true, 42 | "declaration": true, // declaration파일을 생성 43 | "moduleResolution": "node", 44 | "resolveJsonModule": true 45 | } 46 | } 47 | ``` 48 | 49 | 50 | ## css-modules-typescript-loader 51 | - [docs](https://www.npmjs.com/package/css-modules-typescript-loader) 52 | 53 | typescript 프로젝트에서 css 파일을 번들리하기 위해서 css 파일들도 type 정보가 필요하다. 이를 처리해주기 위해서 `css-modules-typescript-loader`를 사용할 수 있다. 54 | 55 | ```javascript 56 | // webpack.config.js 57 | module: { 58 | rules: [ 59 | { 60 | test: /\.tsx?$/, 61 | use: "ts-loader", 62 | exclude: /node_modules/, 63 | }, 64 | { 65 | test: /\.css$/, 66 | use: [ 67 | { loader: "style-loader" }, // to inject the result into the DOM as a style block 68 | { loader: "css-modules-typescript-loader" }, 69 | { loader: "css-loader", options: { modules: true } }, 70 | ], 71 | }, 72 | ], 73 | }, 74 | ``` 75 | 76 | css가 번들되는 순서는 아래와 같다. 77 | 1. ts-loader를 통해서 typescript 파일을 처리 78 | 2. css-modules-typescript-loader를 통해서 css 파일에 대한 .d.ts파일을 생성 79 | 3. css-loader가 JavaScript로 변환해서 번들되도록 함 80 | 81 | ## build output 설정 82 | 83 | - webpack.config.js의 output 설정 84 | ```javascript 85 | output: { 86 | path: path.resolve("lib"), 87 | filename: "filename.js", 88 | libraryTarget: "commonjs2", 89 | }, 90 | ``` 91 | 92 | 번들링된 파일이 lib에 저장되도록 위와 같이 설정할 수 있다. 93 | 94 | 95 | ## package.json 파일 설정 96 | 97 | package.json에서 아래와 같이 설정해서 lib 폴더내에 번들링된 파일을 가르키도록 설정한다. 배포할 때 lib 폴더를 포함하여, main을 통해 번들링된 파일 경로를 지정하고, types를 통해 해당 패키지의 types 정의에 대한 정보를 제공한다. 98 | 99 | ```javascript 100 | { 101 | "name": "package-name", 102 | "version": "0.0.x", 103 | "description": "something something", 104 | "main": "lib/filename.js", 105 | "types": "lib/src/filename.d.ts", 106 | ``` 107 | 108 | -------------------------------------------------------------------------------- /Front-End/tips/web-cache.md: -------------------------------------------------------------------------------- 1 | 여러 문서, 아티클 짬뽕 글입니다.. 잘못된 내용 수정, 보충 환영합니다 2 | 3 | ## **What is a Web** **Cache?** 4 | 5 | 웹 캐시 또는 HTTP 캐시는 웹 페이지, 이미지, 기타 유형의 웹 멀티미디어 등의 웹 문서들을 임시 저장하기 위한 기술이다. 웹 캐시 시스템은 이를 통과하는 문서들의 사본을 저장하며 이후 요청들은 특정 조건을 충족하는 경우 캐시화가 가능하다 (-wiki) 6 | 7 | ## **Why do people use them?** 8 | 9 | - To reduce latency → 시간이 적게 걸린다 10 | - To reduce network traffic → 트래픽에 따라 비용 산정하는 경우 비용 절약할 수 있다 11 | 12 | ## **Kinds of Web Caches** 13 | 14 | 아티클에 따르면 세 가지가 있다고 한다. 15 | 16 | - Browser Caches: 디바이스 메모리를 활용. 17 | - Proxy Caches: client도 origin server도 아닌 shared cache의 일종. 한 사람만 사용하는 것이 아니라 많은 사용자가 사용가능. (?? cdn 이 하나의 예시..??) 18 | - Gateway Caches: ?? 모르겠다 19 | 20 | ⚠️ **근데 캐시 사용하면 outdated, stale 한 컨텐츠 받아올 수도 있지 않나요?** ~~(그건 니가 계획을 잘 못해서..)~~ 잘 계획하면 극복할 수 있는 문제. control 잘 하면 reduce latency, reduce network traffic 할 수 있다! 21 | 22 | 23 | 24 | ## **How to Control Caches?** 25 | 26 | HTTP Headers 를 사용한다. 4가지 방법이 있다. (`브라우저 <---> origin server` 간 요청, 응답에 대한 설명이다.) 27 | 28 | ### **Last-Modified** 29 | 30 | - `Last-modified: Fri, 16 Mar 2007 04:00:25 GMT` 31 | - 브라우저에서 요청 시 **응답 헤더**에 포함 32 | - 최초 요청 이후 (2번째 요청 부터) 브라우저에서 요청 시 **요청 헤더**에 **If-Modified-Since**로 포함. 33 | - **If-Modified-Since** 이후 변경이 있으면 변경된 컨텐츠 응답 34 | - **If-Modified-Since** 이후 변경이 없으면 304 응답 → 디바이스에 캐싱된 데이터 사용 35 | 36 | ### **Etag** 37 | 38 | - `ETag: ead145f` 39 | - 브라우저에서 요청 시 **응답 헤더**에 포함 40 | - 최초 요청 이후 (2번째 요청 부터) 브라우저에서 요청 시 **요청 헤더**에 **If-None-Match**로 포함. 41 | - **If-None-Match** 값이 다르면 변경된 컨텐츠 응답 42 | - **If-None-Match** 값이 같으면 304 응답 → 디바이스에 캐싱된 데이터 사용 43 | - 서버 시간에 오류가 있어서 수정된 경우 등 시간 기반의 **Last-Modified**이 불확실한 경우가 발생할 수 있음 ****→ **Etag**는 **unique identifier**이기 때문에 시간 관련 문제가 발생하지 않음. 44 | - Etag 생성 알고리즘은 서버마다 다름 (명세 참고: [https://tools.ietf.org/html/rfc7232#section-2.3](https://tools.ietf.org/html/rfc7232#section-2.3)) 45 | 46 | ### **Expires** 47 | 48 | - `Expires: Tue, 20 Mar 2007 04:00:25 GMT` 49 | - 브라우저에서 요청 시 **응답 헤더**에 포함. 50 | - 브라우저가 **Expires** 이전에는 서버에 요청하지않고 디바이스에 캐싱된 데이터 사용 51 | 52 | ### **max-age** 53 | 54 | - `Cache-Control: max-age=31536000` 55 | - 브라우저에서 요청 시 **응답 헤더 Cache-control**에 포함 56 | - Expires 보다 간결 (캐싱할 second 표시) 57 | - Expires와 max-age 둘다 헤더에 있는 경우 **max-age**를 따름 ([https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3)) 58 | - max-age 값은 고정인데 남은 기간 어떻게 계산하고 validate 할지말지 결정하는거지? → ([https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3](https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3)) 59 | 60 | ⚠️ **Expires**, **cache-control 설정하지 않았는데도 서버요청하지 않고 메모리 캐시된 파일 사용하는 경우가 있다.. 왜죠?** Heuristic Expiration이 범인. Expires, cache-control 없더라도 **Last-Modified**를 사용해서 유효 기간 추정해버림. ([https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2)) 61 | 62 | ## **More About Cache-Control** 63 | 64 | **cache-control: no-cache** 65 | 66 | - 캐싱함(디바이스에 저장) 67 | - 하지만 무조건 재검증을 위해 origin server로 요청 ( === `max-age=0`) 68 | - 변경 없으면 304 응답 69 | - 변경 있으면 200 컨텐츠 응답 70 | 71 | **cache-control: no-store** 72 | 73 | - 캐싱안함(디바이스에 저장 안함) 74 | 75 | 그외 문서 보기 [https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control](https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control) 76 | 77 | ## 그래서 어떻게 적용하라는거죠...? 78 | 79 | file extension 이나 path 별로 다르게 설정하자. 80 | 81 | **Versioned URLs와 Long-lived caching** 82 | 83 | - versioned URLs를 사용하고, max-age값을 엄청나게 크게 설정 84 | - 해시값포함한파일이름 `style.x234dff.css` + 엄청 큰 max-age `cache-control: max-age=31536000` 85 | 86 | **Unversioned URLs와 Revalidation** 87 | 88 | - index.html 같은 경우 매번 versioned URL을 사용하기 어렵기 때문에 `cache-control: no-cache` 혹은 `cache-control: no-store` + `Etag` / `Last-Modified` 89 | 90 | ## 부록: CDN이 등장한다면...? 91 | 92 | `브라우저 <——> CDN server <——> origin server` 93 | 94 | 위와 같은 모양이 됨. CDN server는 origin server로 요청하는 client이며, 동시에 브라우저에 응답을 주는 server가 된다. CDN 서비스에 따라 정책이 다른데, [aws cloudfront 정책](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist)을 살펴보았다. 95 | 96 | - min TTL, default TTL, max TTL 설정이 가능 97 | - max TTL 은 설정 안하면 31536000s(one year)임. 98 | - default TTL은 설정 안하면 86400s(one day)임. 99 | - min TTL, max TTL과 origin server header의 충돌에 대한 정책 100 | - origin server 응답헤더에 cache-control max-age 있을때 101 | - min TTL = 0 일때: max-age와 max TTL 중 작은걸 택함, 브라우저에 응답은 max-age 전달해줌 102 | - min TTL > 0 일때: 103 | - min TTL < max-age < max TTL: max-age 따름 104 | - max-age < min TTL: min TTL 따름, 브라우저 응답은 max-age 전달 105 | - max-age > max TTL: max TTL 따름, 브라우저 응답은 max-age 전달 106 | - origin server 응답헤더 cache-control max-age 없을때 107 | - min TTL = 0 일때: cloudfront default TTL 을 따름 108 | - min TTL > 0 일때: CloudFront minimum TTL or default TTL 중 큰걸 택함 109 | 110 | 그외에도 아래와 같은 조건일때 설명이 있음. 링크 참고.. 111 | 112 | - origin server 응답헤더 cache-control max-age & s-maxage 있을때 113 | - origin server 응답헤더 Expires 있을때 114 | - origin server 응답헤더 no-cache, no-store, private 있을때.. 115 | 116 | 117 | **ref** 118 | --- 119 | - https://web.dev/http-cache 120 | - https://www.mnot.net/cache_docs/#BROWSER 121 | - https://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching/ 122 | - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist 123 | -------------------------------------------------------------------------------- /JavaScript/DataType.md: -------------------------------------------------------------------------------- 1 | # 데이터 타입 2 | JavaScript가 기본형과 참조형 데이터를 어떻게 처리하는지, 왜 다른지를 알아봅시다. 3 | 4 | ## 데이터 타입의 종류 5 | 1. 기본형 6 | - 숫자(number) 7 | - 문자열(string) 8 | - 불리언(boolean) 9 | - null 10 | - undefined 11 | - (es6 추가) [symbol](https://medium.com/sjk5766/es-6-symbol-%EC%9D%B4%EB%9E%80-48c2ad5b054c) 12 | 13 | 2. 참조형 14 | - 객체(object) 15 | - 배열(array) 16 | - 함수(function) 17 | - 날짜(date) 18 | - 정규표현식(RegExp) 19 | - (es6 추가) Map 20 | - (es6 추가) WeakMap 21 | - (es6 추가) Set 22 | - (es6 추가) WeakSet 23 | 24 | - 기본형은 할당이나 연산시 복제되고 참조형은 참조된다라는 말이 있다. 하지만 알고보면 `모두 복제` 된다. 25 | 26 | - **그렇다면 차이점은?** 27 | - 기본형은 값이 담긴 주소값을 바로 복제 28 | - 기본형은 불변성을 띈다. 새로운 값을 할당하기 위해서 데이터 영역에 새로운 값이 생기고(없는 경우), 변수 영역에 있는 주소값이 변경된다. 29 | 30 | 31 | - 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제 32 | - 아래는 참조형의 값을 변경하는 경우 일어나는 일 33 | - 생성된 객체의 변수 영역에서 가리키는 주소값이 변경된다 34 | 35 | 36 | 37 | ## 데이터 타입에 관한 배경지식 38 | 39 | 1. 메모리와 데이터 40 | - 메모리 용량이 과거보다 월등히 커진 상황에서 나타난 자바스크립트는 상대적으로 메모리 관리에 대해 자유로움 41 | - 숫자의 경우 정수형, 부동소수형 구분없이 64비트/8바이트를 할당 42 | - 모든 데이터는 바이트 단위의 식별자(메모리 주소값, Memory address)를 통해 서로 구분하고 연결 43 | 44 | 2. 식별자와 변수 45 | - 변수(variable): 변할 수 있는 수, 변할 수 있는 무언가(data) 46 | - 식별자(identifier): 어떤 데이터를 식별하는데 사용하는 이름, **변수명** 47 | 48 | ## 변수 선언과 데이터 할당 49 | 1. 변수 선언 50 | ```javascript 51 | let a; 52 | ``` 53 | 위의 선언은 '변할 수 있는 데이터를 만들겠습니다!!! 이 데이터는 a라는 이름으로 식별하겠습니다!' 54 | 55 | 56 | 57 | 이렇게 공간을 하나 차지하게 되는것이 변수 선언 과정 58 | 59 | 2. 데이터 할당 60 | * 데이터 할당할때 일어나는 일 61 | ```javascript 62 | let a; 63 | a = 'abc'; 64 | ``` 65 | 66 | a라는 이름을 가진 주소를 찾아서, 그곳에 문자열 'abc'를 할당하는 것이 **아니다** 67 | 68 | 데이터 저장을 위한 별도의 메모리 공간을 확보하고, 그 주소를 a의 위치에 저장해준다. 69 | 70 | 71 | 72 | 데이터를 할당할때는 아래의 단계가 일어난다. 73 | 74 | 1. 변수 a를 선언하면 a의 공간을 확보한다. (위의 그림에서 주소 1001번) 75 | 2. 1001의 식별자를 a 76 | 3. 'abc'를 넣을 공간을 찾는다. (위의 그림에서 주소 5001) 77 | 4. 식별자 a를 이용해서 이 변수의 위치를 찾는다. 78 | 5. 해당 위치에 'abc'가 저장된 곳의 주소값을 할당한다. 79 | 80 | * 이렇게 하는 이유?(왜 변수 영역에 값을 직접 안넣고 한단계를 더해서 머리터지게 하는가?) 81 | - 자바스크립트는 숫자형 데이터는 8바이트를 확보하지만, 문자열은 규격이 없다. 영어는 1바이트, 한글은 2바이트.. 이런식. 가변적이다. 82 | - 미리 확보한 공간 내에서만 데이터를 넣고 바꾸고 그럴수가 있다면, 크기가 안맞는 데이터가 들어오면 데이터 크기에 맞게 공간을 늘리는 작업이 필요할 것 83 | - 매우 비효율적이다. n개의 데이터가 저장되어있고 0 <= m <= n 인 위치 m의 데이터의 공간을 늘려야하면 대략 n - m개의 데이터를 다 옮겨야한다. (OH NO!!!) 84 | - 변수와 데이터를 별도의 공간에 나누어 저장하면 이 문제를 해결 할 수 있다. 85 | 86 | - 데이터에서 변경이 있을때는 아래처럼 동작한다. 87 | 88 | 89 | 90 | ## 기본형 데이터와 참조형 데이터 91 | 1. 불변값 92 | - 변수와 상수를 구분하는 성질은 **'변경가능성'** 93 | - 변수와 상수를 구분할때? _데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당 할 수 있는가?_ 94 | - 불변성 여부? 데이터 영역의 메모리 95 | 96 | 자바스크립트에서는 5라는 값이 저장되어있는 곳은 가비지 컬렉팅이 되지 않는이상 7이라는 값으로 못바꾼다. 97 | 98 | 즉, [기본형 데이터](#primitive)는 모두 불변값 99 | 100 | 2. 가변값 101 | 기본형 데이터가 모두 불변이라면, 참조형은 어쩐지 모두 가변값일것 같다! 꼭 그렇진 않다. 102 | 103 | 104 | 105 | 기본형 데이터와 차이는 _**객체의 변수(프로퍼티) 영역**_ 이 별도로 존재한다는 것. 106 | 107 | 위의 그림을 봐도 데이터 영역은 여전히 불변이라, 프로퍼티 b에 새로운 값을 넣고 싶을때 데이터 영역에 새로운 공간을 할당 받고, 객체의 변수 영역에서 주소값을 바꾼다. 그래서 슬쩍보면 참조형 데이터는 _불변하지 않은것(가변)_ 으로 보이지만 **데이터 부분은 여전히 불변**이다. 108 | 109 | 3. 변수 복사 비교 110 | - 기본형 데이터의 복사 111 | 112 | 113 | 복사할때 주소값을 복사한다. 114 | 115 | - 참조형 데이터의 복사 116 | 117 | 118 | 복사하면 obj2 = obj 하면 obj가 가지고 있는 주소값이 obj2에 저장된다. 119 | 120 | 121 | ## 불변 객체 122 | 1. 불변 객체를 만드는 법 123 | - 불변 객체는 React, Vue.js, Angular 등의 라이브러리 및 프레임워크 / 함수형 프로그래밍, 디자인 패턴에서 매우 중요한 개념! 124 | 125 | - _**참조형 데이터의 '가변'은 데이터 자체가 아닌 내부 프로퍼티를 변경할때 성립**_ 126 | 127 | - 데이터 자체를 변경하는 경우, 기본형 처럼 기존 데이터는 변하지 않고, 새로운 데이터 할당공간에 값이 생성 후 변수 영역에서 새로운 데이터 공간의 주소값을 가지게 되는 것. 128 | 129 | - 불변 객체가 필요한 경우? 값으로 전달받은 객체를 변경하더라도 원본은 변하지 않아야하는 경우 130 | ex) 정보가 바뀐 시점에 알림을 보내야할 때 등. 131 | 132 | 2. 얕은 복사와 깊은 복사 133 | - 얕은 복사는 바로 아래 단계의 값만 복사하는 방법 134 | - 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법 135 | - 깊은 복사를 하는 방법 136 | - 객체를 JSON 문법의 문자열로 변환했다가 다시 JSON 객체로 변경 137 | - [immutable.js](https://immutable-js.github.io/immutable-js/), baobab.js 138 | 139 | ## undefined와 null 140 | 141 | - 자바스크립트에는 없을을 나타내는 값이 두가지: `undefined` 와 `null` 142 | - undefined 인 세가지 경우 143 | - [1] 값을 대입하지 않은 변수. 즉 데이터 영역의 메모리 주소를 저장하지 않은 식별자에 접근할 때 144 | - [2] 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할때 145 | - [3] return 이 없는 함수의 실행 결과 146 | 147 | - null 은 비어있음을 명시적으로 나타내고 싶은 경우 148 | - typeof null이 object인 버그가 있으므로 주의하기 149 | - 어떤 변수의 값이 null인지 판별하기 위해서는 typeof외에 다른 방법을 사용해야한다. 150 | ```javascript 151 | let n = null; 152 | console.log(typeof n); // 결과가 null 이 아니라 object라고 나온다 153 | console.log(n==undefined); // 결과가 true다 154 | console.log(n==null); // 결과가 true다 155 | 156 | console.log(n===undefined); // 결과가 false다 157 | console.log(n===null); // 결과가 true다 158 | ``` 159 | 160 | 161 | ## 정리 162 | - `변수`는 변경 가능한 데이터가 담길 수 있는 공간 163 | - `식별자`는 그 변수의 이름을 말함 164 | 165 | 166 | ### 출처 167 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 1장 -------------------------------------------------------------------------------- /JavaScript/ExecutionContext.md: -------------------------------------------------------------------------------- 1 | # 실행 컨텍스트(Execution context) 2 | 3 | 실행 컨텍스트(Execution context)란 실행할 코드에 제공활 한경 정보를 모아놓은 객체. 4 | 5 | 이 문서에서는 어떤 실행 컨텍스트가 활성화 될때 해당 컨텍스트 내의 변수들을 위로 끌어올리는(개념적으로 그렇단 말!!) 호이스팅(hoisting), 외부 환경 정보 구성, this 값을 설정하는 등의 동작을 다룸 등 다른 언어에서는 없는 특이한 일들을 다룬다. 6 | 7 | 실행 컨텍스트는 자바스크립트의 핵심 개념으로 클로저를 사용하는 다른 언어에서도 유사한 개념을 사용하기 때문에 이를 잘 이해하면 개발자로서의 실력향상에 많은 도움이 되니 열심히 이해해봅시다!! 8 | 9 | 10 | ## 실행 컨텍스트란? 11 | - 실행 컨텍스트: **실행할 코드에 제공할 환경 정보들을 모아놓은 객체** 12 | - 자바스크립트는 동일한 환경에 있는 코드들을 실행할때 관련 환경 정보들을 싹싹 모아 컨텍스트를 구성하고, 이를 **콜 스택(Call Stack)** 에 쌓아올려, 가장 위의 컨텍스트들을 실행하며 전체 코드의 환경과 순서를 보장한다. 13 | 14 | - 동일한 환경이 구성되는 단위(실행 컨택스트를 구성할 수 있는 방법): 15 | - 전역 공간 16 | - [eval()](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/eval) 함수 -> [코드를 그대로 실행시켜 버리기 때문에 보안에 치명적인 이슈가 발생할 수 있어 사용하지 않음](https://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea) 17 | - **함수 -> 가장 흔한 실행 컨텍스트를 구성하는 방법** 18 | - (es6) 블록 {} 에 의해 생성 19 | 20 | - 실행 컨텍스트와 콜스택 도식 21 | ```javascript 22 | let a = 1; // 1 23 | function outer() { // 2 24 | function inner() { // 3 25 | console.log(a); // 4 26 | let a = 3; // 5 27 | } // 6 28 | inner(); // 7 29 | console.log(a); // 8 30 | } // 9 31 | outer(); // 10 32 | console.log(a); // 11 33 | ``` 34 | 35 | 1. 처음엔 콜스택에 뭐 암것두 없다. 36 | 37 | 38 | 39 | 40 | 2. 처음 자바스크립트 코드를 실행할때 전역 컨텍스트가 콜스택에 담긴다. 일반 실행 컨텍스트와 다른 것은 없다. 41 | `최상단 공간은 별도 실행 명령없이 브라우저가 자동으로 실행하므로 자바스크립트 파일이 열리면 바로 전역컨텍스트가 활성화 되는것으로 이해하기!` 42 | 43 | 44 | 45 | 46 | 3. 코드를 줄줄 읽어보다가 함수가 실행되는 **line 10 에서 자바스크립트 엔진이 outer의 실행컨텍스트**를 구성하고 콜스택에 넣는다. 47 | 48 | 49 | 50 | 이렇게 outer가 콜스택의 최상단에 올라오면 전역 컨텍스트와 관련되서 실행하던 것을 멈추고, outer의 코드를 실행한다. 51 | 52 | 4. outer함수를 실행하다보면 **line 7 에서 inner함수를 만나면 inner함수의 실행 컨텍스트가 콜스택에 담기고**, outer 컨텍스트의 코드가 중단되고, inner를 실행한다. 53 | 54 | 55 | 56 | 57 | 5. inner함수의 실행이 끝나면 inner 실행 컨텍스트가 콜스택에서 제거된다. 그리고 그다음 최상단인 outer 실행 컨텍스트로 돌아가서 마저 실행한다. 58 | 59 | 60 | 61 | 6. outer함수의 실행이 끝나면 outer 실행 컨텍스트가 콜스택에서 제거되고, 콜스택에 전역컨텍스트만 남는다. 62 | 63 | 64 | 65 | 66 | 7. 전역 컨택스트도 마저 실행하고 콜스택에는 아무것도 남지 않게 된다. 67 | 68 | 69 | 70 | 71 | - 실행 컨텍스트에 저장되는 내용? 72 | - 실행 컨텍스트가 쌓일때 자바스크립트 엔진은 해당 컨텍스틀에 관련된 환경 정보를 수집해서 실행 컨텍스트 객체에 저장 73 | - 엔진이 활용하는 내용이라 개발자가 코드를 통해서 건드릴 수는 없다. 74 | - 아래는 실행 컨텍스트 객체에 저장되는 내용이다 75 | 76 | 77 | 78 | 79 | 80 | 81 | ## VariableEnvironment 82 | 83 | VariableEnvironment 의 내용은 LexicalEnvironment와 같지만 snapshot이므로 자바스크립트 엔진은 VariableEnvironment에 처음 정보를 넣고, 이를 복사해서 LexicalEnvironment을 만들고 이것을 주로 활용하게 된다. 84 | 85 | 86 | ## LexicalEnvironment 87 | 88 | - lexical enviroment: 사전적 환경(코어 자바스크립트 책에서 사용하는 의미), 어휘적 환경/정적 환경(사람들이 많이 사용하는 언어) 89 | 90 | 1. environmentRecord와 호이스팅 91 | - environmentRecord에는 해당 실행 컨텍스트와 관련된 코드의 식별자 정보 저장 92 | - 매개변수 식별자, 선언한 함수 자체, 선언된 변수의 식별자 93 | - 컨텍스트를 쭈욱 훑으면서 순서대로 수집 94 | - 코드가 실행되기 전에 변수 정보를 수집하는 과정을 마친다. **그래서 코드 실행전에 자바스크립트 엔진은 해당환경의 모든 변수명을 알게된다!** 95 | - _**이 과정이 마치 자바스크립트 엔진이 변수를 최상단으로 다 끌어올리는 것 같아 hoisting(끌어올리다)로 부른다**_ 96 | 97 | 98 | 2. 스코프, 스코프 체인, outerEnvironmentReference 99 | - scope: 식별자에 대한 유효범위 100 | - 스코프 체인(scope chain): 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것 101 | - LexicalEnvironment의 두번째 수집자료 `outerEnvironmentReference` 를 이용해서 수행 102 | - `outerEnvironmentReference` 는 현재 _**호출된 함수가 선언될 당시**_의 LexicalEnvironment를 참조 103 | - 스코프 체인에서 가장 먼저 발견된 식별자에만 접근가능함 104 | 105 | 106 | 107 | ## this 108 | 109 | - thisBinding에는 this로 저장된 객체, 아무것도 지정이 안되면 전역객체가 저장된다 110 | 111 | ## 정리 112 | 113 | - 실행 컨텍스트는 실행할 코드에 제공할 환경 종보를 모아놓은 객체 114 | - VariableEnvironment와 LexicalEnvironment는 시작할때는 같은 내용인데, 실행하다보면 LexicalEnvironment는 변한다. 115 | - 호이스팅(hoisting) environmentRecord의 수집과정을 추상화 116 | - 스코프: 변수의 유효범위 117 | - 전역변수: 전역컨텍스트의 LexicalEnvironment에 담긴 변수 118 | 119 | ### 출처 120 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 2장 121 | -------------------------------------------------------------------------------- /JavaScript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | - [자바스크립트의 기원](jsHistory.md) 4 | - [유용한 자바스크립트 함수](usefulCode.md) 5 | - 간단한 자바스크립트 예제 6 | - 왜 브라우저마다 자바스크립트의 동작이 다르다는 건가요? 7 | - JS와 V8의 관계 8 | - JS의 메모리 사용법 9 | - JS의 운영체제 사용법 10 | - 이벤트 루프 11 | - 스코프 12 | - 프로토타입 13 | - [호이스팅](hoisting.md) 14 | - [데이터 타입](DataType.md) 15 | - [실행 컨텍스트](ExecutionContext.md) 16 | - [this](this.md) 17 | - [콜백 함수](callback.md) 18 | - [클로저](closure.md) 19 | - [프로토타입](prototype.md) 20 | - 클래스 21 | - [자바스크립트로 알고리즘 풀기](algorithm.md) 22 | -------------------------------------------------------------------------------- /JavaScript/algorithm.md: -------------------------------------------------------------------------------- 1 | # JS 로 알고리즘 풀기 2 | 3 | ## JS 에서 알면 좋은 것들 4 | 5 | ```js 6 | /** 7 | * @param {number[][]} edges 8 | * @return {number} 9 | * https://leetcode.com/contest/weekly-contest-232/problems/find-center-of-star-graph/ 10 | */ 11 | const findCenter = (edges) => { 12 | const hits = edges 13 | .flatMap((e) => e) 14 | .reduce((acc, cur) => { 15 | acc.set(cur, (acc.get(cur) ?? 0) + 1); 16 | return acc; 17 | }, new Map()); 18 | 19 | const [answer, ...rest] = [...hits].reduce( 20 | (acc, cur) => { 21 | const [key, value] = cur; 22 | const maxValue = acc[1]; 23 | if (value > maxValue) acc = cur; 24 | return acc; 25 | }, 26 | ["key", 0] 27 | ); 28 | 29 | return answer; 30 | }; 31 | ``` 32 | 33 | `iterable` 한 객체들을 잘 변환시켜 주는 것이 좋다. 34 | 35 | `flatMap` 으로 2차원 배열을 단순화 해줄 수 있으며 `reduce` 를 통해 loop 횟수를 줄여줄 수 있다. 36 | 37 | `Map` 객체는 `Object.entries({OBJECT})`, `for...of` 등을 활용해 자료형을 쉽게 변환해 줄 수 있다. 38 | 39 | 배열에서 메서드를 사용할 경우 **기존 값을 변경** 하는지 알아두어야 한다. `slice` vs `splice` vs `split` 40 | 41 | ### 정규표현식을 알아두자 42 | 43 | ```js 44 | const regExp = /[A-Z]|[a-z]|[0-9]/g; 45 | 46 | // CASE 1. 문자열에서 만족하지 않는 값을 찾기 47 | `123asdfe(ASDFad`.replace(regExp, ""); // '(' 48 | 49 | // CASE 2. 문자열에서 만족하는 값을 가져오기 50 | `123asdfe(ASDFad`.match(regExp); // ['1', '2', '3', 'a', ..., 'D', 'F', 'a', 'd']; '(' 가 포함되지 않음. 51 | 52 | // CASE 3. 해당 정규표현식이 1회 이상 연속되는 부분을 가져온다 53 | `123asdfe(ASDFad`.match(/[a-z]+/g); // ['asdfe', 'ad'] 54 | 55 | // CASE 4. case 3 의 길이를 제한한다. 56 | `123asdfe(ASDFad`.match(/[a-z]{1,3}/g); // ['ad']; 'asdfe' 가 포함되지 않는다. 1~3 길이만 가져오기 때문. 57 | ``` 58 | 59 | ## 백준 In/Out 하기 60 | 61 | [백준](https://www.acmicpc.net/)에서 자바스크립트로 문제를 푸는 법을 알아보자. 62 | 63 | 백준 문제들은 In/Out 부터 작성해 주어야 하기 때문에 `fs` 모듈로 직접 파일을 읽어 처리하게 된다. 64 | 65 | Node.js 를 통해 백준 서버의 Input 파일을 읽어 `console` 로 출력하면 된다. 66 | 67 | input 68 | 69 | > input 예시 70 | 71 | ```js 72 | const fs = require("fs"); 73 | const INPUT_DIST_PATH = "/dev/stdin"; 74 | 75 | const [nParam, mParam, ...params] = fs 76 | .readFileSync(INPUT_DIST_PATH) 77 | .toString() 78 | .split("\n"); 79 | const n = parseInt(nParam); 80 | const m = parseInt(mParam); 81 | const map = []; 82 | 83 | for (let i = 0; i < n; i++) { 84 | const row = params[i].split(" ").map((e) => parseInt(e)); 85 | map.push(row); 86 | } 87 | 88 | console.log("This is output"); 89 | ``` 90 | -------------------------------------------------------------------------------- /JavaScript/assets/HayoungProto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/HayoungProto.png -------------------------------------------------------------------------------- /JavaScript/assets/PersonPrototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/PersonPrototype.png -------------------------------------------------------------------------------- /JavaScript/assets/callStack1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/callStack1.png -------------------------------------------------------------------------------- /JavaScript/assets/callStack2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/callStack2.png -------------------------------------------------------------------------------- /JavaScript/assets/callStack3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/callStack3.png -------------------------------------------------------------------------------- /JavaScript/assets/callStack4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/callStack4.png -------------------------------------------------------------------------------- /JavaScript/assets/closureOverwrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/closureOverwrite.png -------------------------------------------------------------------------------- /JavaScript/assets/closureOverwriteDisable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/closureOverwriteDisable.png -------------------------------------------------------------------------------- /JavaScript/assets/executionContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/executionContext.png -------------------------------------------------------------------------------- /JavaScript/assets/functor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/functor.jpg -------------------------------------------------------------------------------- /JavaScript/assets/instance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/instance.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress1.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress2.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress3.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress4.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress5.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress6.png -------------------------------------------------------------------------------- /JavaScript/assets/memoryAddress7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/memoryAddress7.png -------------------------------------------------------------------------------- /JavaScript/assets/prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Road-of-CODEr/we-hate-js/a45f4b6c67fc52dd4797532194b7f8e5dea720e0/JavaScript/assets/prototype.png -------------------------------------------------------------------------------- /JavaScript/callback.md: -------------------------------------------------------------------------------- 1 | # 콜백 함수(Callback function) 2 | 콜백 함수란 `다른 코드의 인자로 넘겨주는 함수` 이다. 콜백 함수를 넘겨받은 코드는 적절할때 이 콜백 함수를 실행할 수 있는 **제어권**을 가진다. 3 | 4 | ## 콜백 함수는 함수다 5 | - 어떤 객체의 메서드를 콜밤함수로 전달하면, 함수로 동작해서 **this가 전역객체가 된다**(별다른 설정이 없는 경우) 6 | 1. 메서드를 콜백 함수로 전달했을 때 7 | ```javascript 8 | let obj = { 9 | arr: [1,2,3], 10 | arrFunc: function(v, i){ 11 | console.log(this, v, i); // 1 12 | } 13 | } 14 | obj.arrFunc(1, 2); // 2 15 | [4, 5, 6].forEach(obj.arrFunc); // 3 16 | ``` 17 | - 2번째 줄을 실행하면 this가 obj 객체이므로 1번째 줄의 결과는 `obj, 1, 2` 이다. 18 | - 3번째 줄을 시작하면 arrFunc와 obj 객체의 **연관이 없어지고** 함수 내부에서 this는 전역객체가 된다. 19 | 20 | 21 | 2. 객체의 메서드를 콜백함수로 전달했을때, 해당 객체를 this로 바라볼수 없게 되는 문제 해결방법 22 | - 별도 인자로 this를 받는 경우 해당 인자로 넘겨줌 23 | - bind를 활용해서 바인딩하는 방법 24 | ```javascript 25 | let obj = { 26 | arr: [1,2,3], 27 | arrFunc: function(v, i){ 28 | console.log(this, v, i); // 1 29 | } 30 | } 31 | obj.arrFunc(1, 2); // 2 32 | [4, 5, 6].forEach(obj.arrFunc.bind(obj)); // 3 33 | ``` 34 | - 3번째 줄을 시작하면 arrFunc와 obj 객체에 obj 객체를 다시 바인딩 해준다. 함수 내부에서 this는 obj객체가 된다. 35 | 36 | 37 | ## 콜백 지옥과 비동기 제어 38 | - 콜백 지옥이란? 39 | - 콜백 함수를 익명함수로 전달하는 과정이 계속 반복되다가, 읽기가 너무 힘들어지는 현상. 콜백 지옥이 생기면 읽기도 수정하기도 힘든데, 주로 비동기 처리를 하다가 발생하는 경우가 많음 40 | - 콜백 헬 예시 41 | 콜백 헬 42 | 43 | - 동기(Synchronous)적이란? 44 | - 현재 실행 중인 코드가 완료되야 다음 코드를 실행할 수 있는 것 45 | - 즉시 처리 가능한 코드 대부분 46 | 47 | - 비동기(Asynchronous)적이란? 48 | - 현재 실행 중인 코드가 완료되던 안되던 다음 코드로 넘어가는 것 49 | - ex) setTimeout처럼 설정한 시간동안 실행을 보류하는것, 마우스가 클릭할때까지 실행을 보류 하는 것, api 요청을 하고 응답이 올때까지 기다리는 경우 50 | 51 | 52 | - 콜백 헬 예시 및 이를 해결하는 방법 53 | 1. 콜백 헬 예시 코드 54 | ```javascript 55 | setTimeout(function(name){ 56 | let coffeeList = name; 57 | console.log(coffeeList); 58 | 59 | setTimeout(function(name){ 60 | coffeeList += ', ' + name; 61 | console.log(coffeeList); 62 | 63 | setTimeout(function(name){ 64 | coffeeList += ', ' + name; 65 | console.log(coffeeList); 66 | 67 | setTimeout(function(name){ 68 | coffeeList += ', ' + name; 69 | console.log(coffeeList); 70 | 71 | }, 200, '커피 4') 72 | }, 200, '커피 3'); 73 | }, 200, '커피 2'); 74 | }, 200, '커피 1'); 75 | ``` 76 | 자세히 읽어보면 뭐하는 코드인지 알겠지만, 보기도 싫은 코드. 200 ms 마다 커피1, 커피 2, 커피 3, 커피 4를 더하는 함수이다. 이제 이 코드를 좀더 가독성있게 바꿔보자 77 | 78 | 2. 기명함수로 바꾸기(이름있는 함수) 79 | ```javascript 80 | let coffeelList = ''; 81 | let addCoffee1 = function(name) { 82 | coffeeList = name; 83 | console.log(coffeeList); 84 | setTimeout(addCoffee2, 200, '커피 2'); 85 | } 86 | let addCoffee2 = function(name) { 87 | coffeeList += ', ' + name; 88 | console.log(coffeeList); 89 | setTimeout(addCoffee3, 200, '커피 3'); 90 | } 91 | let addCoffee3 = function(name) { 92 | coffeeList += ', ' + name; 93 | console.log(coffeeList); 94 | setTimeout(addCoffee4, 200, '커피 4'); 95 | } 96 | let addCoffee4 = function(name) { 97 | coffeeList += ', ' + name; 98 | console.log(coffeeList); 99 | } 100 | setTimeout(addCoffee1, 200, '커피 1'); 101 | ``` 102 | 가독성은 더 좋아졌지만, 한번밖에 쓰이지 않는 함수를 하나하나 다 변수에 할당하는 것은 비효율적. 또한 코드 전체를 읽어야 흐름파악을 할 수 있다는 문제가 있다. 103 | 104 | 3. Promise 활용하기 105 | - 비동기적인 작업을 동기적으로 보이게하기 위한 장치 106 | - ES6에서 Promise, Generator가 도입되고 이후 ES7에서 async/await가 도입됨 107 | - [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 는 Promise 객체에 넘겨주는 콜백함수 내에서 resolve, reject 함수를 호출해야 then, catch 구문으로 넘어간다. 이를 통해 비동기적인 동작 내에서 resolve, reject 를 사용해 비동기 작업을 동기적으로 표현할 수 있다. 108 | ```javascript 109 | let addCoffee = function(name){ 110 | return function(coffeeList) { // 1 111 | return new Promise(function (resolve){ 112 | setTimeout(function(){ 113 | let newCoffeeList = coffeeList ? (coffeeList + ', ' + name) : name; 114 | console.log(newCoffeeList); 115 | resolve(newCoffeeList); 116 | }, 200); 117 | }) 118 | } 119 | } 120 | 121 | addCoffee('커피 1')() // 첫번째 promise 객체 반환 122 | .then(addCoffee('커피 2')) // 첫번째 promise의 return 값을 1번째 줄에 인자로 넣어준다. 123 | .then(addCoffee('커피 3')) // 두번째 promise의 return 값을 1번째 줄에 인자로 넣어준다. 124 | .then(addCoffee('커피 4')) // 첫번째 promise의 return 값을 1번째 줄에 인자로 넣어준다. 125 | ``` 126 | 127 | 4. Generator 활용하기 128 | - [Generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) 는 Iterator를 반환해주는 함수로 next를 호출해서 실행하며, yield를 만나면 해당 부분에서 실행을 멈춘다. 다음에 next를 다시 호출하면 다시 그다음 yield에서 멈춘다. **비동기 작업이 완료될때 next메서드를 호출해서 비동기 작업을 동기적으로 보이게 할 수 있다** 129 | ```javascript 130 | let addCoffee = function(coffeeList, name){ 131 | setTimeout(function() { 132 | coffeeFunc.next(coffeeList ? coffeeList + ', ' + name: name); 133 | }, 200) 134 | } 135 | let coffeeGenerator = function* () { 136 | let coffee1 = yield addCoffee('', '커피 1'); 137 | console.log(coffee1); 138 | let coffee2 = yield addCoffee(coffee1, '커피 2'); 139 | console.log(coffee2); 140 | let coffee3 = yield addCoffee(coffee2, '커피 3'); 141 | console.log(coffee3); 142 | let coffee4 = yield addCoffee(coffee3, '커피 4'); 143 | console.log(coffee4); 144 | } 145 | let coffeeFunc = coffeeGenerator(); 146 | coffeeFunc.next(); 147 | ``` 148 | 5. [Async/Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 활용하기 149 | - async는 항상 promise를 반환하는 함수로 promise만 이용하는 것보다 더 쉽게 promise객체를 사용할 수 있게 해주는 도구 150 | ```javascript 151 | let addCoffee = function(name){ 152 | return new Promise(function (resolve){ 153 | setTimeout(function(){ 154 | resolve(name); 155 | }, 200); 156 | }) 157 | } 158 | 159 | let coffeeFunc = async function() { 160 | let coffeeList = ''; 161 | 162 | let _addCoffee = async function(name) { 163 | coffeeList += (coffeeList ? ',': '') + await addCoffee(name); 164 | } 165 | 166 | await _addCoffee('커피1'); 167 | console.log(coffeeList); 168 | await _addCoffee('커피2'); 169 | console.log(coffeeList); 170 | await _addCoffee('커피3'); 171 | console.log(coffeeList); 172 | await _addCoffee('커피4'); 173 | console.log(coffeeList); 174 | } 175 | 176 | coffeeFunc(); 177 | ``` 178 | 위처럼 async/await를 이용하면 가독성이 좋게 문제를 해결할 수 있다. 179 | 180 | ### 출처 181 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 4장 182 | [콜백 헬 이미지 출처](https://medium.com/gousto-engineering-techbrunch/avoiding-callback-hell-97734e303de1) -------------------------------------------------------------------------------- /JavaScript/class.md: -------------------------------------------------------------------------------- 1 | # Class 2 | 3 | > 이 내용은 모던 JS-Deep-dive 내용을 참고하였습니다. 4 | 5 | 자바스크립트는 **프로토 타입 객체지향 언어**이다. 프로토타입 언어는 클래스가 필요 없는 프로그래밍 언어다. 6 | 7 | 따라서 클래스 없이도 생성자 함수와 프로토타입을 통해 객체지향 언어의 상속을 구현할 수 있다. 하지만 ES6 에서 `class` 가 생기며 자바와 같은 클래스 기반 객체지향 프로그래밍 언어와 매우 흡사한 객체 생성을 할 수 있게 되었다. 8 | 9 | 자바스크립트에서 클래스는 흔히 Syntactic sugar(문법적 설탕)이라고 불린다. 그 이유는 앞서 이야기 했듯 생성자 함수와 프로토타입으로 구현할 수 있지만, 더 짧고 쉽게 사용하기 위해 class 가 추가되었다는 이유 때문이다. 10 | 11 | 이 내용은 반은 맞고 반은 틀렸다. 클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않는다. 12 | 13 | **클래스는 생성자 함수보다 엄격하며 생성자 함수에서 제공하지 않는 기능도 제공한다**. 14 | 15 | 1. 클래스를 `new` 연산자 없이 호출하면 에러가 발생한다. 하지만 생성자 함수는 `new` 연산 없이 호출가능(일반 함수로 호출된다) 16 | 2. 클래스는 상속을 지원하는 `extends`, `super` 키워드를 제공한다. 17 | 3. 클래스는 호이스팅이 발생하지 않는 것 처럼 동작한다.(호이스팅은 일어난다) 18 | 4. 클래스 내부의 모든 코드에는 암묵적으로 `strict mode` 가 지정되며 **해제할 수 없다**. 19 | 5. 클래스는 `constructor`, 프로토타입 메서드, 정적 메서드 모두 `[[Enumerable]]` 값이 `false` 이다.(열거되지 않는다) 20 | 6. 클래스는 함수로 평가된다. 21 | 22 | 클래스에서 정의한 메서드의 특징 23 | 24 | 1. `function` 키워드를 생략한 축약 표현을 사용한다. 25 | 2. 객체 리터럴과는 다르게 콤마를 사용할 필요 없다. 26 | 3. 암묵적 `strict mode` 가 실행된다. 27 | 4. `for...in` 혹은 `Object.key` 메서드 등으로 열거할 수 없다. 28 | 5. 내부 메서드 `[[Constructor]]` 를 갖지 않는 `non-constructor` 이다. 따라서 `new` 연산자를 사용할 수 없다. 29 | 30 | ```js 31 | const Person = ""; 32 | { 33 | console.log(Person); // ReferenceError: ... 34 | // 만약 호이스팅이 일어나지 않는다면 '' 가 출력되어야 한다! TDZ 에 빠짐. 35 | class Person {} 36 | 37 | console.log(typeof Person); // function 38 | } 39 | 40 | class Base { 41 | constructor(name) { 42 | this.name = name; 43 | } 44 | 45 | static fn() { 46 | return `${this.name} is static function constructor fn`; 47 | } 48 | 49 | fn() { 50 | return `${this.name} is prototype fn`; 51 | } 52 | } 53 | 54 | console.log(Base.fn()); // Base is static function constructor fn; 55 | 56 | const a = new Base("1ilsang"); 57 | console.log(a.fn()); // 1ilsang is prototype fn; 58 | ``` 59 | 60 | ### Ref 61 | 62 | - 모든 자바스크립트 Deep-dive 25장. 클래스 63 | -------------------------------------------------------------------------------- /JavaScript/closure.md: -------------------------------------------------------------------------------- 1 | # 클로저 (Closure) 2 | 3 | - `A closure is the combination of a function and the lexical environment within which that function was declared` 4 | 5 | - 어떤 함수 `outer`내에 선언한 `inner`라는 함수에서 `outer`에 있는 변수를 참조하게 해놓고, `inner`함수는 외부로 전달해서 할 수 있다. 이때 `inner`함수가 outerEnvironmentReference가 이미 종료된 `outer`의 LexicalEnvironment를 참조하기 때문에 이 LexicalEnvironment의 environmentRecord에 `inner`함수가 참조하는 식별자가 가비지 컬렉팅 되지 않고 남아있는 현상을 말한다. 6 | 7 | - JavaScript에만 한정되는 내용이 아니라 ECMAScript에서도 클로저의 정의는 포함하지 않고있다. 8 | 9 | ## 클로저와 메모리 관리 10 | 11 | - 클로저는 개발자의 의도에 따라 함수의 지역변수가 사라지지 않도록 해서 발생. 12 | - 필요성이 사라지면 메모리를 소모하지 않도록 null 이나 undefined를 할당 13 | 14 | - 코드 예제 15 | ```javascript 16 | let outer = (() => { 17 | let a = 1; 18 | let inner = () => { 19 | return ++a; 20 | } 21 | return inner; 22 | })(); // 1 inner 함수 반환 23 | 24 | console.log(outer()); 25 | console.log(outer()); 26 | 27 | outer = null; // 2 28 | ``` 29 | - outer 식별자가 반환한 inner 함수가 아니라 null, undefined값을 가지도록해서 참조를 끊고 가비지 컬렉팅 될 수 있도록 한다. 30 | 31 | ## 클로저 예제 32 | 33 | - 접근 권한 제어(정보 은닉) 34 | - 정보 은닉(information hiding)이란? 35 | - 모듈 내부 로직에 대해 외부 노출을 최소하해서 모듈간의 결합도를 낮추고자 하는 현대 프로그래밍의 중요한 개념 36 | - public, private, protected 가 있음 37 | - JavaScript에는 변수 자체에 해당 접근 권한을 부여할 수 없지만 `클로저를 통해서 함수내에서 public / priviate 값을 구분할 수 있다` 38 | - return 한 변수는 `public`, 아닌 변수는 `private` 39 | 40 | 41 | - 예제: 자동차 게임에 접근 권한 제어가 필요한 이유 42 | 1. 정보은닉이 적용 안됬을때 43 | ```javascript 44 | let car = { 45 | fuel: Math.ceil(Math.random() * 10 + 10), 46 | power: Math.ceil(Math.random() * 3 + 2), 47 | moved: 0, 48 | run: function() { 49 | let km = Math.ceil(Math.random() * 6); 50 | let wasteFuel = km / this.power; 51 | if(this.fuel < wasteFuel){ 52 | console.log("연료가 모자람"); 53 | return; 54 | } 55 | this.fuel -= wasteFuel; 56 | this.moved += km; 57 | console.log(`${km} km 이동, 총 ${this.moved} km`) 58 | } 59 | } 60 | ``` 61 | 62 | 위의 객체는 car.run()으로 자동차가 이동할 수 있지만, 사용자가 임의로 `car.fuel = '임의의 값'` 으로 설정할 수 있다. 하지만 우리는 사용자가 이렇게 접근하지 못하도록 하고 싶다. 이때 클로저를 활용할 수 있다. 63 | 64 | 2. 클로저를 적용해서 fuel, power, moved는 사용자가 접근 할 수 없도록 하기 65 | ```javascript 66 | 67 | let createCar = function() { 68 | let fuel = Math.ceil(Math.random() * 10 + 10); 69 | let power = Math.ceil(Math.random() * 3 + 2); 70 | let moved = 0; 71 | 72 | // 접근 권한을 주고 싶은 것만 반환함 73 | return { 74 | get moved(){ 75 | return moved; 76 | }, 77 | run: function() { 78 | let km = Math.ceil(Math.random() * 6); 79 | let wasteFuel = km / power; 80 | if(fuel < wasteFuel){ 81 | console.log("연료가 모자람"); 82 | return; 83 | } 84 | fuel -= wasteFuel; 85 | moved += km; 86 | console.log(`${km} km 이동, 총 ${moved} km`) 87 | } 88 | } 89 | } 90 | let car = createCar(); 91 | car.run() 92 | ``` 93 | 94 | 이를 통해 car.fuel = 1000 등의 동작을 할 수 없도록 했다. 하지만 여전히 car.run 의 값은 다른 값으로 덮어 씌울수 있다. 95 | 96 | 97 | 98 | 위의 그림처럼 다른 함수로 덮어쓰기 할수가 있다. 99 | 100 | 3. car.run 조차도 다른 값으로 못바꾸도록 만들기 101 | ```javascript 102 | let createCar = function() { 103 | let fuel = Math.ceil(Math.random() * 10 + 10); 104 | let power = Math.ceil(Math.random() * 3 + 2); 105 | let moved = 0; 106 | 107 | // object.freeze로 고정시키기 108 | let publicMembers = { 109 | get moved(){ 110 | return moved; 111 | }, 112 | run: function() { 113 | let km = Math.ceil(Math.random() * 6); 114 | let wasteFuel = km / power; 115 | if(fuel < wasteFuel){ 116 | console.log("연료가 모자람"); 117 | return; 118 | } 119 | fuel -= wasteFuel; 120 | moved += km; 121 | console.log(`${km} km 이동, 총 ${moved} km`) 122 | } 123 | } 124 | 125 | Object.freeze(publicMembers); 126 | return publicMembers; 127 | } 128 | let car = createCar(); 129 | car.run() 130 | ``` 131 | 132 | 133 | 134 | 이제 car.run 도 덮어쓰기가 안된다. 135 | 136 | - 커링 함수 137 | - 커링함수(currying function)이란? 138 | - 여러개의 인자를 받는 함수를 하나의 인자를 받는 함수로 구성해서 순차적으로 호출할 수 있도록 구성한 것 139 | - 중간 과정에 있는 함수는 그다음 인자를 받기 위해 대기한 후 마지막 인자가 와야 최종 목표였던 함수를 실행 140 | - 예시 141 | ```javascript 142 | let curryingFunc = function(func) { 143 | return function(a){ 144 | console.log("1. ", a); 145 | return function(b){ 146 | console.log("2. ", b); 147 | return function(c){ 148 | console.log("3. ", c); 149 | return function(d){ 150 | console.log("4. ", d); 151 | return function(e){ 152 | console.log("5. ", e); 153 | return func(a, b, c, d, e); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | let getMax = curryingFunc(Math.max); 162 | console.log(getMax(1)(2)(3)(4)(5)); 163 | ``` 164 | 165 | 이렇게 인자를 따로따로 받을 수 있다. 위의 함수는 더 짧게 구성할 수 있다. 166 | 167 | ```javascript 168 | let curryingFunc = func => a => b => c => d => e => func(a, b, c, d, e); 169 | ``` 170 | 171 | 이처러 구성하면 커링함수를 더 쉽게 이해할 수 있다. 172 | - 커링함수에서 각 단계에서 전달한 인자들은 마지막 함수에서 참조하기 때문에, 클로저의 성질때문에 가비지 컬렉팅 되지 않고 메모리에 계속 남아있게 된다. 이후 마지막 함수에서 실행이 완료되고 실행 컨텍스트가 종료되면 가비지 컬렉팅의 대상이 된다. 173 | 174 | ## 정리 175 | 176 | 클로저는 본질적으로 실행 컨텍스트가 종료된 후에도 다른곳에서 해당 실행컨텍스트의 식별자를 참조해서 그 값이 메모리를 계속 차지하는 것이라 메모리와 밀접한 관련이 있다. 이때문에 사용하지 않는 클로저에 대해서는 null, undefined 등으로 메모리를 차지않도록 해제 해줘야한다. 177 | 178 | ### 용어 다시 정리 179 | - Lexical Environment란? 180 | - 현재 컨텍스트에서 사용되는 변수 등의 식별자에 대한 정보가 EnvironmentRecord에 기록 181 | - EnvironmentRecord에 없는 내용은 outerEnvironmentReference를 찾아가라는 뜻 182 | 183 | ### 출처 184 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 5장 -------------------------------------------------------------------------------- /JavaScript/fp.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "함수형 프로그래밍" 4 | comments: true 5 | description: "" 6 | keywords: "" 7 | --- 8 | 9 | 10 | ## index 11 | 12 | - 순한맛 13 | - 함수형 프로그래밍이란? 14 | - 보통맛 15 | - 지연 연산, 커링, 병렬성, 비동기 16 | - 매운맛 17 | - Functor, Monad... 18 | 19 | 20 | 21 | 22 | 23 | ## What is Functional Programming 24 | 25 | 1. a programming paradigm 26 | 2. a coding style 27 | 3. a mindset 28 | 29 | ## Why Functional ? 30 | 31 | 1. More easy (what is prototype, this) 32 | 2. safer, easier to debug / maintain 33 | 34 | 35 | 36 | ## Again What is FP 37 | 38 | - 고차 함수 39 | - 일급 함수 40 | - 커링 41 | - 재귀 42 | - 멱등성 43 | - 순수 함수와 참조 투명성 44 | - 불변성과 영속적 자료구조 45 | 46 | 47 | 48 | ## How...? 49 | 50 | ### Do everything with function 51 | 52 | input -> output 53 | 54 | ```js 55 | // Not functional 56 | const name = "manhyuk"; 57 | const greeting = "Hi, I'm "; 58 | console.log(gretting + name); 59 | ``` 60 | 61 | 62 | 63 | Input , output 개념으로 표현하지 않았기 때문에 functional하지 않다 64 | 65 | 66 | 67 | ```js 68 | // Functional 69 | function greet(name) { 70 | return "Hi I'm " + name; 71 | } 72 | greet("manhyuk"); 73 | ``` 74 | 75 | input, output 으로 표현 76 | 77 | 78 | 79 | ### Pros. Avoid side effect 80 | 81 | 순수함수란 기본적으로 함수가 input만을 받아 output을 계산하는 함수를 뜻 한다. 82 | 83 | ```js 84 | // Not pure 85 | const name = "manhyuk"; 86 | function greet() { 87 | console.log("Hi I'm " + name) 88 | } 89 | ``` 90 | 91 | `name` 변수를 input으로 받지 않고 전역 변수에서 읽어왔고 output 또한 없다 92 | 93 | 94 | 95 | ```js 96 | // pure 97 | function greet(name) { 98 | return "Hi I'm " + name; 99 | } 100 | ``` 101 | 102 | output에 영향을 주는것은 input 뿐이다 103 | 104 | 105 | 106 | 107 | 108 | ## High Order Function 109 | 110 | 함수를 input으로 받거나 output으로 함수를 리턴하는 함 111 | 112 | ```js 113 | function makeAdjectifier(adjective) { 114 | return function (string) { 115 | return adjective + ' ' + string; 116 | } 117 | } 118 | 119 | const coolifier = makeAdjectifier('cool'); 120 | coolifier('conference') // cool conference 121 | ``` 122 | 123 | `map, filter, reduce` 또한 고차함수의 일종이다 124 | ```js 125 | // map 126 | function map(f, iter) { 127 | const result = []; 128 | for (const it of iter) { 129 | result.push(f(it)); 130 | } 131 | return result; 132 | } 133 | ``` 134 | ```js 135 | // filter 136 | function filter(f, iter) { 137 | const result = []; 138 | for (const it of iter) { 139 | if (f(it)) { 140 | result.push(it); 141 | } 142 | } 143 | return result; 144 | } 145 | ``` 146 | ```js 147 | // reduce -> Imperative programming 148 | const numbers = [1, 2, 3, 4, 5]; 149 | let total = 0; 150 | for (const number of numbers) { 151 | total = total + number; 152 | } 153 | console.log(total); 154 | 155 | // reduce -> Declarative programming 156 | const add = (a, b) => a + b; 157 | const reduce = (f, acc, iter) => { 158 | if (!iter) { 159 | iter = acc[Symbol.iterator](); 160 | acc = iter.next().value; 161 | } 162 | for (const it of iter) { 163 | acc = f(acc, it); 164 | } 165 | return acc; 166 | }; 167 | 168 | 169 | reduce(add, 0, numbers); 170 | // add(add(add(add(add(0, 1), 2), 3), 4), 5) 171 | ``` 172 | ```js 173 | // 명령형 174 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 175 | let total = 0 176 | let limit = 2; 177 | for (const number of numbers) { 178 | if (number % 2 === 0) { 179 | const temp = number * number; 180 | total = temp; 181 | if (--limit) { 182 | break; 183 | } 184 | } 185 | } 186 | console.log(total); 187 | 188 | // 선언형 189 | pipe( 190 | filter(number => number % 2), 191 | map(number => number * number), 192 | take(2) 193 | )(numbers) 194 | ``` 195 | 196 | ## Immutable 197 | 198 | ```js 199 | const rooms = [1, 2, 3]; 200 | rooms[2] = 4; 201 | console.log(rooms); // 1, 2, 4 202 | ``` 203 | 204 | 이 코드는 rooms를 임의로 변경하기 때문에 rooms를 사용하는 다른곳에선 문제가 생길 수 있다. 205 | 206 | ```js 207 | const rooms = [1, 2, 3] 208 | const newRooms = rooms.map(room => { 209 | if (room === 3) { 210 | return 4; 211 | } else { 212 | return room; 213 | } 214 | }) 215 | console.log(newRooms) // 1, 2, 4 216 | ``` 217 | 218 | rooms를 복사하여 새로운 newRooms라는 변수에 값을 변경했기 때문에 219 | 220 | `rooms`를 사용하고 있는 다른 코드에서는 문제가 생기지 않게된다. 221 | 222 | 223 | 하지만 이 코드는 배열이 커지게 된다면 더 오래걸리고 더 메모리를 많이 차지하게 된다. 224 | 225 | 이를 해결하기 위해 `Persistent Data Structure` 를 사용하면 된다. 226 | 227 | 기존의 배열을 복사하기 위해 새 배열을 만드는것이 아니라 228 | 229 | 기존 배열의 요소들을 트리로 만든뒤 새로운 요소만 트리에 합쳐주면 된다. (structure sharing) 230 | 231 | 이러한 귀찮고 복잡한 과정을 `immuatable.js`를 통해 쉽게 구현이 가능하다. 232 | 233 | 234 | ### Lazy 235 | ```js 236 | // 반복문을 사용한 일반적인 range 함수 237 | const range = length => { 238 | let i = -1; 239 | const result = []; 240 | while (++i < length) { 241 | result.push(i); 242 | } 243 | return result; 244 | } 245 | const list = range(2) // [0, 1] 246 | reduce(add, list) 247 | ``` 248 | 249 | ```js 250 | const L = {}; 251 | L.range = function *(length) { 252 | let i = -1; 253 | while (++i < length) { 254 | yield i; 255 | } 256 | } 257 | const list = L.range(2) // suspend (iterator) 258 | // list.next() 259 | reduce(add, list) 260 | ``` 261 | generator를 사용해 함수가 평가되기 직전까지 값이 만들어지지 않는다. (지연평가) 262 | 263 | ```js 264 | //test 265 | function test(name, time, f) { 266 | console.time(name) 267 | while(time--) { 268 | f() 269 | } 270 | console.timeEnd(name) 271 | } 272 | 273 | test('range', 10, () => reduce(add, range(10000))) // 500ms 274 | test('L.range', 10, () => reduce(add, L.range(10000))) // 200ms 275 | ``` 276 | 277 | 278 | 279 | 280 | ## 모나드에 대해서 281 | 1. 함수 합성 : compose(f, g)(x) = (f ∘ g)(x) = f(g(x)) 282 | 2. Functor의 기본 : Array.map()에 대한 이해. 283 | 284 | ### Lifting 285 | 리프팅은 특정 타입을 다루는 함수를 특정 타입과 관련된 다른 타입을 다루는 함수로 변화시키는 방법 286 | 287 | ```js 288 | function add10(x: number): number { 289 | return x + 10; 290 | } 291 | ``` 292 | 이 함수는 number에 대해서 오류없이 완벽하게 작동하는 순수함수이다. 293 | 하지만 number에 대해서만 작동하기 때문에 String 또는 List에 대해서 작동하지 않는다. 294 | 295 | ```typescript 296 | function add10ForArray(xArr: number[]): number[] { 297 | const resultArr: number[] = []; 298 | 299 | for (const x of xArr) { 300 | resultArr.push(add10(x)); 301 | } 302 | 303 | return resultArr; 304 | } 305 | ``` 306 | List에 대해서 작동하게 하기 위해 위와같은 함수를 추가로 만들어야한다. 307 | 308 | 309 | 만약에 String, List 을 input으로 넣어도 잘 작동해야 한다면? 310 | ```js 311 | function addA(x: string): string { 312 | return x + 'a'; 313 | } 314 | ``` 315 | ```typescript 316 | function addAForArray(xArr: string[]): string[] { 317 | const resultArr: string[] = []; 318 | 319 | for (const x of xArr) { 320 | resultArr.push(addA(x)); 321 | } 322 | 323 | return resultArr; 324 | } 325 | ``` 326 | 327 | 이러한 중복코드를 줄일 수 있도록 도와주는게 리프팅이다 328 | 329 | ```typescript 330 | function transFunctionForArray(f: (x: P) => R): (x: P[]) => R[] { 331 | return (xArr: P[]): R[] => { 332 | const resultArr: R[] = []; 333 | 334 | for (const x of xArr) { 335 | resultArr.push(f(x)); 336 | } 337 | 338 | return resultArr; 339 | } 340 | } 341 | 342 | const add10ForArray = transFunctionForArray(add10); 343 | const addAForArray = transFunctionForArray(addA); 344 | ``` 345 | 346 | 리프팅은 코드의 재사용성을 비약적으로 높여 주는 방법이다. 347 | 어떤 타입 A의 값을 다루는 함수가 있을 때, 이 함수를 A와 관련된 다른 타입 `F` 의 값에도 적용하고 싶다면 348 | 그 타입을 지원하기 위한 코드가 들어간 새 함수를 만드는 게 일반적이다. 349 | 350 | 리프팅은 A타입의 값을 다루는 함수를 가지고 `F` 타입의 값을 다루는 함수를 간단하게 정리할 수 있다. 351 | 352 | 353 | ### Functor 354 | ![functor](/images/fp/functor.jpg) 355 | 356 | - 한 범주의 대상과 사상을 다른 범주로 대응하는 함수 357 | - 자신을 구성하는 타입의 값을 다루는 함수를 리프팅하거나 자신에게 적용하는 방법을 제공해야 한다. 358 | - 리프팅이 가능한 `F` 꼴의 선언을 갖고, `(f: (x: A) => B) => (tx: F) => F 꼴 또는 (f: (x: A) => B, tx: F) => F` 꼴의 함수를 지원해야 한다. 359 | 360 | 가장 친숙한 예로 `map`이 functor의 일종이다. 361 | 362 | ```java 363 | interface Functor { 364 | Functor map(Function f); 365 | } 366 | Int stringToInt(String string); 367 | ``` 368 | 369 | 370 | `map`은 단지 원소를 순회하는 용도가 아닌 `타입`을 `타입` 으로 변경해주는 기능이다. 371 | 372 | `map`은 값을 꺼낼 수도 없고, 메소드로 값을 변경하는 용도밖에 없는데 사용하는 이유는 373 | 일반적으로 모델링할 수 없는 상황을 모델링할 수 있다. 374 | 375 | 간단하게 표현하자면 376 | `Optional -> map( stringToInt() ) -> Optional` 로 표현할 수 있다 377 | 378 | 379 | ```js 380 | [].map(i => i * i); 381 | [1].map(i => i * i); 382 | ``` 383 | 384 | ```typescript 385 | function parseBool(x: string): Maybe { 386 | const isTrue = /^true$/; 387 | const isFalse = /^false$/; 388 | const caseIgnoredX = x.toLowerCase(); 389 | 390 | if (isTrue.test(caseIgnoredX)) return new Just(true); 391 | else if (isFalse.test(caseIgnoredX)) return new Just(false); 392 | else return new Nothing; 393 | } 394 | 395 | function not(x: boolean): boolean { 396 | return !x; 397 | } 398 | 399 | parseBool('true')[map](not); // Just(false) 400 | parseBool('false')[map](not); // Just(true) 401 | parseBool('is not boolean')[map](not); // Nothing 402 | ``` 403 | 404 | 405 | ### Monad 406 | 407 | 안전한 함수 합성을 위해 사용 408 | 409 | 410 | ~~js예제 코드가 생각안나서 java로..~~ 411 | ```java 412 | Cart cart = getCart(); 413 | if (cart != null) { 414 | Product product = cart.getProduct(); 415 | if (product != null) { 416 | // ... ~ 417 | } 418 | } 419 | 420 | // using monad 421 | Optinal.ofNullable(getCart().ifPrenset(cart -> 422 | Optinal.ofNullable(cart.getProduct().ifPresnet(product -> { 423 | // ... 424 | })) 425 | )) 426 | 427 | // js 428 | getCart().map(cart -> 429 | cart.getProduct().map(product -> { 430 | // ... 431 | }) 432 | ) 433 | ``` 434 | `Optional`도 모나드의 일종 435 | 436 | 값이 미래에 준비된다거나 지금 당장 평가할 수 없는 코드를 안전하게 사용하기 위한 방법 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | --- 451 | 452 | 참고 453 | 1. https://overcurried.com 454 | 2. https://www.inflearn.com/course/함수형_ES6_응용편 455 | 3. https://www.youtube.com/watch?v=jI4aMyqvpfQ&ab_channel=naverd2 456 | -------------------------------------------------------------------------------- /JavaScript/hoisting.md: -------------------------------------------------------------------------------- 1 | # Hoisting 2 | 3 | ## JavaScript의 변수가 선언되는 단계 4 | 5 | JavaScript에서는 `var, let, const` 키워드를 통해서 변수를 선언한다. JavaScript는 변수 선언을 아래의 2단계를 거친다. 6 | 7 | 1. `선언 단계`: 변수 이름(식별자)를 실행 컨텍스트에 등록한다. 이를 통해 JavaScript Engine에 변수의 존재를 알린다. 8 | 2. `초기화 단계`: 값을 저장하기 위해 메모리 공간을 확보하고 암묵적으로 `undefined`를 할당해 초기화한다. 9 | - 이건 C 같은 언어에서는 변수를 선언한 후 초기화를 개발자가 직접 하지 않고 해당 변수의 값을 읽으면 쓰레기 값이 읽히는데, JavaScript는 `undefined`로 초기화해서, 이런 위험을 줄인다. 10 | 11 | ## Hoisting이란? 12 | 13 | JavaScript에서는 이상하게도 아래와 같은 코드가 동작을 한다. 14 | 15 | ```javascript 16 | console.log(score); // (1) undefined가 출력 17 | var score; // (2) 변수 선언문 18 | ``` 19 | 20 | C와 같은 언어에서는 상상도 할 수 없는 일들이 일어나버렸다. 선언도 하기전에 변수를 사용했는데 `에러를 내지 않고, 그저 undefined만 출력한다`. 이건 **변수 선언이 소스코드가 실행될 때 일어나는 것이 아니라(런타임), 그전에 실행되기 때문이다.** JavaScript Engine은 소스코드를 실제로 실행하기 전에 `평가 과정`이라는 것을 가지는데, 이때 `변수 선언문, 함수 선언문 등의 모든 선언문`을 먼저 실행한다. 그래서 실제로 코드의 어디에서 변수가 선언되던지간에 항상 다른 코드보다 먼저 실행되는 것이다. 21 | 22 | 위의 코드를 보면 변수 선언문인 (2) 의 경우 코드가 실행되기 전에 실행되어, 코드가 실제로 실행되는 시점에 (1)에서 출력을 하면 `변수의 선언이 이미 완료된 후`라 `undefined`가 출력이 된다. 이를 쉽게 표현해서 `JavaScript Engine은 선언 문을 코드의 선두로 끌어올린다`라고도 표현할 수 있는데, 이러한 JavaScript 고유의 특징을 Hoisting이라고 한다. 23 | 24 | ## Hoisting 예시 살펴보기 25 | 26 | ```javascript 27 | console.log(score); // (1) 28 | var score = 80; // (2) 29 | console.log(score); // (3) 30 | ``` 31 | 32 | 위와 같은 코드가 있다면 어떻게 동작할까? 이때 `var score=80` 은 변수의 선언과 값의 할당이 하나의 문으로 단축해서 표현되어 있지만, 변수 선언과 값의 할당이 따로 이루어진것처럼 동작한다. 33 | 34 | ```javascript 35 | console.log(score); // (1) 36 | var score; // (2) 37 | score = 80; // (3) 38 | console.log(score); // (4) 39 | ``` 40 | 41 | 즉, 위와 똑같이 동작한다. 이 (2)번 줄은 JavaScript Engine에 의해 Hoisting되고, 실제 값이 할당되는 (3) 은 원래의 소스코드가 실행되는 시점에 실행된다(런타임). 그래서 `(1) 은 undefined, (2) 는 80`을 출력한다. 42 | 43 | ## Temporal Dead Zone 44 | 45 | - case1: let을 사용한 경우 46 | 47 | ```javascript 48 | console.log(score); // (1) 49 | let score; // (2) 50 | score = 80; // (3) 51 | console.log(score); // (4) 52 | ``` 53 | 54 | - case2: const를 사용한 경우 55 | 56 | ```javascript 57 | console.log(score); // (1) 58 | const score = 80; // (3) 59 | console.log(score); // (4) 60 | ``` 61 | 62 | case1과 case2 모두 `ReferenceError: score is not defined` 에러를 발생 시킨다. 이를 보고 그럼 `const, let`은 hoisting이 되지 않는가 생각할 수 있다. 하지만 JavaScript는 `var, let, const` 등 모든 선언문은 호이스팅을 한다. 하지만 `const, let`와 `var` 가 다르게 동작하는 이유는 `초기화 단계`때문이다. `var`의 경우 호이스팅 될때 `undefined`로 초기화까지 완료된다. 하지만 `const, let`의 경우 **호이스팅 단계에서 초기화가 일어나지 않는다.** 이 키워드들은 런타임에 값이 평가가 될때 초기화가 된다. 그래서 이 평가가 일어나기 이전을 `temporal dead zone`이라고 부른고, 이때 해당 score 변수/상수에 접근하면 에러가 발생한다. 63 | 64 | 즉, 결론적으로 `let, const`도 호이스팅을 한다. 65 | 66 | ### 출처 67 | 68 | [모던 자바스크립트 DeepDive](http://www.yes24.com/Product/Goods/92742567) 4장 69 | 70 | [Are variables declared with let or const hoisted?](https://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-hoisted) 71 | -------------------------------------------------------------------------------- /JavaScript/jsHistory.md: -------------------------------------------------------------------------------- 1 | # JavaScript의 역사 2 | 3 | JavaScript는 1995년 당시 시장 점유율이 90프로이던 Netscape communications가 `브라우저에서 동작하는 것을 목표`로 [`Brendan Eich`](https://en.wikipedia.org/wiki/Brendan_Eich)가 개발한 언어이다. Brendan Eich씨가 10일간 걸려서 만든 JavaScript는 그 후 여러 변화를 거쳐 지금은 모든 브라우저의 표준 프로그래밍 언어가 되었다! 4 | 5 | ## 표준화를 위한 JavaScript의 노력 6 | 7 | JavaScript가 출시되고 1년뒤쯤 1996년 8월에 Microsoft는 JavaScript의 파생 버전인 `JScript`를 출시해서 이를 Internet Explorer에 사용했다. 그런데 JavaScript랑 JScript가 표준화된 기준이 없고 적당히 호환되었는데, Microsoft와 Netscape communications가 서로 시장에서 점유율을 높이기 위해서 더욱 더 다른 기능들을 추가하기 시작했다... OMG 8 | 9 | 이러한 이유로 브라우저마다 어떤건 되고, 어떤건 안되는 식의 문제가 많이 발생(`크로스 브라우징 이슈`)해서 개발자들의 삶이 고달파졌다. 그래서 표준화된 기준을 마련하기 위해 1996년 11월에 Netscape communications가 [ECMA](https://en.wikipedia.org/wiki/Ecma_International) 인터네셔널에 자바스크립트의 표준화를 요청했다. 10 | 11 | ## ECMAScript 출시 12 | 13 | - 1997년 7월에 ECMA-262라고 불리는 ECMAScript 1 sepecification이 완성되었다! 이제 JavaScript는 ECMAScript 명세를 구현한 언어로 표준화되기 시작했다. 14 | 15 | - 이후 `1999년에 ECMAScript3`가 공개 되었고, 이후 `10년만인 2009년에 HTML5와 함께 ECMAScript5(ES5)`가 출시 되었다. 16 | 17 | - 그리고 2015에 범용 프로그래밍 언어가 갖춰야할 기능들인 `let/const, arrow function, class, module`등을 갖춘 ECMAScript6(ES6)가 공개 되었다. 이 이후에는 매해 기능을 변경해서 발표하고 있다. 18 | 19 | - `ESNext`란? 엄청난 변화가 있었던 ES6부터 그 이후의 버전을 모두 ESNext라고 통칭한다 20 | 21 | ## ECMAScript 각 버전별 특징 파악하기 22 | 23 | 1. ES1 24 | - 1997년에 출시된 ECMAScript 1 25 | 2. ES2 26 | - 1998년 출시 27 | 3. ES3 28 | - 1999년 29 | - 드디어 `try... catch` 및 정규 표현식 기능이 추가되었다. 30 | - 이 이전에는 표준화된 에러처리 방식이 없었다 31 | 4. ES5 32 | - 2009년에 HTML5와 함께 출시되었다. 33 | - JSON, strict mode와 같은 기능들이 추가되었다. 34 | 5. **ES6** 35 | - 2015년에 일어난 대변화 36 | - `let/const, arrow function, class` 도입 37 | - 템플릿 리터럴 38 | - [디스트럭처링 할당](https://poiemaweb.com/es6-destructuring) 39 | - 스프레드/rest 파라미터 40 | - 심벌 41 | - 프로미스 42 | - Map/Set 43 | - 이터러블 44 | - for ... of 45 | - 제너레이터 46 | - Proxy 47 | - Module import/export 48 | 6. ES8 49 | - 2017년 50 | - `async/await` 도입 51 | 7. ES9 52 | - 2018년 53 | - Object의 rest/spread 54 | - [async generator](https://ko.javascript.info/async-iterators-generators) 55 | - [for await ... of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) 56 | 8. ES11 57 | - 2020년 58 | - null 병합 연산자 59 | - 옵셔널 체이닝 연산자 60 | 61 | ### 출처 62 | 63 | [모던 자바스크립트 DeepDive](http://www.yes24.com/Product/Goods/92742567) 2장 64 | [What is the difference between JavaScript and ECMAScript?](https://www.freecodecamp.org/news/whats-the-difference-between-javascript-and-ecmascript-cba48c73a2b5/) 65 | -------------------------------------------------------------------------------- /JavaScript/propertyAttribute.md: -------------------------------------------------------------------------------- 1 | # Property Attribute 2 | 3 | 자바스크립트의 프로퍼티에 대해서 알아보고 객체의 속성을 다루는 방법을 알아보자. 4 | 5 | > 해당 글은 모던 자바스크립트 딥 다이브 16 장을 요약했습니다. 6 | 7 | 자바스크립트에는 내부 슬롯(Internal slot)과 내부 메서드(Internal method)가 존재한다. 이것들은 JS 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티(Pseudo property)와 의사 메서드(Pseudo method)이다. 8 | 9 | 이중 대괄호(`[[...]]`)로 감싼 이름들이 이에 해당한다. 엔진에서 동작하는 방식을 설명한 것이지 개발자가 직접 접근할 수는 없다. 단 일부 내부 슬롯/메서드에 한정해 접근 방법을 제공해 주고 있긴하다. 10 | 11 | 예를 들어 모든 객체는 `[[Prototype]]` 이라는 내부 슬롯을 갖는다. 이는 JS 엔진 내부 로직이므로 직접 접근할 수 없지만 `[[Prototype]]` 내부 슬롯은 `__proto__` 를 통해 간접적으로 접근할 수 있다. 12 | 13 | ```js 14 | // JS-deep-dive 에제 16-01 15 | const o = {}; 16 | 17 | // 내부 슬롯이므로 접근 불가능 18 | o[[Prototype]]; // Error 19 | 20 | // 단, 일부 내부 슬롯/메서드 에 한정해 간접적으로 접근할 수 있음. 21 | o.__proto__; // Object.prototype 22 | ``` 23 | 24 | JS 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다. 25 | 26 | ```js 27 | // JS-deep-dive 예제 16-03 28 | const person = { 29 | name: "Lee", 30 | }; 31 | 32 | person.age = 20; 33 | 34 | // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다. 35 | Object.getOwnPropertyDescriptors(person); 36 | /* 37 | { 38 | name: { value: "Lee", writable: true, enumerable: true, configurable: true }, 39 | age: { value: 20, writable: true, enumerable: true, configurable: true }, 40 | } 41 | */ 42 | ``` 43 | 44 | 객체의 프로퍼티 정보를 제공해주는 프로퍼티 디스크립터 객체를 살펴보면 `[[Value]]`(프로퍼티의 값), `[[Writable]]`(프로퍼티 값의 변경 가능 여부), `[[Enumerable]]`(프로퍼티의 열거 가능 여부), `[[Configurable]]`(프로퍼티 재정의 여부)가 존재하는 것을 알 수 있다. 45 | 46 | **_해당 값을 변경해 객체의 성질을 변경시킬 수 있다._** 47 | 48 | ```js 49 | // JS-Deep-dive 예제 16-08 50 | const person = {}; 51 | 52 | Object.defineProperty(person, "firstName", { 53 | value: "Ungmo", 54 | writable: true, 55 | enumerable: true, 56 | configurable: false, 57 | }); 58 | 59 | /* 60 | person > { 61 | firstName: 'Ungmo', 62 | }; 63 | */ 64 | 65 | // [[Configurable]] 이 false 이므로 프로퍼티 변경이 불가능함. 66 | // 따라서 delete 가 무시됨(에러 발생 하지 않음) 67 | delete person.firstName; 68 | ``` 69 | 70 | 해당 값을 사용해 완전한 불변객체를 만들어 줄 수 있다. 71 | 72 | ### Ref 73 | 74 | - [모던 자바스크립트 Deep dive](http://www.yes24.com/Product/Goods/92742567) 16장 프로퍼티 어트리뷰트 75 | -------------------------------------------------------------------------------- /JavaScript/prototype.md: -------------------------------------------------------------------------------- 1 | # 프로토타입 (Prototype) 2 | 3 | - JavaScript는 `프로토타입` 기반 언어이다. 클래스 기반에서는 상속이라는 개념을 사용하지만, JavaScript에서는 원형객체(프로토타입)을 복제(참조) 해서 상속의 효과를 얻는다. 4 | - 이것만 잘 이해해도 숙련자 레벨이라고 하니, 어려워도 이해해보자! 5 | 6 | ## 프로토타입이란? 7 | 8 | - 객체를 생성할때 일어나는 일 9 | 10 | ```javascript 11 | let instance = new Constructor(); 12 | ``` 13 | 14 | 1. 어떤 생성자 함수가 있다. 그리고 이 생성자 함수는 prototype 속성이 있다(없을수도 있다) 15 | 2. 이 생성자 함수를 new 연산자와 함께 호출한다. 16 | 3. Constructor에 정의된 내용으로 인스턴스(새로운 객체)가 생긴다. 17 | 4. 이러한 인스턴스는 `__proto__` 속성이 부여된다. 18 | 5. 인스턴스의 `__proto__` 는 Constructor의 prototype 속성을 참조한다. 19 | 20 | * 도식화: 위의 절차를 도식화 하면 아래와 같다. 21 | 22 | 23 | * 예제: 24 | ```javascript 25 | let Person = function(name){ 26 | this._name = name; 27 | } 28 | Person.prototype.getName = function(){ 29 | return this._name 30 | } 31 | let hayoung = new Person("Hayoung"); 32 | hayoung.__proto__.getName(); // 1 결과: undefined 33 | hayoung.getName() // 2 결과: Hayoung 34 | ``` 35 | - 1번째 줄에서 결과가 undefiend인 것은 이 경우 this 가 `hayoung.__proto__` 이기 때문 36 | - 2번째 줄에서 `__proto__` 는 __**생략 가능**__하기 떄문에 hayoung.getName()을 통해서는 this 가 hayoung이기 때문에 제대로된 결과가 출력된다. 37 | - Person 생성자의 속성: 38 | 39 | 40 | 41 | - hayoung 인스턴스의 속성: 42 | 43 | 44 | 45 | * 프로토타입의 개념 정리 46 | * JavaScript는 함수에 자동으로 prototype이라는 속성을 설정함 47 | * 이런 함수를 new 연산자와 함께 생성자 함수로 사용하는 경우, 새로 생성된 인스턴스에 `__proto__` 속성이 생기고, 이건 생성자 함수의 prototype을 참조한다 48 | * 생성자 함수의 prototype에 어떤 속성 및 메소드가 있으면 인스턴스에서도 `__proto__`을 통해 생성자 함수의 prototype을 참조해서 사용할 수 있다 49 | * 이때 함수에 자동으로 생긴 prototype에는 constructor 속성이 있고, 여기는 자기 자신에 대한 정보가 있다. 인스턴스에서도 `__proto__`를 통해 자신의 constructor를 참조 할 수 있다. 50 | 51 | 52 | 53 | ## 프로토타입체인이란? 54 | 55 | - 메서드 오버라이드란? 56 | - `instance.__proto__` 에서 `__proto__` 를 생략하고 prototype의 내용을 참조할 수 있는데, 인스턴스에 동일한 이름의 프로퍼티나 메서드가 있다면 오버라이드가 된다. 57 | - 예제 58 | ```javascript 59 | let Person = function(name){ 60 | this._name = name; 61 | } 62 | Person.prototype.getName = function(){ 63 | return this._name 64 | } 65 | let hayoung = new Person("Hayoung"); 66 | hayoung.getName = function() { // 1 67 | return "인스턴스: " + this._name; 68 | } 69 | 70 | console.log(hayoung.getName()) // 2 결과: "인스턴스: hayoung" 71 | console.log(hayoung.__proto__.getName()) // 3 결과: undefined 72 | console.log(hayoung.__proto__.getName.call(hayoung)) // 4 결과: hayoung 73 | ``` 74 | - 1번째 줄에서 hayoung.getName에 새로운 함수를 정의하면 `hayoung.__proto__`의 getName이 아니라 hayoung 인스턴스의 `getName`에 접근한다 75 | - JavaScript 엔진은 가장가까운 자신의 프로퍼티를 검색하고, 없으면 `__proto__`에서 내용을 찾는다. 76 | - 이제 prototype 메소드를 접근하고 싶을때, 3번째 줄에서는 결과가 undefined라고 나온다. 이때 this를 다시 hayoung 인스턴스를 바라보게 하려면 4번째 줄에서처럼 call, apply, bind를 적용할 수 있다. 77 | 78 | - 프로토타입 체이닝(prototype chaining)이란? 79 | - 프로토타입 체인이란 `__proto__`프로퍼티가 연쇄적으로 이어진것 80 | - 이 체인을 따라가면서 검색하는것을 프로토타입 체이닝 81 | - 모든 객체는 프로토타입 체이닝을 따라가다가 최상단에 도착하면 `Object.prototype`이 존재한다 82 | - 예외로 Object.create로 생성한 함수는 `__proto__`가 없는 객체를 생성해서 프로토타입 체인을 따라갔을때 `Object.prototype`가 없을 수 있다 83 | 84 | 85 | ### 출처 86 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 6장 -------------------------------------------------------------------------------- /JavaScript/this.md: -------------------------------------------------------------------------------- 1 | # this 2 | 3 | 많은 객체지향 언어에서는 `this`를 클래스에서만 사용하기 때문에, 우리가 예상하는대로 동작한다. 즉, Java와 같은 객체지향 언어에서는 this란 클래스로 생성한 인스턴스를 가리킨다. 하지만 JavaScript는 함수와 객체(메서드)의 구분이 느슨하고, `this`를 어디서든 사용할 수 있기 때문에, 상황에 따라 `this`가 달라져서 우리가 원하지 않는 동작을 할 가능성도 있다. 4 | 5 | ## 상황에 따라 달라지는 this 6 | 7 | JavaScript에서 함수를 호출할 때, 즉 실행 컨텍스트가 생성될때 `this`가 결정된다. 그리고 이와 같은 특성 때문에 함수를 **`어떻게`** 호출하는지에 따라 `this`가 달라지는 것이다. 8 | 9 | - 전역 공간에서의 this 10 | - 전역 공간에서 this는 전역 객체로, 브라우저 환경에서는 `window`, Node.js 환경에서는 `global`이다. 11 | 12 | - 메서드로 호출할 때 메서드 내의 this 13 | - 함수를 실행하는 일반적인 방법 두가지: 메서드로 호출하기 또는 함수로 호출하기 14 | - 두가지 방법을 구분하는 차이는 `독립성` 15 | - 메서드는 자신을 호출한 대상 객체에 대한 동작 수행 16 | - 함수는 그 자체로 독립적인 기능 수행 17 | - JavaScript에서는 객체의 메서드로 호출하면 메서드로 동작하고, 함수로 호출하면 함수가 된다. 18 | - 함수앞에 `.` 이 있는가로 쉽게 구분할 수 있다. `.`이 있으면 메소드, 없으면 함수 19 | 20 | ```javascript 21 | let func = function(x) { 22 | console.log(this, x); // 결과 Window{...} 1; 23 | }; 24 | func(1); 25 | 26 | let obj = { 27 | method: func 28 | } 29 | 30 | obj.method(2); // { method: f} 2 31 | obj['method'](2); // { method: f} 2 32 | ``` 33 | 34 | 35 | - 함수로서 호출할 때 함수 내의 this 36 | - 함수로 호출할때는 this가 지정되지 않으므로 함수내에서 `this`는 전역객체이다. 37 | - __**메서드 내부함수에서의 this**__ 38 | - 내부함수를 함수로서 호출했는지, 메서드로 호출했는지 파악을 통해 this 값을 예상할 수 있다. 39 | 40 | ```javascript 41 | let obj1 = { 42 | outer: function() { 43 | console.log(this); // 1 44 | 45 | let innerFunc = function () { 46 | console.log(this); // 2 47 | } 48 | 49 | innerFunc(); // 3 50 | 51 | let obj2 = { 52 | innerMethod: innerFunc 53 | }; 54 | 55 | obj2.innerMethod(); // 4 56 | } 57 | } 58 | 59 | obj1.outer(); // 5 60 | ``` 61 | 1. 5번째 줄에서 obj1.outer() 을 호출하면 메서드로 호출했기 때문에 1번째 줄의 결과값은 `obj1` 이다. 62 | 2. 3번째 줄에서 innerFunc()를 함수로서 호출하면 this가 지정되지 않기 때문에 2번째 줄의 결과값은 `window 객체`이다. 63 | 3. 4번째 줄에서 obj2.innerMethod()를 호출하면 메서드로 호출했기 때문에 2번째 줄의 결과값은 `obj2` 이다. 64 | 65 | - 위의 방식의 문제점: 3번째 줄 실행 결과가 어색하다. 호출객체가 없을때 전역객체를 바인딩하지 않고, 호출 당시 주변 환경의 this를 상속 받는 것이 더 자연스러운 동작. 66 | - 해결 방법 1: ES5에서 화살표 함수가 없을때 67 | ```javascript 68 | let obj1 = { 69 | outer: function() { 70 | console.log(this); // 1 71 | 72 | let self = this; // 2 73 | let innerFunc = function () { 74 | console.log(self); // 3 75 | } 76 | 77 | innerFunc(); // 4 78 | 79 | let obj2 = { 80 | innerMethod: innerFunc 81 | }; 82 | 83 | obj2.innerMethod(); // 5 84 | } 85 | } 86 | 87 | obj1.outer(); // 6 88 | ``` 89 | 1. 2번째 줄에서 `let self = this`를 통해 this의 값을 지정. 90 | 2. 4번째 줄에서 innerFunc()를 실행하면 this가 아니라, self에 담겨있던 주변환경의 this가 출력 91 | 3. 5번째 줄에서 obj2.innerMethod()를 실행해도 self에 담겨있던 주변환경의 this가 출력 92 | 93 | - 해결 방법 2: ES6 이후로 화살표 함수가 등장했을 때 94 | - [화살표 함수](https://poiemaweb.com/es6-arrow-function)는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완함. 따라서 화살표 함수를 사용하면 this를 자동으로 바인딩하지 않음. 95 | ```javascript 96 | let obj1 = { 97 | outer: function() { 98 | console.log(this); // 1 99 | 100 | let innerFunc = () => { 101 | console.log(this); // 2 102 | } 103 | 104 | innerFunc(); // 3 105 | } 106 | } 107 | ``` 108 | 1. innerFunc를 화살표 함수로 변경 109 | 2. 3번째 줄에서 innerFunc를 호출하면 **화살표 함수이기때문에 전역객체를 바인딩 하지 않음** 110 | 3. innerFunc내의 this는 주변환경의 this를 그대로 상속 받고, 2번째 줄의 결과로 { outer: f } 를 출력 111 | 112 | - [콜백 함수](./callback) 호출 시 함수 내의 this 113 | - 콜백함수도 함수이기때문에 기본적으로 this가 전역객체를 참조하지만, 콜백 함수를 받는 함수에서 콜백함수에 this가 될 대상을 지정할 수 있음 114 | - 콜백함수 예시 115 | ```javascript 116 | setTimeout(function() { 117 | console.log(this); // 1 118 | }, 300) 119 | ``` 120 | setTimeout이라는 함수는 `function() { console.log(this);}`를 전달받고 이에 대한 제어권을 가지게 된다. 121 | 이때 setTimeout은 함수 내부에서 콜백함수의 대상이 될 this를 지정하지 않기때문에, 1번째 줄의 결과는 전역객체이다. 122 | - 콜백함수 내에서 this는 상황에 따라 다름 123 | 124 | - 생성자 함수 내부의 this 125 | - JavaScript는 함수에 생성자 역할도 부여해서, `new` 명령어와 함께 함수를 호출하면 해당 함수가 생성자로 동작함 126 | - 생성자 함수 내에서 예시 127 | ```javascript 128 | let Cat = function (name, age){ 129 | this.bark = '야옹'; 130 | this.name = name; 131 | this.age = age; 132 | } 133 | let choco = new Cat('초코', 7); 134 | let navi = new Cat('나비', 4); 135 | console.log(choco, navi); 136 | ``` 137 | 138 | 139 | 140 | - 생성자 함수 내에서 this는 해당 객체를 의미함. 이를 통해서 해당객체에 속성을 할당할 수 있음 141 | 142 | 143 | ## 명시적으로 this를 바인딩하는 방법 144 | 145 | - [call 메서드](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) 146 | - call 함수를 통해서 임의의 객체를 this로 지정하는 방법 147 | ```javascript 148 | let func = function(a, b, c){ 149 | console.log(this, a, b, c); // 1 150 | } 151 | 152 | func(1,2,3); // 2 153 | func.call({x: 1}, 4, 5, 6); // 3 154 | ``` 155 | - 2번째 줄의 결과는 this가 전역객체이다. 결과로 `Window{...} 1 2 3` 156 | - 하지만 3번쨰 줄에서 call을 통해서 첫번째 인자로 this로 지정할 객체를 넘겨 줄 수 있다. 이때 1번째 줄의 결과는 `{ x: 1} 4 5 6` 157 | 158 | 159 | - [apply 메서드](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) 160 | - apply 함수와 call 함수는 기능적으로 완전히 동일 161 | - 차이점은 arguments를 __**배열로 넘겨준다는 것**__ 162 | ```javascript 163 | let func = function(a, b, c){ 164 | console.log(this, a, b, c); // 1 165 | } 166 | 167 | func.apply({x: 1}, [4, 5, 6]); // 2 168 | ``` 169 | - 2번째 줄에서 apply 함수를 통해 통해서 첫번째 인자로 this로 지정할 객체를 넘겨 준다. 그외의 arguments를 배열로 넘겨준다. 이때 1번째 줄의 결과는 `{ x: 1} 4 5 6` 170 | 171 | 172 | - [bind 메서드](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) 173 | - ES5에서 추가됨 174 | - call/apply와 유사하나, 즉시 호출하지 않고, 넘겨받은 this를 기반으로 새로운 함수를 반환 175 | ```javascript 176 | let func = function(a, b, c){ 177 | console.log(this, a, b, c); // 1 178 | } 179 | 180 | let bindFunc = func.bind({x: 1}); // 2 181 | bindFunc(4,5,6); // 3 182 | ``` 183 | - 2번째 줄에서 bind 함수를 통해 {x: 1}이 this로 바인드된 새로운 함수를 반환해준다. 이를 이용해서 bindFunc 함수에 인자를 넘기고 실행하면 1번째 줄의 결과는 `{ x: 1} 4 5 6` 184 | 185 | 186 | ### 출처 187 | [코어 자바스크립트](http://www.yes24.com/Product/Goods/78586788) 3장 188 | -------------------------------------------------------------------------------- /JavaScript/usefulCode.md: -------------------------------------------------------------------------------- 1 | # 의미있는 코드 및 구현체 2 | 3 | ### deepFreeze 4 | 5 | `Object.freeze` 를 사용하면 최초 키를 동결시킬 수는 있으나 내부객체를 자동으로 동결시켜 줄 수는 없다. 이때문에 재귀적으로 순회하며 객체를 동결시켜야 한다. 6 | 7 | ```js 8 | function deepFreeze(target) { 9 | if (!target || typeof target !== "object" || Object.isFrozen(target)) { 10 | return target; 11 | } 12 | 13 | Object.freeze(target); 14 | Object.entries(target).forEach(([key, value]) => deepFreeze(value)); 15 | } 16 | 17 | const a = { 18 | b: "this is a.b", 19 | c: 123, 20 | }; 21 | 22 | const b = { 23 | a, 24 | b: "this is b.b", 25 | }; 26 | 27 | deepFreeze(b); 28 | Object.isFrozen(b); // true 29 | Object.isFrozen(b.a); // true 30 | 31 | b.c = 123; 32 | b.a.c = "321"; 33 | 34 | console.log(b); 35 | ``` 36 | 37 | ### Scope-safe constructor 38 | 39 | 생성자 함수를 `new` 키워드로 사용하지 않고 호출하게 될 경우 `this` 바인딩이 달라진다.(`[[Constructor]]`) 따라서 `new` 연산자를 사용하지 않았을 경우 자동으로 넣어 생성자 호출시 휴먼에러를 방지한다. 40 | 41 | ```js 42 | function Person(name) { 43 | // ES6 44 | if (!new target()) { 45 | // ES5 46 | // if (!this instanceof Person) { 47 | return new Person(name); 48 | } 49 | 50 | this.name = name; 51 | this.getName = function () { 52 | return `제 이름은: ${this.name} 입니다.`; 53 | }; 54 | } 55 | 56 | const person1 = Person("1ilsang"); 57 | const person2 = new Person("2ilsang"); 58 | 59 | console.log(person1.getName()); 60 | console.log(person2.getName()); 61 | ``` 62 | 63 | ### instanceof 64 | 65 | `instanceof` 를 구현해 보자. 66 | 67 | `객체 instanceof 생성자 함수` 의 뜻은 우변의 생성자 함수의 `prototype` 에 바인딩된 객체가 좌변 객체의 _프로토타입 체인_ 상에 존재하는지 체크한다. 존재한다면 `true` 를 반환. 68 | 69 | ```js 70 | function isInstanceof(instance, constructor) { 71 | const prototype = Object.getPrototypeOf(instance); 72 | 73 | if (prototype === null) return false; 74 | 75 | return ( 76 | prototype === constructor.prototype || isInstanceof(prototype, constructor) 77 | ); 78 | } 79 | 80 | console.log(isInstanceof(person, Person)); // true 81 | console.log(isInstanceof(person, Object)); // true 82 | console.log(isInstanceof(person, Array)); // false 83 | ``` 84 | 85 | ### Ref 86 | 87 | - 모던 자바스크립트 Deep-dive 88 | - MDN 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Road-of-CODEr, Developers. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WE HATE JAVASCRIPT 2 | 3 | ### 구성 4 | 5 | 1. [JavaScript](JavaScript/README.md) 6 | 7 | - 자바스크립트 그 자체를 탐구하는 레포 8 | 9 | 2. [TypeScript](TypeScript/README.md) 10 | 11 | - 타입스크립트를 탐구하는 레포 12 | 13 | 3. [Front-End](Front-End/README.md) 14 | 15 | - 프론트엔드 생태계를 알아보는 레포 16 | 17 | 4. [Back-End](Back-End/README.md) 18 | - 백엔드 생태계를 알아보는 레포 19 | 20 | ### LICENSE 21 | 22 | This is released under the MIT license. See [LICENSE](LICENSE) for details. 23 | -------------------------------------------------------------------------------- /TypeScript/README.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | 어이어이ㅋㅋ 타입없이 되냐고ㅋㅋ 4 | 5 | - 타입스크립트란? 6 | - 간단한 예제 7 | - 인터페이스 8 | - `Record` 타입 9 | --------------------------------------------------------------------------------