[JavaScript] 함수형 프로그래밍 (map, filter, reduce)

 

 

 

- 데이터

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
];

 

 

# map

const map = (f, iter) => {
  let res = [];
  for (const a of iter) {
    names.push(f(a));
  }
  return res;
}

함수의 인자로 받은 iter를 통해 순회를 하고 새로운 배열의 형태를 만든다. 첫번째 인자 f는 이터러블의 어떤 값을 수집할 것인지 추상화한다. 그렇기때문에 받아온 함수(f)를 통해서 어떤 값을 수집할 것인지 f 함수에게 위임한다.

 

let names = [];
for (const a of products){
  names.push(p.name);
}

console.log(names); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]
console.log(map(p => p.name, products)); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]


let prices = [];
for (const a of products){
  prices.push(p.price);
}
console.log(prices); // [15000, 20000, 15000, 30000, 25000];
console.log(map(p => p.price, products)); // [15000, 20000, 15000, 30000, 25000];

함수형 프로그래밍에선 map이라는 함수의 보조함수를 통해서 이터러블 안에 있는 수집하고자 하는 어떠한 값을 수집할 수 있다. map 함수는 함수를 값으로 다루면서 원하는 시점에 인자를 적용하는 고차함수이다.

 

 

# 이터러블 프로토콜을 따른 map의 다형성

console.log([1, 2, 3].map(a => a + 1)); // [2, 3, 4]
console.log(document.querySelectorAll('*')); // undefined

[1, 2, 3]은 배열(Array)이고 이터러블이다. 하지만 querySelectorAll(NodeList)은 이터러블이긴 하지만 배열은 아니므로 Array.prototype을 상속하고 있지 않다. 따라서 map함수를 사용할 수 없다.

 

console.log(map(el => el.nodeName, document.querySelectorAll('*')));

const it = document.querySelectorAll('*')[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

querySelectorAll은 이터러블이므로 [Symbol.iterator]() 메서드를 가지며 next()메서드로 값을 순회할 수 있다.

 

function* gen(){
  yield 2;
  if(false) yield 3;
  yield 4;
}

console.log(map(a => a * a, gen())); // [4, 16]

querySelectorAll 말고도 이터러블을 만족하는 것이라면 위에서 만들어놓은 map함수를 사용할 수 있다. gen()이라는 제너레이터 함수 또한 map으로 순회 가능하다. 이터러블 프로토콜을 따르는 이미 만들어져있는 이터러블에 map을 사용할 수 있지만 gen() 제너레이터 함수의 결과(문장)에 대해서도 map을 사용하여 순회할 수 있다. 그렇다는 것은 사실상 모든것들에 대해 map을 할 수 있다.

 

앞으로 만들어지는 WEB API 자바스크립트를 포함한 브라우저에서 사용되는 값들(querySelectorAll) 또한 ECMAScript의 이터러블 프로토콜을 따르고 있기때문에 계속해서 이러한 형태로 만들어질 것이다. 이터러블 프로토콜을 따르는 함수를 사용하는 것은 앞으로의 많은 다른 함수와의 조합성이 좋아진다. 훨씬 더 유연하고 다형성이 있는 기법을 사용할 수 있을것이다.

 

- Map

let m = new Map();
m.set('a', 10);
m.set('b', 20);

console.log(map(([k, a]) => [k, a * 2], m)); // [ ["a", 20], ["b", 40] ]
console.log(new Map(map(([k, a]) => [k, a * 2], m))); // { "a" => 20, "b" => 40 }

처음에 만든 Map객체와 동일하게 내부적으로 로직처리를 하고 난 후(*2)의 새로운 Map 객체를 map함수를 통해서 만들 수 있다.

 

 

# filter

const filter = (f, iter) => {
  let res = [];
  for(const a of iter){
    if (f(a)) res.push(a);
  }
  return res;
}

map과 형태는 동일하다. 하지만 첫번째 인자로 받는 함수(f)를 가지고 어떤 조건을 가지고 로직을 처리할 것인지에 대해 체크하는 부분을 받아온 함수에 위임한다.

 

let under20000 = [];
for (const a of products){
  if(p.price < 20000) under20000.push(p);
}
console.log(...under20000); // { name: "반팔티", price: 15000 } { name: "핸드폰케이스", price: 15000 }

console.log(...filter(p => p.price < 20000, products)); // { name: "반팔티", price: 15000 } { name: "핸드폰케이스", price: 15000 }

let over20000 = [];
for(const a of products){
 if(p.price >= 20000) over20000.push(p);
}
console.log(...over20000); // { name: "긴팔티", price: 20000 } { name: "후드티", price: 20000 } { name: "바지", price: 25000 }

console.log(...filter(p => p.price >= 20000, products); // { name: "긴팔티", price: 20000 } { name: "후드티", price: 20000 } { name: "바지", price: 25000 }

내부에 있는 값에대한 다형성은 첫번째 인자로 받은 보조함수를 통해서 지원을 해주고 외부의 경우는 두번 째 인자로 받은 이터러블 프로토콜을 따르는 인자를 통해서 다형성을 지원해줄 수 있다.

 

console.log(filter(n => n % 2, [1, 2, 3, 4])); // [1, 3]
console.log(fitler(n => n % 2, function* () {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}())); // [1, 3, 5]

filter함수 역시 이터러블 프로토콜을 따르고 함수형적인 코딩을 통해서 중복을 제거하고 구현할 수 있다.

 

 

# reduce

const reduce = (f, acc, iter) => {  
  for(const a of iter){
    acc = f(acc, a)
  }
  return acc;
}

reduce는 값을 축약하는 함수이다. 특정한 값들을 순회하면서 하나의 값으로 누적할 때 사용하는 함수이다. 

 

const add = (a, b) => a + b;

console.log(reduce(add, 0, [1, 2, 3, 4, 5]); // 15

// console.log(add(add(add(add(add(0, 1), 2), 3), 4), 5)); 형태로 실행된다.

reduce 함수는 첫번째 인자로 로직을 처리할 함수, 두번째 인자로 초기값, 세번째 인자로 이터러블을 받는다.

 

console.log(reduce(acc, [1, 2, 3, 4, 5]); // 15

초기 값을 전달하지 않아도 reduce 함수는 똑같은 값을 도출하는데 그 이유는

 

const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for(const a of iter){
    acc = f(acc, a)
  }
  return acc;
}

위의 if문의 로직과 같이 reduce 함수의 파라미터의 초기값을 누락하고 호출하게되면 reduce 내부의 로직에서 acc에 들어온 이터러블의 [Symbol.iterator] 메서드를 실행하고 초기 값에 iter 이터러블의 next()를 실행한 값을 넘겨주기때문에 초기값이 없더라도 같은 값으로 실행된다.

 

console.log(reduce(
  (total_price, product) => total_price + product.price, 
  0, 
  products)); // 105000

reduce는 보조함수를 통해서 축약하는 것을 완전히 위임하기 때문에 숫자나 숫자 배열 뿐만아니라 객체 등의 복잡한 값들을 특정한 값으로 축약할 수 있다. reduce도 역시 보조함수를 통해서 안쪽에 있는 값의 다형성을 지원해주며 이터러블을 통해서 외부값의 다형성도 지원한다.

 

 

# map, filter, reduce

const add = (a, b) => a + b;

console.log(
  reduce(
    add, 
    map(p => p.price, 
      filter(p => p.price < 20000, products))));
      
console.log(
  reduce(
    add, 
    filter(n => n >= 20000, 
      map(p => p.price, products))));

map과 filter, reduce 함수가 중첩되어있는 함수이다. 이렇게 작성된 함수들은 뒤부터 읽으면 쉽게 읽을 수 있다. 함수를 작성할 때 내부에서 사용하려는 함수와 외부에서 평가될 이터러블의 값을 유추하면서 작성한다.

 

 

[JavaScript] ES6 chap.3

 

 

 

Destructuring

* Array

// Array
var array = [2,3,4];
var a = array[0]; 
var b = array[1];
var c = array[2];

// ==
var [a,b,c] = [2,3,4];

// default 값
var [a,b,c = 5] = [2,3];

 

 

* Object

var { name : a, age : b } = { name : 'Kim', age : 30 }; // { a: 'Kim', b: 30 }

// key 생략
var { name, age } = { name : 'Kim', age : 30 }; // { name: 'Kim', age: 30 }

 

- 변수를 Object에 넣기

var name = 'Kim';
var age = 30;

var obj = { name : name, age : age } // { name: 'Kim', age: 30 }
// == //
var obj = { name, age } // { name: 'Kim', age: 30 }

 

 

* 함수

- Object

function 함수(name, age){
  console.log(name);
  console.log(age);
}

var obj = { name : 'Kim', age : 20 }
함수(obj.name, obj.age);


// destructuring
function 함수( { name, age }){
  console.log(name);
  console.log(age);
}

var obj = { name : 'Kim', age : 20 };
함수(obj);

 

 

- Array

function 함수( name, age ){
  console.log(name);
  console.log(age);
}

var array = [ 'Kim', 30 ];
함수(array[0], array[1]);


// destructuring
function 함수( [name, age] ){
  console.log(name);
  console.log(age);
}

var array = [ 'Kim', 30 ];
함수( ['Kim', 30] );

 

 

 

import & export

* default export

// import할 곳
<script type="module">
  import a from 'library.js';
  console.log(a);
</script>


// export할 곳
var a = 10;
export default a;

- export default는 한번만 사용이 가능하다.

- import 시에 변수명을 새롭게 작명이 가능하다.

 

 

* export

// import할 곳
<script type="module">
  import {a,b} from 'library.js';
  console.log(a);
</script>

// export할 곳
var a = 10;
var b = 20;
export {a, b};

- export는 {변수명1, 변수명2} 이렇게 사용해야하고 import 할때도 정확한 변수명을 써줘야한다.

 

 

* default export & export 동시사용

// import할 곳
<script type="module">
  import c, {a,b} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(c);
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

** default export로 설정한 변수가 export보다 먼저 와야한다.

 

 

* 알리아스(as)

// import할 곳
<script type="module">
  import c as 폭풍, {a as 간지, b as 폭발} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(폭풍); // 30
  console.log(간지); // 10
  console.log(폭발); // 20
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

as라는 키워드를 통해 새로운 변수명을 지정할 수 있다.

 

 

* 아스테리스크(*)

// import할 곳
<script type="module">
  import c, {* as 변수모음} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(a); // 10
  console.log(c); // 30
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

export 했던 모든 변수들을 지정할 수 있다.

** 그냥쓰면 안되고 꼭 as를 통해 변수명을 새로 지정해주어야 한다.

 

 

 

Stack & Queue

* Stack과 Queue의 동작

브라우저는 C++ 기반으로 짜여져 자바스크립트 코드를 발견하면 stack에 넣어 돌린다. 동기식 처리로 맨 윗줄부터 하나하나 실행시키는 공간이다. 하지만 ajax 요청이나 이벤트리스너, setTimeout 등의 코드를 만나게되면 처리하기까지의 시간이 오래 걸리기 때문에 Stack에서 제외하여 잠시 보류시킨다. 보류시키는 동안에 Stack에 있는 다른 처리들을 먼저 처리하고 보류되어있던 코드들은 실행 시점이 다 되었을 때 Queue에 집어 넣고 Queue에 있는 코드를 Stack에 옮겨서 처리하는데 Stack이 비어있을 때만 차례로 집어넣어 처리한다.

 

때문에 자바스크립트의 스택에 너무 무겁거나 많은 데이터가 쌓여있으면 Queue를 통해서 오는 ajax 요청이나 이벤트리스너 setTimeout 등의 코드를 실행 할 수 없다. (Stack이 10초간 실행중이면 해당 코드들은 실행 시점이 와도 실행이 되지 않음)

 

프로그램을 무겁게 돌려야 할 시

1. setTimeout을 통해 분할해서 실행한다.

2. Web worker를 이용한다.

 

 

 

 

 

Synchronous / Asynchronous

* Synchronous

- 동기식 처리: 한번에 코드 한줄씩 차례차례 실행

<script>
  console.log(1); 
  console.log(2); 
  console.log(3);
  // 1 2 3
</script>

 

 

- 비동기식 처리: 먼저 시작된 코드와 상관없이 새로운 코드를 실행

// python
print(1)
print(2)
time.sleep(1)
print(3)
// 1 2 1초쉬고 3

<script>

  console.log(1);
  setTimeout(function(){console.log(2);}, 1000);
  console.log(3);
  // 1 3 1초쉬고 2

</script>

 

 

브라우저는 비동기처리를 하는 코드들을 제쳐두고 원래 코드부터 실행한다. (ex. ajax, setTimeout, addEventListener)

 

코드를 실행하다가 ex에 해당하는 코드를 보게되면 Web API 대기실로 옮겨서 대기 시키다가 실행시점이 도달했을 때 코드를 꺼내서 실행시킨다. 때문에 시간이 오래 걸리는 코드들을 비동기식으로 처리할 수 있다.

 

 

* 콜백함수

- 함수 안에 들어가는 함수. 비동기 상황 등에서 순차적으로 실행하고 싶을 때 사용한다. 어떤 이벤트가 발생했거나 특정시점에 도달했을 때 시스템에서 호출.

function 첫째함수(){
  console.log(1)
}

function 둘째함수(){
  console.log(2)
}

function 셋째함수(){
  console.log(3);
}

첫째함수(); // 비동기 시 실행순서 보장 못함
둘째함수(); 


// 콜백함수
function 첫째함수(콜백){
  console.log(1);  
  콜백();
}

function 둘째함수(콜백){
  console.log(2);
  콜백();
}

function 셋째함수(){
  console.log(3);
}

첫째함수(function(){
  둘째함수(function(){
    셋째함수(function(){
      ...
    });
  });
}); // 1 2 3

다른 프로그래밍 언어라면 순차적으로 출력되겠지만 자바스크립트의 경우는 비동기식 처리 함수가 있을 시 Web API 대기실로 보내져서 나중에 실행된다.

 

 

- callback hell

첫째함수(function(){
  둘째함수(function(){
    셋째함수(function(){
      어쩌구..
    });
  });
}):

 

 

 

Promise

- 콜백함수의 한 디자인 패턴 (성공&실패 판정)

var 프로미스 = new Promise(function(성공, 실패){
  var 연산 = 1 + 1;
  성공(연산);
});

프로미스.then(function(결과){
  // Promise 성공 시 실행할 코드
  // 성공 파라미터를 통해 결과 값을 받아올 수 있음
}).catch(function(){
  // Promise 실패 시 실행할 코드
}).finally(function(){
  // Promise 결과와 상관없이 실행할 코드
});

성공하면 then(), 실패하면 catch()를 실행하는 코드를 짤 수 있다.

 

: 장점

 - 콜백함수 사용시 코드가 옆으로 길어지지 않아 보기 직관적이다.

 - 성공뿐만 아니라 실패 또는 결과와 상관없이 실행할 코드를 지정할 수 있다. (catch, finally)

 

 

* Promise 특징

1. Promise를 선언한 변수를 출력하면 대기<pending>, 성공<resolved>, 실패<rejected>의 3단계로 나뉜다. (한번 정해진 상태를 되돌릴 수는 없다.)

 

2. 동기를 비동기로 만드는 것이 아니라 콜백 함수를 지정하는 하나의 디자인 패턴이다. (Promise 안에 어려운 연산을 시키면 브라우저가 멈춤)

 

 

 

async / await

- Promise와 then을 쉽게 쓸수 있게 만들어주는 ES8 문법

 

* async

async function 연산 (){ // 이 함수 자체가 Promise
  return 1 + 1;
}

연산().then(function(결과){ // then을 사용할 수 있음
  console.log(결과); // 2
})

Promise를 알아서 생성해주기 때문에 함수를 실행할 때 then을 붙일 수 있다. 또한 async 함수 안에서 나온 결과를 then 안에서 파라미터로 받아서 사용할 수 있다.

 

 

* await

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    var 결과 = 1 + 1;
    성공();
  });
  어려운연산.then(function(result){
    console.log(result);
  });
}
더하기();

/////////// 같은 결과 /////////////

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    var 결과 = 1 + 1;
    성공();
  });
  var 결과 = await 어려운연산;
  console.log(결과);
}
더하기();

////////// async만 사용 했을 때 //////////

async function 더하기(){
  async function 어려운연산(){                
    return 1 + 1;
  }
  var result = await 어려운연산();
  console.log(result);
}
더하기();

단순히 then 대신에 await을 사용했다. await을 사용하면 문법이 훨씬 더 간단하다. 어려운연산 Promise를 기다린 다음에 완료되면 결과를 변수에 담는다. ( ** 비동기식 처리되는 코드를 담는다면 await을 기다리는 동안 브라우저가 잠깐 멈출수 있다.)

 

 

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  try {  
    var 결과 = await 어려운연산 // 성공했을 시 실행할 코드
  }
  catch { 
    // 어려운연산 Promise가 실패할 경우 실행할 코드 
  }
}

await은 Promise가 실패하면 에러가 나고 코드가 멈추기때문에 하단의 코드들은 더 이상 실행되지 않지만 그것을 방지하기 위해 try{} catch{}를 실행하면 된다. try 문에 성공했을 시 수행할 코드, catch 문엔 실패했을 시 수행할 코드를 넣으면 된다.

 

 

 

반복문
  • for 반복문 (공통)
  • forEach() 반복문 (Array 전용)
  • for in 반복문 (Object 전용)
  • for of 반복문 (iterable 전용)

 

* for in 반복문

- Object 자료형에 저장된 자료들을 꺼내고 싶을 때 사용

var 오브젝트 = { name : 'Kim', age : 30 };

for (var key in 오브젝트) { // 오브젝트의 자료 2개 = 2번 반복
  console.log(오브젝트[key]); // key는 오브젝트의 key
}

: 특징

 1. enumerable한 것만 출력한다.

var 오브젝트 = { name : 'Kim', age : 30 };

console.log( Object.getOwnPropertyDescriptor(오브젝트, 'name') ); // 오브젝트 속성
// {value: "Kim", writable: true, enumerable: true, configurable: true}
// enumberable은 셀수 있는지의 여부인데 object는 생성 시 true로 되어있음.

enumerable이 true로 체크 되어있어야 for in 문에서 거르지 않는다.

 

 

2. 부모의 prototype에 저장된 것도 출력한다.

class 부모 {

}
부모.prototype.name = 'Park';

var 오브젝트 = new 부모();

for (var key in 오브젝트) {
  console.log(오브젝트[key]); // Park
}

부모가 가지고 있는 prototype도 출력한다.

 

class 부모 {
 
}
부모.prototype.name = 'Park';

var 오브젝트 = new 부모();

for (var key in 오브젝트) {
  if (오브젝트.hasOwnProperty(key)) {
    console.log(오브젝트[key]);
  }
}

해당 오브젝트에 hasOwnProperty() 함수를 사용하면 오브젝트가 직접 가지고 있는지 true or false로 체크 하여 사용할 수 있다.

 

 

* for of 반복문

- 여러가지 자료형에 사용할 수 있는 반복문 (Array, 문자, arguments, NodeList, Map, Set 등)

var 어레이 = [2,3,4,5];
for (var 자료 of 어레이) {
  console.log(자료);
}

: 특징

1. iterable한 자료형들에만 사용 가능하다.

var 어레이 = [2,3,4,5];
console.log( 어레이[Symbol.iterator]() ); // ~~~ iterator {} 속성이 있음
// 위의 방법으로 콘솔 출력시 iterator속성이 있으면 iterable하다고 한다.

 

 

 

 

 

Symbol
var 심볼 = Symbol('설명아무거나적기');

- Object 자료형에 비밀스러운 key값을 부여하고싶을 때 사용한다.

 

 

var person = { name : 'Kim' };
person.weight = 100;

var weight = Symbol('내 진짜 몸무게');
person[weight] = 200;

console.log(person);

: 특징

- for문에 등장하지 않는다.

- 특이한 이름을 가진 자료를 Object안에 만들고 싶을 때 사용한다.

 

// 직접 입력
var height = Symbol('내 키임');
var person = { name : 'Kim', [height] : 160 };


// 심볼은 값은 같아도 서로 다른 Symbol
var a = Symbol('설명1');
var b = Symbol('설명1');
console.log(a === b); // false


// 전역 심볼
var a = Symbol.for('설명1');
var b = Symbol.for('설명1');
console.log(a === b); // true


// 기본 내장 Symbol
ar array = [2,3,4];
console.log(array[Symbol.iterator]); // Array iterator {}...

 

 

 

 

Map & Set

* Map

- 자료의 연관성을 위해 사용한다.

var person = new Map();
person.set('name', 'Kim');
person.set([1,2,3], 'Kim'); // key 값에 어떤 자료도 가능하다
person.set('age', 20);
// Map {"name" => "Kim", "age" => 20}

 

 

- 사용법

var person = new Map();
person.set('age', 20);

person.get('age'); //자료 꺼내는 법
person.delete('age'); //자료 삭제하는 법
person.size; //자료 몇갠지 알려줌

//Map자료 반복문 돌리기
for (var key of person.keys() ){
  console.log(key)
}

//자료를 직접 집어넣고 싶으면

var person = new Map([
  ['age', 20],
  ['name', 'Kim']
]);

Array 같은 자료형에 대량의 데이터가 있을시 Hash Map, Hash Table 등을 사용한다. Array 같은경우 필요한 데이터를 사용 시에 반복문을 돌려 일일이 출력해봐야 아는데, Hash Table은 미리 abc순으로 정렬되어 있어서 빠르게 찾을 수 있다. Hash Table은 자료에 key 값을 부여해놓고 정렬하는 식으로 구성되어있다. Object 자료형이랑 비슷하다. Object는 hasOwnProperty, toString 키들도 집어넣을수 있어서 너무 유연하고, key 값이 문자형태로 제한되어 들어올 수 밖에 없는 차이가 있다.

 

 

* Set

- 자료를 Array처럼 일렬로 저장할 수 있다. (***중복 제거)

var 출석부2 = new Set([ 'john' , 'tom', 'andy', 'tom' ]);

console.log(출석부2); // Set { "jhon", "tom", "andy"}

Set은 중복된 자료를 허용하지 않는다.

 

 

var 출석부2 = new Set([ 'john' , 'tom', 'andy', 'tom' ]);

출석부2.add('sally'); //자료더하기 
출석부2.has('tom'); //자료있는지 확인
출석부2.size;  //자료 몇갠지 세기

var 출석부 = [ 'john' , 'tom', 'andy', 'tom' ];

var 출석부2 = new Set(출석부); //Array를 Set으로 바꾸기

출석부 = [...출석부2]  //Set을 Array로 바꾸기

 

 

 

Web Components

- 여러개의 태그들을 하나의 단어로 축약해서 사용할 수 있다

<custom-input name="이름"></custom-input>
<custom-input name="비번"></custom-input>

<script>
  class 클래스 extends HTMLElement {

      connectedCallback() { // html 태그 사용 할 부분
        let name = this.getAttribute('name'); // attribute 추가 해서 사용 가능
        this.innerHTML = '<label>${name}을 입력하쇼</label><input>'
      }
      
      static get observedAttributes() {
        return ['name'] // 여러개면 [ 속성, 속성...] 이런식으로 사용
      }
      
      attributeChangedCallback() {
        // attribute 변경시 실행할 코드
      }
  }

  customElements.define("custom-input", 클래스); // 컴포넌트 등록
</script>

<custom-input> 같은 커스텀 태그를 컴포넌트라고 한다. 해당 기능은 React나 Vue에서 제공하는 자동 html 재렌더링 기능처럼 구현한 것이다. 

 

 

* Shadow DOM과 template 모듈화

- Shadow DOM

<div class="mordor"></div>
<script>
  document.querySelector('mordor').attachShadow({mode : 'open'}); // shadow 공간 만들기
  document.querySelector('mordor').shadowRoot.innerHTML = '<p>심연에서왔도다</p>' // html 사용
</script>

해당 방법으로 shadow DOM을 만들 수 있고 shadow DOM은 web component와 사용시 유용하다.

 

 

: web component 문제점

<custom-input></custom-input>
<label>왜 나까지 빨개짐?</label>

<script>

  class 클래스 extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `<label>이름을 입력하쇼</label><input>
        <style> label { color : red } </style>`
    }
  }

  customElements.define("custom-input", 클래스);
  
</script>

컴포넌트화할 때 스타일까지 포함하는 경우가 많은데 해당 컴포넌트를 제외한 다른 요소들까지 같이 적용될 수가 있다. 이럴때 스타일을 shadow DOM을 열어서 집어넣게 되면 shadow DOM안에 있는 스타일은 밖에까지 영향을 끼치지 않기때문에 html의 모듈화 개발이 가능하다.

 

 

- template 태그

해당 태그에 적힌 html은 렌더링 되지 않는다.

 <custom-input></custom-input>

  <template id="template1">
    <label>이메일을 입력하쇼</label><input>
    <style>label { color : red }</style>
  </template>

  <script>
    class 클래스 extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode : 'open'});
        this.shadowRoot.append(template1.content.cloneNode(true));
        let el = this.shadowRoot.querySelector('label');
        el.addEventListener('click', function(){ // 이벤트리스너 사용
          console.log('클릭함')
        })
      }
    }
    customElements.define("custom-input", 클래스);
  </script>

컴포넌트를 만들 때 html이 너무 길어지면 가독성이 떨어지니 template태그에 내용을 보관해두고 shodowRoot를 통해 집어 넣을 수 있다.

 

 

 

+ Recent posts