# 학습 자료

- Learning React

 

# 제 1장 리액트 소개

1.1 튼튼한 토대

리액트를 사용하면 자바스크립트 안에서 HTML 같아 보이는 코드를 작성할 수 있다.

그런 Tag를 브라우저에서 실행하려면 Webpack 과 같은 도구를 통해 전처리를 수행할 것이다.

자바스크립트의 배열, 객체, 함수에는 익숙해져야 개발이 수월하다.

함수형 프로그래밍은 리액트 탄생의 바탕이 되었다.

리액트의 부수적인 장점은 가독성, 재사용성, 테스트 가능성이 좋은 패턴을 더 많이 활용하게 된다는 점이다.

 

리액트는 컴포넌트를 사용해, 사용자 인터페이스를 만든다.
그리고, 컴포넌트를 합성(compose)하고 프롭(prop)과 상태(state)를 사용해 로직을 추가한다.
그리고, 리액트 훅스(React Hooks)를 통해, 컴포넌트에 대한 상태 로직을 재사용할 수 있게 해준다.

훅스(Hooks)와 서스펜스(Suspense)가 데이터를 읽어올 때 어떻게 도움이 되는지 살펴볼 것이다.

라우팅(Routing), 테스팅, 서버측 렌더링(Server-side rendering) 등의 리액트 생태계 내 존재하는 도구를 소개할 것이다.

 

1.2 리액트의 과거와 미래

리액트는 jQuery, Angular 등의 여러 UI 라이브러리 범주에 속한다.

리액트 컴포넌트는 JS 어플리케이션의 사용자 인터페이스나 View 계층을 담당한다.

리액트는 라이브러리라고 소개한다.

모든 Use case에 적합한 도구를 제공하지 않고, 특정 기능망 구현해 제공하는데 관심이 있다는 뜻이다.

리액트 훅스(Hooks)란?
여러 컴포넌트 간에 상태가 있는 로직을 추가하고 공유하는 새로운 방법이다.
리액트 서스펜스(Suspense)란?
비동기 렌더링을 최적화하는 방법이다.

 

1.3 코드 예제 사용법

github : https://github.com/enshahar/learning-react-kore/tree/seconded 

리액트 개발에는 크롬 개발자 도구 설치가 필수적이다.

개발자 도구를 설치하고 나면, 리액트 컴포넌트 트리(DOM)를 탐색할 수 있다.

노드(Node.js)란?
풀스택(Full-stack) 어플리케이션을 구축할 수 있는 런타임 환경이다.

리액트를 다루려면 노드(Node.js)를 설치해야 한다.

노드를 설치하면 자동으로 npm도 설치된다.

npm이란?
노드 패키지 관리자의 약자로, JS 커뮤니티에 구현해 놓은 프레임워크, 라이브러리, 도우미 함수를 패키지 형태로 설치할 수 있도록 해준다.

리액트도 npm 라이브러리의 예이다.

# npm 사용법
npm install
npm install [패키지명]
npm remove [패키지명]

 

package.json 파일이란?
JS 프로젝트 폴더에 존재하는 파일로써, 프로젝트에 필요한 모든 노드 패키지 정보를 기술한 파일이다.
npm install을 실행하면 npm이 프로젝트에 필요한 모든 패키지를 설치한다.
yarn이란?
npm의 대안으로써, 의존 관계를 좀더 신뢰성 있게 관리할 수 있도록 돕고, yarn을 사용하는 프로젝트 폴더에는 yarn.lock 파일이 존재한다.
# yarn 설치
npm install -g yarn
# yarn 사용법
yarn install
yarn add [패키지명]
yarn remove [패키지명]

 

# 제 2장 리액트를 위한 자바스크립트

처음에 JS는 웹페이지에 버튼 클릭, 마우스 hover 상태, 입력 폼 검증 등의 상호작용을 추가하기 위해 만들어진 보조적 언어였다. DHTML과 AJAX를 거쳐 더 활발히 사용되어 왔다.

노드(Node.js)의 등장으로, JS는 FullStack 어플리케이션을 개발하는데 쓰이는 언어가 되었다.

JS의 변경을 이끌어나가는 위원회는 유럽 컴퓨터 제조사 위원회(ECMA)이다.

ES1에서 시작하여 ES6=ES2015까지 버전업 해오고 있다.

브라우저 벤더(Chrome, Firefox)는 ES 버전 업에 따라 추가되는 문법을 지원할 수 있도록 브라우저 코드를 수정한다.

JS 컴파일은 최신 JS 문법을 브라우저가 인식할 수 있는 예전 JS 문법으로 변환하는 과정이다.

 

2.1 변수 선언하기

ES6 이전 - var를 사용하는 것이 변수를 선언하는 유일한 방법

[ES6 문법] const란?
값을 변경할 수 없는 상수 선언 시 사용되는
자바스크립트 신규 keyword 이다. (ES6 버전부터 추가)

 

JS도 이제는 구문적인 변수영역 규칙(lexical variable scoping)을 지원한다.

기존 var 키워드를 사용할 경우, 기 선언된 global 변수명과 블록({}) 내 선언한 변수명이 동일할 때, 블록 내 변수와 global 변수를 동일한 것으로 취급한다.

(중요!) const는 변수를 다시 대입하는 것은 막아주지만, 변수가 가리키는 객체의 내용을 변경하는 것은 막지 않는다.

객체의 불변성이 정말 필요한 경우에는 Object.freeze()를 통해 객체를 고정시켜주어야 한다.

var을 사용할 경우, if/for 안에서 변수를 선언해도, 변수의 영역이 글로벌 영역에 변수가 생성된다.
let 키워드를 사용하면 변수의 영역을 코드 블록({}) 안으로 한정시킬 수 있다.
let을 사용하면 블록 안에서 글로벌 변수를 보호할 수 있다.
[ES6 문법] let이란? 
변수의 영역을 코드 블록({}) 안으로 한정시킨 변수를 선언하기 위해 사용되는 
자바스크립트 신규 keyword 이다. (ES6 버전부터 추가)

템플릿 문자열

템플릿 문자열을 문자열 연결 대신 사용할 수 있다.

[ES6 문법] 템플릿 문자열(`TEXT... ${변수명} TEXT...`)이란?
문자열 중간에 쉽게 변수를 삽입할 수 있도록 지원하는 문법이다.

템플릿 문자열은 공백(빈칸 뿐만 아니라 탭이나 개행문자)등을 포함한다.

<AS-IS>
console.log(lastName + ", " + firstName + " " + middleName)
<TO-BE>
console.log(`${lastName}, ${firstName} ${middleName}`)
document.body.innerHTML = `
<section>
<h2>${article.title}</h2>
<p>content</p>
</section>
`;

2.2 함수 만들기

함수 선언

[ES6 이전 문법] 호이스팅(Hoisting)이란?
ES6 문법 이전의 JS는 동작원리상 함수 또는 변수 선언을 작성하기 이전에 함수나 변수를 호출해도 되었고,
코드 블록 중간에 정의(선언)된 변수(함수)를 먼저 호출했을 때, 해당 변수(함수)를 마치 코드 블록 맨 처음에 정의하고 undefined로 초기화한 변수(함수)처럼 처리되는 것을 호이스팅되었다고 지칭한다. 

JS도 이제는 함수가 Hoisting 되지 않도록 함수를 선언할 수 있다.

함수 표현식에 의해 만들어진 함수를 함수 표현식이 실행되기 전에는 호출할 수 없다.
[ES6 문법] 함수 표현식(function expression)이란?
함수를 표현식처럼 선언할 수 있는 문법이다.
(이름 없는) 함수를 만들며, 변수에 값을 대입할 수 있다.
<AS-IS>
// 선언하기 전에 함수 호출이 가능
doSomething('홍길동', 20);
function doSomething(name, age) {
   console.log('저의 이름은 ' + name + '이고, 나이는 ' + age + '입니다.');
}

<TO-BE>
const doSomething = function(name, age) {
   console.log(`저의 이름은 ${name}이고, 나이는 ${age}입니다.`);
}
// 선언한 후에야 호출 가능
doSomething('홍길동', 20);

JS도 이제는 함수 선언 시, 기본값을 선언할 수 있다.

[ES6 문법] 디폴트 파라미터란?
함수 선언 시, 인자의 기본값을 지정할 수 있도록 지원하는 문법이다.
function doSomething(name="홍길동", age = 20) {
   console.log(`저의 이름은 ${name}이고, 나이는 ${age}입니다.`);
}

화살표 함수

JS 상에서 이제 function 키워드 없이 함수를 선언할 수 있습니다.

[ES6 문법] 화살표 함수란?
function 키워드를 생략하고, 화살표(=>) 키워드를 활용하여
간단하게 함수 정의(선언)할 수 있도록 지원하는 문법이다.
<AS-IS>
const doSomething = function(name, age) {
    console.log(`저의 이름은 ${name}이고, 나이는 ${age}입니다.`);
}
<TO-BE>
const doSomething = (name='홍길동', age=20) => {
    console.log(`저의 이름은 ${name}이고, 나이는 ${age}입니다.`);
}

// 입력 파라미터가 없는 경우
const doSomething = () => {
    console.log('입력 파라미터가 없는 함수 입니다.');
}

// 입력 파라미터를 하나만 받는 경우, 주변의 입력 괄호[()]를 생략해도 된다.
const doSomething = name => {
    console.log(`저의 이름은 ${name}입니다.`);
}

// 한 줄로 계산해서, 반환(return)하는 함수의 경우, 중괄호[{}]를 생략해도 된다.
const getTextByName = name => `이름 : ${name}`
console.log(getSomething('홍길동')) // 이름 : 홍길동

// (중요!) 한 줄로, 객체를 반환(return)하는 함수인 경우, 소괄호[()]를 둘러싸야 오류가 발생하지 않는다.
const getObjectByInfo = (name='홍길동', age=20) => ({
    name: name,
    age: age
})

JS의 this 바인딩과 setTimeout() 함수

JS 상에서 function 키워드를 활용한 함수는 새로운 this 영역을 생성한다.
화살표 키워드를 활용한 함수는 새로운 this 영역을 생성하지 않는다.
setTimeout(function, delay) 함수에서, 인자로 기존 JS 함수 선언(function) 방식을 채택하고, this 키워드를 사용 시, 의도한 바와 다르게, Window 객체를 반환하는 문제가 발생한다. 화살표 함수 사용시 setTimeout(function, delay) 함수 내에서도 this의 영역이 제대로 유지된다.
<AS-IS>
const gangwondo = {
   resorts: ['용평', '평창],
   print: function() {
     console.log(this); // gangwondo 객체
     setTimeout(function() {
       console.log(this); // Window 객체 (자바스크립트의 설계 오류)
       console.log(this.resorts.join(',')); // 에러 발생
     }, 1000);
   }
}
<TO-BE>
const gangwondo = {
   resorts: ['용평', '평창],
   print: function() => {
     console.log(this); // gangwondo 객체
     setTimeout(() => {
       console.log(this); // gangwondo 객체 (제대로된 this의 영역 반환)
       console.log(this.resorts.join(','));
     }, 1000);
   }
}
// 주의사항 : 모든 함수를 화살표 함수로 바꾸면 this 영역이  의도치 않게 Window 객체를 반환한다.
// function 키워드 함수만 this 영역을 새롭게 생성한다.
const gangwondo = {
   resorts: ['용평', '평창],
   print: () => {
     console.log(this); // Window 객체
     setTimeout(() => {
       console.log(this); // Window 객체 (화살표 함수는 this 영역을 만들지 않음)
       console.log(this.resorts.join(',')); // 에러 발생
     }, 1000);
   }
}

2.3 자바스크립트 컴파일하기

자바스크립트 컴파일링이란?
신규 자바스크립트 문법을 더 호환성이 높은 JS 코드로 변환하는 과정이다. 
코드를 바이너리로 변환하는 것이 아니라는 점에서 전통적인 컴파일링과 자바스크립트 컴파일링은 다르다.
바벨(Babel)이란?
신규 자바스크립트 문법이 이전 브라우저에서 동작할 수 있도록, 더 호환성이 높은 JS 코드로 변환하는 유명한 JS 컴파일링 도구이다.

 

바벨을 통해 브라우저가 해당 문법(기능)을 지원할 때까지 기다리지 않아도, 최신 자바스크립트 기능을 바로 사용할 수 있게 되었다. 

< 바벨이 내부적으로 하는 일 >
const add =- (x=5, y=10) => console.log(x+y);
// 바벨 변환 후
"use strict"; // JS 코드 엄격한 모드 실행

var add = function add() {
   var x = arguments.length <= 0 || arguments[0] === undefined ? 5 : arguments[0];
   var y = arguments.length <= 1 || arguments[1] === undefined? 10 : arguments[1];
   return console.log(x + y);
}
기존 문법 중 함수의 arguments 배열을 통해, 디폴트 파라미터 기능을 처리한다.

웹팩(Webpack)이란?

바벨과 같은 도구를 통해 이루어지는 자바스크립트 컴파일은 자동화된 빌드 도구인 Webpack에 의해 처리된다. Webpack과 비슷한 것은 파슬(Parcel) 정도가 있다.

 

2.4 객체와 배열

ES2016부터 객체와 배열을 다루는 방법과 객체와 배열 안에서 변수의 영역을 제한하는 다양한 방법을 제공하였다.

구조 분해를 사용한 대입

구조 분해(destructuring)이란?
객체의 구조를 분해해서, 객체 내부의 변수명과 동일한 이름의 변수에 값을 넣어주거나 함수 선언시 입력 객체의 구성요소만 접근해서 사용할 수 있도록 하는 편의성 문법이다.

구조분해 문법을 사용하면, 객체의 변수명과 동일한 이름의 변수를 한번에 여러개 선언할 수 있고,

객체를 입력으로 받는 함수 선언 시 (.)을 사용하지 않고, 선언할 수 있다.

// 객체 구조분해 활용 예시
const sandwich = {
   bread: "허니 오트",
   cheese: "아메리칸",
   ...
}

<AS-IS>
const bread = sandwich.bread;
const cheese = sandwich.cheese;

<TO-BE>
// (중요!) 객체 멤버 변수와 동일한 변수명을 일일히 별도 선언하고, 대입할 필요가 없다.
const {bread, cheese} = sandwich;
// 값에 의한 참조이다. 값을 변경해도, 원래 객체의 변수에는 영향을 주지 않음

console.log(bread, cheese); //허니 오트 아메리칸

<AS-IS>
const printNameOfPerson = person => {
   console.log(`이 사람의 이름은 ${person.name}입니다.`);
};
const printNameOfPerson = person => {
   console.log(`이 사람의 성은 ${person.nameInfo.lastName}입니다.`);
};

<TO-BE>
// (중요!) 객체의 멤버 변수에 접근하기 위해, (.) 문법을 사용할 필요가 없다.
const printNameOfPerson = ({name}) => {
   console.log(`이 사람의 이름은 ${name}입니다.`);
};
const printNameOfPerson = ({nameInfo: { lastName } }) => {
   console.log(`이 사람의 성은 ${lastName}입니다.`);
};
// 입력으로 받는 객체를 바로 구조분해하여, 사용할 수 있다.

배열 구조 분해하기

배열 구조 분해(destructuring) & 리스트 매칭(list matching)이란?
배열에서, 무시하고 싶은 원소의 위치에 콤마를 넣어서, 리스트를 구조 분해하여 변수에 대입하거나 활용할 수 있도록 지원하는 문법을 의미한다.

const animalList = ["캥거루", "코끼리", "토끼"];

<AS-IS>
const firstAnimal = animalList[0];
const thirdAnimal = animalLIst[2];

<TO-BE>
const [firstAnimal] = animalList; // firstAnimal이라는 변수를 선언 후, 리스트의 첫번째 값을 대입한다.
const [,,thirdAnimal] = animalList; // thirdAnimal이라는 변수를 선언 후, 리스트의 세번째 값을 대입한다.

객체 리터럴 개선

객체 리터럴 개선(Object Literal Enhancement) 또는 객체 재구축이란?
구조 분해(destructuring)의 반대로써, 구조를 다시 만들어 객체로 만드는 과정 또는 내용을 한데 묶는 과정을 지원하는 편의성 문법이다.

객체 리터럴 개선을 사용하면, 현재 영역에 있는 변수를 객체의 필드로 묶을 수 있다.

// 객체 리터럴 개선 문법을 사용시, 변수와 동일한 이름의 멤버변수 또는 함수를 가진 객체를 생성할 수 있다.
const name = '홍길동';
const age = 20;
// (중요) 화살표 함수가 아닌 function()만 this 영역을 생성하므로, this 키워드를 통해 객체의 key에 접근하려면 function 키워드를 사용하여야 한다.
cosnt print = function() {
   console.log(`이 사람의 이름은 ${this.name}`이고, 나이는 ${this.age}입니다.`);
}

<AS-IS>
const person = {
   name: name,
   age: age,
   print: print
};

<TO-BE>
const person = {name, age, print};

console.log(person); // {name: '홍길동', age: 20}

일반 메서드 선언이 아닌 객체의 메서드를 간편하게 선언하기

<AS-IS>
const person = {
   name: '홍길동',
   print :  function() {
      console.log(`이 사람의 이름은 ${this.name}`입니다.`);
   },
   setName : function(name) {
      this.name = name;
   }
}

<AS-IS>
// (중요) 일반 함수 선언이 아닌, 객체 내 함수 선언 시에 : function() 키워드를 ()로 대체할 수 있습니다.
const person = {
   name: '홍길동',
   print() {
      console.log(`이 사람의 이름은 ${this.name}`입니다.`);
   },
   setName(name) {
      this.name = name;
   }
}
객체 리터럴 개선을 통해 현재 영역에서 볼 수 있는 변수들을 객체의 필드에 대입할 수  있으며, function 키워드를 입력하지 않아도 되어 에포트가 줄어든다.

 

스프레드 연산자

스프레드 연산자(...)란?
배열의 내용을 조합(두 배열을 조합하여, 통합 배열을 만듬)하거나, 함수의 인자를 n개의 배열로 받을 수 있도록 해주는 편의성 문법이다.
const arr1 = [1,2,3,4,5];
const arr2 = [6,7];
const arr = [...arr1, ...arr2];
console.log(arr.join(', ')); // 1,2,3,4,5,6,7
const copyArr1 = [...arr1];
console.log(copyArr1.join(', ')); // 1,2,3,4,5
const [first, ...rest] = arr1;
console.log(rest); //[2,3,4,5]

// 스프레드 연산자를 사용해 n개의 가변인자를 배열 형태로, 받을 수 있다.
// 함수 파라미터 정의에서, 스프레드 연산자가 쓰일 때 Rest 파라미터라고 명칭한다.
function doSomething(...args) {
  let [start, ...remaining] = args;
  let [last, ...remaining2] = remaining.reverse();
}

// 스프레드 연산자는 배열 뿐만 아니라, 객체에서도 동일하게 사용 가능하다.
const person = {
   name: '홍길동',
   age: 20
}
const job = '의사';
const newPerson = {
   ...person,
   job
}
console.log(newPerson); // {name:'홍길동', age:20, job:'의사'}

JS 배열 관련 암기하면 좋은 함수

const arr = [1, 2, 3, 4, 5];
const strArr = arr.join(', '); 
console.log(strArr); // 1,2,3,4,5
arr.reverse();
console.log(arr); // [5,4,3,2,1]

2.5 비동기 자바스크립트

좀더 쉽게 비동기 처리를 할 수 있는 방법이 존재한다.

(1) 단순 Promise와 fetch

기존에 REST API에 요청을 보내기 위해서는 20줄이 넘는 코드를 작성해야만 했다. fetch() 함수가 등장하면서 매우 간편해졌다.

fetch(apiUrl)란?
자바스크립트에서 비동기 API 요청을 간편하게 처리할 수 있도록 ECMAScript 위원회에 채택된 함수이다.
// fetch는 유일한 인수로 데이터를 받아올 URL을 입력받는다.
fetch(api_url);

// fetch는 비동기 요청 성공시 response 객체를 반환하는 Promise 객체를 반환한다.
fetch("https://api.randomuser.me/?nat=US&results=10")     
    .then(response => response.json())          
    .then(members => console.log(members))     
    .catch(err => console.error(err))

(2) async/await 

async 함수 키워드란?
전통적인 동기방식의 함수 선언 앞에 async 키워드를 붙여, 해당 함수를 비동기 함수로 만들어주는 문법이다.
async 함수는 Promise<void> 또는 Promise<Return 값타입> 객체를 반환한다.

await 키워드란?
비동기 async 함수 내에서 또 다른 비동기 작업을 순차적으로 실행해야할 경우,
await Promise 문법을 활용하여 Promise 다음에 있는 코드를 실행하기 전에 Promise가 끝날 때까지 기다리라고 명령할 수 있다.

(중요!) await Promise는 Promise.then() 함수를 연쇄 호출해서 Promise의 결과를 기다리는 방식과 완전히 동일한 기능을 하므로, 병행하여 사용하지 않는다.

(중요!) await와 Promise.then()은 서로 대체재 역할을 하므로, 병행하여 사용하지 않는다.

conse getPerson() = async () => {
   try {
      // Promise.then()과 동작 방식이 동일하다.
      let res = await fetch("https://...getMember"); 
      let res2 = await fetch("https://...getMember"); 
      let { results } = res.json();
      console.log(results);
   } catch (error) {
     // Promise.catch()와 동작 방식이 동일하다.
     console.error(error);
   }
}
// 함수를 호출할 때 비동기 작업이 실행되고, Promise 객체를 생성하여 즉시(동기적으로) 반환한다.
getPerson(); 

(3) Promise 만들기

Promise란?
JS에서 비동기적인 작업을 단순명료하게 처리할 수 있게 도와주는 JS 객체이다.
간단하게 비동기 작업을 정의하고 성공 이후 처리할 작업과 실패 이후 처리할 작업을 정의할 수 있다.


Promise 객체 선언 시,

명시된 비동기 작업을 성공적으로 완료하고 나면, resolve함수를 호출하고,

문제가 생겼을 경우, reject 함수를 호출하는 방식으로 선언한다.

# Promise 객체 선언 방법
const getSomethingAPI = () => new Promise((resolve, reject) => {
   // API 요청 로직 구현
   // 비동기 작업을 성공적으로 완료했을 경우, 결과값과 함께 resolve() 함수 호출
   // 비동기 작업 중 문제가 생겼을 경우, 에러정보와 함께 reject() 함수를 호출
   request.status === 200 ? resolves(request.response.results) : reject(Error(request.statusText));
}
Promise.then() / Promise.catch() 함수란?
비동기 작업을 명시한 Promise를 처리하는 방법 중 하나로써,
성공적으로 처리했을 경우 then() 함수에 callback 함수를 넘겨주어 이후 작업을 처리하고,
비동기 작업에 문제가 생겼을 경우, catch() 함수에 callback 함수를 인자로 넣어서 에러를 처리한다.

 

# (중요)Promise 객체.then() 함수는 Callback 함수를 인자로 받는다.
# 성공적으로 처리된 Promise를 처리하기 위해, then() 함수는 연쇄적으로 호출할 수 있다.
-> 기존 callback 지옥을 가독성 좋게 표현할 수 있다.
# Promise 객체.then() 함수를 통해, Promise 객체 내 비동기 작업이 성공적으로 완료된 이후에, 처리된 결과값을 받아 이후 로직을 처리할 수 있다.
# Promise 객체.catch() 함수를 통해, Promise 객체 내 비동기 작업이 에러가 발생했을 경우, 전달받은 에러정보를 받아 실패 처리를 할 수 있다.
getSomethingAPI()
     .then(something => console.log(something))
     .catch(error => console.error(`getSomethingAPI 호출 실패: ${error.message}`));

2.6 클래스

ES2015 이후에 추가된 JS 상에서 class 문법이다. Java과 같이 객체지향 언어와 비슷한 구문을 제공하므로, 초기에 리액트가 클래스 기반의 컴포넌트를 만들었으나, 현재는 함수를 사용해 컴포넌트를 구성한다.

 

기존에는 프로토타입을 사용한 상속(prototypical inheritance)라고 불리는 기법으로, class와 유사하게 사용하였다.

<AS-IS>
function Vacation(destination, length) {     
   this.destination = destination     
   this.length = length   
}   

// 프로토타입(prototype)을 사용한 상속 기법
// 객체지향 언어의 커스텀 타입을 만들 때와 비슷하다.
Vacation.prototype.print = function() {     
   console.log(this.destination + "은(는) " + this.length + "일 걸립니다.")   

 
var maui = new Vacation("마우이", 7)   
maui.print()

<TO-BE>
// 일반적인 class 선언과 생성
class Vacation {   
    constructor(destination, length) {     
       this.destination = destination     
       this.length = length   
    }   
    print() {     
       console.log(this.destination + "은(는) " + this.length + "일 걸립니다.")   
    }   
}   
const trip = new Vacation("칠레 산티아고", 9)   
trip.print()

// 상속 예시
class Expedition extends Vacation {     
   constructor(destination, length, gear) {       
      super(destination, length)       
      this.gear = gear     
   }
   // 동일한 명칭의 함수는 overiding 된다.      
    print() {       
       super.print()       
       console.log(`당신의 ${this.gear.join("와(과) 당신의 ")}를(을) 가져오십시오.`)     
    }   
}   
const trip = new Expedition(      "한라산",      3,      ["선글라스", "오색 깃발", "카메라"]    )   
trip.print()

2.7 ES6 모듈

자바스크립트 Module이란?
다른 JS 파일에서 이름 충돌이 없이 쉽게 불러서 활용할 수 있는 재사용 가능한 코드 조각을 의미한다.
JS는 한 모듈당 하나씩 별도의 파일로 저장한다.

모듈을 만들고 외부에 export 하는 방법

- 한 모듈에서 여러 JS 함수/객체/배열/변수/상수를 외부에 노출시키는 방식 : export를 여러개 사용한다.

- 한 모듈에 하나의 JS 객체를 노출시키는 방식이 있다. : export default 문법을 사용한다.

 

export/import 문법이란?
export를 사용해, 다른 모듈에서 함수/객체/배열/변수/상수 등을 사용할 수 있도록 노출시킬 수 있다.
import 명령을 사용해 다른 JS 파일에 있는 모듈을 불러와 사용할 수 있다.

- export 를 사용하지 않는 함수/객체/배열/변수/상수 등 선언은 모두 모듈(파일) 내부에만 한정된다.(로컬 선언이다)

- 외부에 여러 이름을 노출한 모듈을 import할 때에는 객체 구조분해(destructuring)을 활용할 수 있다.

// a.js
export const print(msg) => log(msg);
export const log(msg) => console.log(`message : ${msg}`);

// b.js
import { print, log } from './a'; // or './a.js'
print('메시지입니다.');
log('메시지입니다.');

// 모듈에서 가져온 대상에 다른 이름을 부여할 수 있다.(as)
import {print as p, log as l} from './a'

// 다른 모듈에서 가져온 모든 이름을 사용자가 정한 로컬 이름 공간 안에 가둘 수 있다.(* with as)
import * as fns from './a'

 

export default / import 문법이란?
특정 모듈에서 단 하나의 이름만을 외부에 export 하고 싶을 때, 사용되는 문법으로써 export 대신에 사용된다.
// person.js
const person = new Person('홍길동', 20);
export default person;

// b.js
import person from './person'; 
person.print();
CommonJS란?
모든 버전의 노드(Node)에서 지원하는 일반적인 모듈 패턴이다.

 

CommonJS 상에서의 module 문법이란?
CommonJS를 사용할 경우, module.exports = <객체> 문법를 사용해 JS 객체를 export할 수 있다.
CommonJS를 사용할 경우, require('./모듈파일명') 함수를 통해 모듈을 import한다.

CommonJS 상에서는 export/import 문법을 사용할 수 없고, 상단의 명시된 문법을 사용하여야 한다.

// a.js
const print(msg) => log(msg);
const log(msg) => console.log(`msg : ${msg}`);
module.exports = {print, log};

// b.js
const {log, print} = require('./a.js');

# 제 3장 JS를 활용한 함수형 프로그래밍

함수형 프로그래밍이란?

여러 함수를 모아둔 것으로 코드를 생각하고,
그런 함수를 서로 합성해서 APP을 구축하는 프로그래밍 방식이다.

 

최신 JS 문법은 함수형 프로그래밍 기법을 더 풍부하게 해주는 화살표 함수, Promise, 스프레드 연산자 등이 추가되었다.

3.1 함수형이란?

JS에서는 함수를 변수에 넣을 수 있다.

JS에서는 함수를 객체에 넣을 수 있다.

JS에서는 함수를 배열에 추가할 수 있다.

JS에서는 함수를 다른 함수의 인자로 넘길 수 있다.

JS에서는 함수가 함수를 반환할 수 있다.

 

고차함수란?

함수가 함수를 인자로 받는 경우와 함수가 함수를 반환하는 경우를 의미한다.

함수 선언 시, 2개 이상의 화살표를 사용한다면 고차 함수를 사용하고 있다는 의미이다.

 

선언적 프로그래밍이란?
필요한 것을 달성하는 과정을 하나하나 기술하는 것보다 필요한 것이 어떤 것인지를 기술하는 것에 더 방점을 두고, APP의 구조를 세워나가는 프로그램밍 스타일이다.

 

- 선언적 프로그래밍의 코드 구문은 어떤 일이 발생해야 하는지에 대해 기술하고, 실제로 그 작업을 처리하는 방법은 추상화를 통해 아랫단에 감춰진다.

- 선언적 프로그램은 코드 자체가 어떤 일이 벌어질지에 대해 설명하기 때문에 좀 더 추론하기 쉽다.

 

명령형 프로그래밍이란?

코드로 원하는 결과를 달성해 나가는 과정에만 관심을 두는 프로그래밍 스타일이다.

- for/if 등을 사용하여 원하는 바를 구현하지만, 하는 일을 알기 위해 주석이 많이 요구된다. 

// 명령형 프로그래밍 스타일
var string = "This is the mid day show with Cheryl Waters"   
var urlFriendly = ""   
for (var i=0; i<string.length; i++) {     
  if (string[i] === " ") {       
     urlFriendly += "-"     
  } else {       
    urlFriendly += string[i]     
  }   
}   
urlFriendly = urlFriendly.toLowerCase()    console.log(urlFriendly)

// 선언적 프로그래밍 스타일
const string = "This is the mid day show with Cheryl Waters"   
const urlFriendly = string.replace(/ /g, "-").toLowerCase()   
console.log(urlFriendly)

함수형 프로그램밍의 핵심 개념

(1) 불변성(Immutable)

불변성이란?
함수형 프로그래밍에서는 데이터가 변할 수 없다. 불변성 데이터는 결코 바뀌지 않는다.
-> 데이터가 불변성을 가지고 있으므로, 원본 데이터 구조를 변경하는 대신 그 데이터 구조의 복사본을 만들되, 그중 일부를 변경하여 사용한다.
// 불변성 함수의 예시
Array변수.concat(data); // 원본 배열은 유지, 새로운 배열 반환
const addColor = (title, list) => list.concat({title})
const addColor(title, list) => [...list, {title}];

// 데이터를 변경하는 함수의 예시
Array변수.push(data); // 원본 배열에 새로운 원소를 추가하여 변동시킨다.

 

Object.assign()이란?
객체를 순차적으로, 합성해서 복사본을 반환해주는 함수이다.
스프레드 연산자(...)를 활용해 동일한 기능을 수행할 수 있다.
const person = { name: '홍길동', age: 20 }
Object.assign({}, person, {name: 'Hong'}); // return { name: 'Hong', age: 20}
const newPerson = {...person, name: 'Hong'} // 동일

쉽게 생각해서 Object.assign()이나, 스프레드 연산자(...)를 사용할 경우, 객체의 경우 {}을 생략하고, 배열의 경우 []을 생략하고, 합친 객체 또는 배열을 반환한다고 생각하면 된다.

 

(2) 순수함수

순수함수란?
파라미터에 의해서만 반환값이 결정되는 함수를 의미한다.
동일한 x에 대해서 항상 동일한 y를 반환하는 함수를 의미한다.
함수가 외부 객체를 변경시키지 않는다.

- 최소한 1개 이상의 인수를 받고, 인자가 같으면 항상 같은 값이나 함수를 반환해야 한다.

- 인수가 없는 함수는 거의 다 순수함수가 아니다.

- 인수가 없는 순수함수는 상수와 동일하므로, 의미가 없다.

순수함수는 인수를 변경불가능한 데이터로 취급한다.

- 인수로 넘겨받은 객체 변수의 필드값을 변경(불변성을 해치는 행위)해도 순수함수가 아니다.

- 순수함수를 사용하면, 어플리케이션의 상태에 영향을 주지 않으므로 코딩이 편해진다.

 

<1> 순수 함수는 파라미터를 최소 하나 이상 받아야 한다.

<2> 순수 함수는 값이나 다른 함수를 반환해야 한다.

<3> 순수 함수는 인자나 함수 밖에 있는 다른 변수를 변경하거나, 입력을 수행해서는 안된다.

 

리액트는 UI를 순수함수로 표현한다.
const header =  (props) => <h1>{props.title}</h1>;
// 동일한 props에 대해, 항상 동일한 UI Element를 반환한다.

(3) 데이터 변환

객체의 불변성을 유지하고, 순수함수를 사용하여 데이터를 변경하면 명령형 프로그래밍이 적어져, 복잡도도 감소한다.

** 함수형 자바스크립트를 위해 필요되는 배열 함수

Array.filter(필터 함수), Array.map(변환 함수) 함수란?
<<배열 -> 배열로 변환>> 해주는 함수이다.

 

const countries = ['korea', 'japan'];
// Array.join() : 원본 Array에 영향을 주지 않고, 인자로 받은 구분자를 기준으로, 원소를 합친 string을 반환
countries.join(', '); // return 'korea, japan'
// Array.filter() : 원본 Array에 영향을 주지 않고, 필터함수를 인자로 받아, 필터링된 배열을 반환 
countries.filter( element => element && element.startWith('k')); // return ['korea']
// Array.pop() : 원본 Array에 영향을 주고, 마지막 값을 반환한다. -> 순수함수가 아니므로, filter 함수 활용
// Array.splice() : 원본 Array에 영향을 주고, i~j 번째의 배열을 반환한다. -> 순수함수 아니므로, filter 함수 활용
// Array.map() : 변환함수를 인자로 받아, 모든 원소에 대하여, 동일한 변환 작업을 수행 후, 처리된 배열을 반환
n개의 원소를 가진 배열에서 n개의 원소를 가진 변환배열을 반환한다.
countries.map( element => ({ title: element}); // return [{title: 'korea'},{title: 'japan'}];
countries.map( (element, index) => ({ title: element}); // return [{title: 'korea'},{title: 'japan'}];
Object.keys() / Object.values() 문법이란?

<<객체 -> 배열로 변환>>해주는 함수이다.
Object.keys(객체)는 객체의 key값으로 이루어진 배열을 반환,
Object.values(객체)는 객체의 value값으로 이루어진 배열을 반환
const person = { name : '홍길동', age : 20};
Object.keys(person); // return ['name', 'age']
Object.values(person); // return ['홍길동', 20]
Object.keys(person).map(item => ({keyName: item}) // return [ {keyName: 'name'}, { keyName: 'age'}]

Array.reduce(), Array.reduceRight() : reduce(축약하다)의 의미대로, 배열을 축약해주는 함수이다.

<<배열 -> Number/String/Boolean/객체/함수 등 값으로 변환해야하는 경우>>

const ages = [21, 18, 32, 40, 53];
// reduce 함수 자체가 배열을 reduce(축약)하는 것이 목적이므로, 축적용 변수를 인자로 받는 함수를 넘겨주어야 한다.
ages.reduce( (축적용 변수, item) => {  do Something with item... return 축적용 변수 }, 축적용 변수 초기값 )
// 배열을 최대값으로 변환하는 예시
const maxAge = ages.reduce( (accumulatedVal, item) => item > accumulatedVal ? item : accumulatedVal, 0)
Array.reduceRight()는 Array.reduce와 동일하지만, 첫번째 원소부터가 아닌 맨 마지막 원소부터 축약을 시작한다는 차이점이 있다.

-> 축적용 변수의 초기값 형식 배열의 최종 축약값의 형태일 가능성이 높다.

// 배열 -> 객체 변환 예시
const colors = [ { id: '1', title: "빨강", rating: 11 }, { id: '2', title: "파랑", rating: 22 } ] const hashColors = colors.reduce( (hash, {id, title, rating}) => { hash[id] = {title, rating} return hash }, {} )
console.log(hashColors)
//{'1': {title:'빨강', rating: 11 }, '2': {title:'파랑', rating: 22}}

(4) 고차함수

고차함수란?

함수를 반환하는 함수 이다. 다른 함수를 인자로 받을 수 있거나, 함수를 반환할 수 있다.
다른 함수를 조작할 수 있는 함수이다.

// 다른 함수를 인자로 받는 Case

Array.map(변환함수), Array.filter(필터함수), Array.reduce(축적용변수를 활용한 축약함수)

const 고차함수 = userName => message => console.log(`${userName} : ${message}`);
// userName => ( message => console.log(`${userName} : ${message}`);)
// 함수를 반환하는 함수로 이해하면 되고, 맨 앞이 인자가 되고, 첫번째 화살표 바로 뒤에 소괄호를 붙여서
return 값이 함수가 된다는 것을 명시적으로 이해하도록 한다.

(5) 재귀

재귀란?
자기 자신을 호출하는 함수를 만드는 기법이다.
Loop를 모두 재귀로 바꿀 수 있다.

일부 Loop는 재귀로 표현하는 쪽이 더 쉽다.

// Loop의 i의 초기값처럼 함수 호출 시, i의 초기값을 입력으로 반드시 받는다.
const countdown = (input_value, input_function) => {
  // Loop 안에서 수행하는 메인 작업으로써, input_value(i)에 따라 값이 달라진다.
  input_function(input_value);
  // Loop가 어느 순간까지 반복 한계 조건이 있는 것처럼
  // 재귀함수 사용시, 항상 언제까지 재귀적으로 함수를 호출할지 함수의 Input_parameter에 대한 조건문을 명시 후, 자기 자신 함수를 호출해야한다.
  if (input_value > 0) {
    // Loop에서, i값만 변동하듯이, 나머지 Input 값은 그대로 넘겨주고 i값만 바꾼 상태로 호출한다.
    // Loop의 증감문처럼 input parameter에 대한 증감문이 명시되어야 한다.
    countdown(input_value-1, input_function)
  }
  // 아래와 같이 표현하는 이유는 삼항연산자를 사용하기 위해, 불필요한 return값을 반환하는 것이다.
  // return input_value > 0 ? countdown(input_value-1, input_function) : input_value;
}

// 10을 for문의 i의 초기값으로 이해하면 훨씬 쉽다.
countdown(10, value => console.log(value));
// 10 9 8 7 6 5 4 3 2 1 0

(6) 합성

합수형 프로그래밍 상에서 합성이란?

작은 순수함수를 서로 연쇄적으로 또는 병렬로 호출하거나 조합하여 더 큰 함수로 만드는 과정을 의미한다.

연쇄호출하는  Chaining 기법

const template = 'hh:mm';
const clockTime = template.replace('hh', '02').replace('mm', '30')
console.log(clockTime); // 02:30
compose(함수명1, 함수명2, 함수명3) 함수란?

A함수의 출력이 B함수의 입력이 되고, B함수의 출력이 C함수의 입력이 되는 경우,
가독성이 떨어지는 Nested로 표현하지 않고, 하나로 합쳐서 하나의 함수를 반환하는 합성함수이다.
// 기존 방식 : 이해하기 어렵고, 중간 변경이 어렵다.
const both = date => appendAMPM(civilianHours(date));

// compose 합성 함수 사용시 : 합성한 함수의 순서를 쉽게 바꿀 수 있다.
// compose는 고차 함수이다. 이 함수는 인자를 받아서 값을 하나 반환한다.
// 여러 함수명을 인자로 받아서, 하나의 합성 함수를 결과로 반환해준다.
// 입력 인자가 각각 하나일 때 제일 깔끔하다.
const both = compose(
  civilianHours,
  appendAMPM
);

(7) 함수형 프로그래밍 핵심 개념 정리

- 작업 방식 : 어플리케이션 로직을 더 작은 부분인 함수로 나눈다(함수는 한 가지 작업에 초점) -> 함수를 합성해서 더 큰 함수로 만들어 로직을 만든다.

- 함수형 프로그래밍은 가능하면 값보다는 함수를 활용하여야 한다.

- 함수형 프로그램밍은 원본 객체를 변화시키지 않고, 새 객체를 반환한다.(각 함수는 인자를 불변 객체로 다룬다.)

- 함수형 프로그래밍은 고차함수(함수를 반환하는)를 통해서, 재사용할 함수를 얻을 수 있다.

- 함수형 프로그래밍은 순수함수를 사용(같은 인자에 같은 반환값 반환하는)하므로, 전역변수에 영향을 받지 않아, 에러가 생겨도 함수 내부로 국한지을 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기