์ด ์ฝ๋๋ฉ์ DevFest WebTech 2019 ์ด๋ฒคํธ๋ฅผ ์ํด ๋ง๋ค์ด์ก์ต๋๋ค. ํด๋ง๋ค ์กฐ๊ธ์ฉ ๋ณํํ๊ณ ์๋ JavaScript์ ์๋ก์ด ๋ช
์ธ๋ฅผ ์์๋ณด๊ณ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉ ๋ฐ ํ์ฉํ ์ ์์์ง ํจ๊ป ์์๋ด
์๋ค!
CodeLab ๊ณผ์
์ด CodeLab์ ์๋์ ๊ฐ์ ์์๋ก ์งํ๋ฉ๋๋ค.
- ES6์ ES7์ ๋ํด์ ์์๋ณด๊ธฐ
- ES8, ES9, ES10์ ๋ํด์ ์์๋ณด๊ธฐ
- ES6~10์ ํ์ฉํด ๊ฐ๋จํ ๊ฒ์ ๊ธฐ๋ฅ ๋ง๋ค์ด๋ณด๊ธฐ feat. RxJS(option)
๊ฐ ์ธ์
์ ๋
๋ฆฝ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋ ๊ผญ ์์๋๋ก ๋ฃ์ง ์์ผ์
๋ ๋๊ณ , ์ํ๋ ๊ฒ๋ง ๋ค์ผ์
๋ ๋ฌด๋ฐฉํฉ๋๋ค.
ECMAScript?
ํ์ฌ ์ฐ๋ฆฌ๊ฐ ์ฐ๊ณ ์๋ JavaScript๋ ์ ๋ณด์ ํต์ ์์คํ
์ ์ํด ์กด์ฌํ๋ ๊ตญ์ ํ์คํ ๊ธฐ๊ตฌ Ecma International์ ECMA-262 ๊ธฐ์ ๊ท๊ฒฉ์ ์ ์ ๋ฐ ํ์คํ๋ ECMAScript์ BOM(Browser Object Model) ๊ทธ๋ฆฌ๊ณ DOM(Document Object Model) ์ด ์ธ ๊ฐ์ง๋ก ์ด๋ฃจ์ด์ ธ ์์ด์. ์ด ์ค ECMAScript๋ JavaScript์ ํ์ค ๊ท๊ฒฉ์ ๋ด๋นํ๊ณ 2015๋
๋ถํฐ ์ฝ 1๋
์ ์ฃผ๊ธฐ๋ก ๋ช
์ธ๊ฐ ์๋กญ๊ฒ ์ถ๊ฐ ๋ฐ ์์ ๋๋ฉด์ ์
๋ฐ์ดํธ ๋๊ณ ์์ด์.
ES6 & ES7์์ ๋ฌด์์ด ๋ฌ๋ผ์ก๋์?
ES6๋ถํฐ ํ์ฌ 2019๋
๋์ ๋์จ ES10๊น์ง ECMAScript์ ๋ฒ์ ์ค ES6์์ ๊ฐ์ฅ ๋ง์ ๋ณํ๊ฐ ์๊ฒผ์ด์. ์ฝ 20๊ฐ ์ด์์ ๋ช
์ธ๊ฐ ์ถ๊ฐ๋์๋๋ฐ ๊ทธ ์ค ๋ํ์ ์ธ ๋ณํ๋ฅผ ์์๋ณด๊ณ ํ๋ก๋ํธ์์ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๊ฒ๋ค์ ์ง์ ํ์ฉํด ๋ด
์๋ค.
ES6 โ http://www.ecma-international.org/ecma-262/6.0/
Arrow functions / Arrow function expressions
โ Regular function ๋์ ์ฌ์ฉํ ์ ์๋ ๋ฌธ๋ฒ์ ์ผ๋ก ๊ฐ์ํ๋ ํจ์
โ ์ฃผ์ํ ์ : ์ด Arrow function์ regular function ๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก
this
, arguments
, super
, new.target
keyword์ ๋ํ ๋ฐ์ธ๋ฉ์ด ๋์ง ์์ต๋๋ค.// ๋ ํจ์์ ์๊น์ // Regular Function function generalFunction(a, b) { // ... } // Arrow Function const arrowFunction = (a, b) => { // ... }
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
const cats = ['ebony', 'ivory', 'harmony', 'rhythm'] // Statement body cats.forEach((cat) => { console.log(cat) registerCat(cat) }) // Expression body const wrappedCats = cats.map((catName, index) => ({catName, age: index})) const olderCats = wrappedCats.filter((cat) => cat.age > 1) const catHarmony = olderCats.find((cat) => cat.catName === 'harmony')
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Classes
โ
Syntactical sugar over JavaScript's existing prototype-based inheritance
โ JavaScript์ ์๋ก์ด Object Oriented Pattern์ ์ ์ํ ๊ฑด ์๋์์!
โ ์ฃผ์ํ ์ : Class๋ Function declaration ์ฒ๋ผ hoisted ๋์ง ์์์.
class nameOfClass extends existingClass { constructor() { // ... } someFunction() { // ... } get someVariable() { // ... } set someVariable(variable) { // ... } static someStaticFunction() { // ... } }
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
class GDGEvent { constructor(eventName, eventDate) { this.eventName = eventName this.eventDate = eventDate } updateEvent(eventName, eventDate) { // ... } } class GDGWebTechEvent extends GDGEvent { constructor(eventName, eventDate, focusingTopic) { super(eventName, eventDate) this.focusingTopic = focusingTopic this.eventPlace = GDGWebTechEvent.defaultEventPlace() } updateEvent(eventName, eventDate, focusingTopic) { // do something with focusingTopic super.updateEvent(eventName, eventDate) } get eventInformation() { return `${this.eventName} will be held at ${this.eventDate} focusing on ${this.focusingTopic}` } static defaultEventPlace() { return 'Google for Startups located in Seoul, Korea' } } const devFestWebTech2019 = new GDGWebTechEvent( 'DevFest WebTech', '21 Nov 2019', 'web technologies' ) console.log(devFestWebTech2019.eventInformation) // 'DevFest WebTech will be held at 21 Nov 2019 focusing on web technologies'
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Enhanced Object Literals
โ
Object Literals
๊ฐ ์๋์ ๋ด์ฉ์ ์ง์ํ๋๋ก ํ์ฅ๋์์ด์.โ setting the prototype at construction
โ shorthand for foo: foo assignments
โ defining methods(+making super calls)
โ computing property names with expressions
const exampleObjectLiteral = { // __proto__ __proto__: theProtoObj, // Shorthand for โhandler: handlerโ handler, // Methods toString() { // Super calls return "d " + super.toString() }, // Computed (dynamic) property names [ 'prop_' + (() => 42)() ]: 42 }
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
const height = 30 const width = 60 const weight = '3kg' const color = 'ivory' const myCat = { height, width, weight, color, [`computed${height * width}`]: height * width }
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Template Strings
โ
string literals allowing embedded expressions
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Basic literal string creation const example1 = 'string '/n' text' // Multiline strings const example2 = `string text line 1 string text line 2` // String interpolation const exmaple3 = `string text ${example1} string text`
// Tagged templates const cat = 'Mambo' const age = 8 function simpleTag(strings, catExp, ageExp) { const firstString = strings[0] const secondString = strings[1] return `${firstString}${catExp}${secondString}${ageExp > 7 ? 'old' : 'not old'}` } const catString = simpleTag`My cat ${ cat } is ${ age }` console.log(catString)
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Destructuring
โ
binding using pattern matching
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
const exampleObject = { animal: { cat: ['Navy', 'Black', 'Yello'], dog: ['Joy', 'Sam'], }, car: { sedan: { bmw: ['528i', '530i'], mercedes: ['E300', 'S350D'], }, suv: { bmw: ['X3', 'X5'], mercedes: ['GLE', 'GLC'], } } } // list matching const [a, , b] = [1,2,3] // object matching const { animal: a, car: { sedan: s } } = exampleObject // object matching shorthand const {animal, car} = exampleObject // Can be used in parameter position function exampleFunction({name: awesomeName}) { console.log(awesomeName) } exampleFunction({name: 'Duke'}) // Fail-soft destructuring const [a] = [] a === undefined // Fail-soft destructuring with defaults const [a = 1] = [] a === 1
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
Default parameters & Rest parameters & Spread Operator
โ
Default parameters
: ํจ์์ ์ ์ธ๋ถ์์ ํ๋ฆฌ๋ฏธํฐ์ ๊ธฐ๋ณธ ๊ฐ์ ์ง์ ํ ์ ์์โ
Rest parameters
: ํจ์์ ์ ์ธ๋ถ์์ ํ๋ผ๋ฏธํฐ ์ผ๋ถ๋ฅผ ํ๋์ ๋ฐฐ์ด๋ก ์ ๋ถ ๋ฐ์โ
Spread Operator
: ๋ฐฐ์ด๊ณผ ๋ฌธ์์ด์ ์์๋ฅผ ์ ๋ถ ํ์ด ํจ์์ ์ธ์๋ ๋ฐฐ์ด์ ์์๋ก ํ์ฅํ ์ ์์์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Default parameters function interpretAge(age = 1) { if (age < 3) { return 'baby' } return '์ผ๋ฅธ' } console.log(interpretAge()) // baby console.log(interpretAge(1)) // baby console.log(interpretAge(5)) // ์ผ๋ฅธ // Rest parameters function organizeRoom(clothes, chairs, ...others) { console.log(clothes) // ['Jeans', 'Shirts'] console.log(chairs) // ['a', 'b'] console.log(others) // [['Book1', 'Book2'], ['Note1']] } organizeRoom(['Jeans', 'Shirts'], ['a', 'b'], ['Book1', 'Book2'], ['Note1']) // Spread Operator function sumNumbers(x, y, z, s) { return x + y + z + s } const numbers = [1, 2, 5, 6] sumNumbers(...numbers) // 14
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Let & Const
โ Block scope๊ฐ ์ ์ฉ๋๋ ๋ณ์
โ ์ฃผ์ํ ์ :
var
keyword๋ก ์ ์ธํ ๋ ์ฒ๋ผ let
๋ฐ const
๋ก ์ ์ธ๋๋ ๋ณ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก window ๊ฐ์ฒด์ ํ๋กํผํฐ๋ก ๋ฑ๋ก๋์ง ์๊ณ ๋ค๋ฅธ ๊ณต๊ฐ์ ๋ฑ๋ก๋ฉ๋๋ค. var๋ Object Environment Record, let๊ณผ const๋ Declarative Environment Record ๋ก์ ์๋ก ๋ค๋ฅด๊ฒ ์ทจ๊ธ๋ฉ๋๋ค.์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// ์ผ๋ฐ ํจ์์์์ ์ฐ์์ function f() { { let x { // ๋ค๋ฅธ ๋ธ๋ก ์ค์ฝํ๋ผ์ ๋ฌธ์ ์์ด ์คํ const x = 'sneaky' // ๊ฐ์ ๋ธ๋ก ์ค์ฝํ์์ const ๋ณ์๋ฅผ ์ฌํ ๋นํ๋ฏ๋ก error x = 'foo' } // ์ด๋ฏธ ๊ฐ์ ๋ธ๋ก ์ค์ฝํผ์ ๋์ผํ ์ด๋ฆ์ let ๋ณ์๊ฐ ์ ์ธ๋์ด ์์ด error let x = 'inner' } } // ๊ณ ์ ์ ์ด๋ฉด์๋ ์ ๋ช ํ ์ for (var i = 0; i < 10; i++) { setTimeout(() => { console.log(i) }, 3000) } // ์ด๋ฒ ๋ชจ๋ 10 // Versus for (let i = 0; i < 10; i++) { setTimeout(() => { console.log(i) }, 3000) } // 0 ~ 9
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Modules
โ
Language-level support for modules for component definition
โ ๋๋์ด ์ธ์ด ๋ ๋ฒจ์์ ์ง์๋๋ import, export ๊ตฌ๋ฌธ
โ ์ฃผ์ํ ์ : ์์ง ๋ธ๋ผ์ฐ์ ์์ ์์ ํ ์ง์ํ์ง๋ ์์์ script tag์ type ์์ฑ์
module
์ ์ง์ ํด์ฃผ์ด์ผ ํ๋ค.์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// math.js export const sum = (x, y) => x + y export const subtract = (x, y) => x - y export const multiply = (x, y) => x * y export const PI = 3.141593 export default { sum, subtract, multiply, PI, } // app.js import math from 'math' console.log('2ฯ = ' + math.sum(math.PI, math.PI)) // or import { sum, multiply, PI } from 'math' console.log(multiply(sum(3, 8), sum(2, 10)) / PI) // 42.01...
<script type="module" src="app.js"></script>
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Array API: from, of, fill, find ...
โ Array์ ์ฌ๋ฌ conversion helpers๊ฐ ์ถ๊ฐ๋์์ด์!
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Array-like DOM Nodes๋ฅผ ์ค์ ๋ฐฐ์ด ํ์ ์ผ๋ก ๋ํ Array.from(document.querySelectorAll('*')) Array.of(1, 2, 3) // 7๋ก 1๋ฒ์งธ๋ถํฐ ์ฑ์ฐ๊ธฐ [0, 0, 0].fill(7, 1) // [0, 7, 7] // ํน์ ์์ ๋ฐ ์ธ๋ฑ์ค ์ฐพ๊ธฐ [1, 2, 3].find(x => x === 3) // 3 [1, 2, 3].findIndex(x => x === 2) // 1
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Promise
โ JavaScript์ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์ผ๊ด์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๋ค๋ฃฐ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์คํ์์ค๋ก ๊ด๋ฆฌ๋๊ณ ์๋ค๊ฐ ES6์ ์ด๋ฅด๋ฌ์ JavaScript ๋ช
์ธ๋ก ์ถ๊ฐ๋์์ด์.
new Promise((resolve, reject) => { ... })
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// ํ์ด๋จธ function timeout(duration = 0) { return new Promise((resolve) => { setTimeout(resolve, duration); }) } const promiseExample = timeout(1000) .then(() => { return timeout(2000) }) .then(() => { throw new Error('hmm') }) .catch((error) => { Promise.all([timeout(100), timeout(200)]) return '๋ง์ด ๊ธฐ๋ค๋ ธ๋ค..' }) // API๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ fetch('https://awesome.com/api/cats', { method: 'GET' }) .then((response) => response.json()) .then(({status, data}) => { // ... })
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
ES7 โ http://www.ecma-international.org/ecma-262/7.0/
Array.prototype.includes
โ ๊ธฐ์กด์
indexOf
๋ ํน์ ์์๊ฐ ์๋์ง ๊ฒ์ฌํ๊ณ 0 ๋๋ 1์ ๋ฐํํ์ง๋ง, includes
๋ ๋์ผํ๊ฒ ์์๋ฅผ ๊ฒ์ฌํ๊ณ boolean์ ๋ฐํํด์. ๊ทธ๋์ number type์ boolean type์ผ๋ก conversion ํ๋ ์ผ์ ์ํ๊ฒ ๋์์ด์.์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// arrayVariable.includes(valueToFind[, fromIndex]) [1, 2, 3].includes(-1) // false [1, 2, 3].includes(1) // true [1, 2, 3].includes(3, 4) // false [1, 2, 3].includes(3, 3) // false [1, 2, NaN].includes(NaN) // true ['foo', 'bar', 'quux'].includes('foo') // true ['foo', 'bar', 'quux'].includes('norf') // false
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Arithmetic Operators: Exponentiation(**)
โ ์๋ฅผ ์ ๊ณฑํ๋ Operator๊ฐ ์ถ๊ฐ๋์์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// 5์ ์ธ์ ๊ณฑ์ ๊ตฌํ ๋ ๊ธฐ์กด์ ๋ฐฉ๋ฒ Math.pow(5, 3) // ์ด์ ๋ operand ** operand ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์์ด์ 5 ** 3 // 125
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
ES8 & ES9์์ ๋ฌด์์ด ๋ฌ๋ผ์ก๋์?
ES8 โ http://www.ecma-international.org/ecma-262/8.0/
Async function
โ async๋ฅผ ๋ถ์ด๋ ๊ฒ์ ์ ์ธํ๊ณ ๋ Regular function๊ณผ ๊ตฌ์กฐ์ ์ผ๋ก ๋์ผํ์ง๋ง ์๋ฌต์ ์ผ๋ก Async function์ Promise๋ฅผ ๋ฐํํ๊ณ , ์ด ํจ์ ๋ด์์๋ await operator๋ก Promise ํน์ Thenable objects๋ฅผ ๊ธฐ๋ค๋ ธ๋ค๊ฐ fulfilled ๋ ๊ฐ์ ๋ฐ์ ์ ์์ด์.
async function asynchronousFunction() { return 3 } const resultAsPromise = asynchronousFunction()
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ async function getFive() { return 5 } getFive().then((value) => { console.log(value) }) // or async function getFive() { return 5 } async function waitForFive() { const value = await getFive() console.log(value) } waitForFive() // Thenable objects๋ฅผ ๊ธฐ๋ค๋ฆด๋ async function waitForThenable() { const thenable = { then: function(resolve, reject) { resolve('resolved!') } } console.log(await thenable) // resolved! } waitForThenable() // API๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ // Promise๋ก ํ์๋(ES6์์์ ์์ ์ ๋์ผ) fetch('https://awesome.com/api/cats', { method: 'GET' }) .then((response) => response.json()) .then(({status, data}) => { // ... }) // Async function๊ณผ await์ ์ฌ์ฉํ์๋ async function apiHandler() { const response = await fetch('https://awesome.com/api/cats', { method: 'GET' }) const { data } = await response.json() console.log(data) } apiHandler()
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Object entries, values
โ Object์ enumerable property pair๋ฅผ ๊บผ๋ด์ค๊ฑฐ๋ value๋ง ๋ฐฐ์ด๋ก ๊บผ๋ด์ฌ ์ ์์ด์.
Object.entries(objectVariable) Object.values(objectVariable)
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Object.entries() const obj = { eventName: 'DevFest WebTech', eventPlace: 'Google for Startups' } console.log(Object.entries(obj)) // [ ['eventName', 'DevFest WebTech'], ['eventPlace', 'Google for Startups'] ] // Object.values() const obj = { foo: 'foo', bar: [100, 200], baz: 55 } console.log(Object.values(obj)) // ['foo', [100, 200], 55 ] const myStr = 'Lufthansa' console.log(Object.values(myStr)) // ["L", "u", "f", "t", "h", "a", "n", "s", "a"]
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Trailing commas in function declarations and calls
โ Trailing commas๋ฅผ ํจ์ ์ ์ธ๋ถ์ ํธ์ถ๋ถ์์๋ ์ฌ์ฉํ ์ ์๊ฒ ๋์์ด์.
โ ์ด ๋ฌธ๋ฒ์ผ๋ก ์ด์ ๋ Array, Object, Function ๋ฑ์ ํ์ฉํจ์ ์์ด์ ์ค์ ํ๋ก๋ํธ๋ฅผ ๋ง๋ค๋ ์์ฃผ ํ๋ ์ด๊ฑฐํ ๊ฐ ๋ณต์ฌ๋ฅผ Syntax Error๋ Code Convention(ESLint ํน์ Prettier์ ์ฌ์ฉํ๋ค๋ฉด) ๋ฑ์ ๊ฑฑ์ ํ์ง ์๊ณ ํ ์ ์๊ฒ ๋์์ด์!
// ES5์์๋ legalํ ๋ฌธ๋ฒ์ด์๋ trailing commas in objects const arr = [ 1, 2, 3, ]; const exampleObject = { foo: "bar", baz: "qwerty", age: 42, }; // ์ด์ ๋ ํจ์์ ์ ์ธ๋ถ์ ํธ์ถ๋ถ์์๋ legal! const awesomeFunction = ( a, b, c, d, ) => a + b + c + d const a = 3 const b = 22 const c = 1 const d = 5 awesomeFunction(a, b, c, d,)
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
ES9 โ http://www.ecma-international.org/ecma-262/9.0/
Object rest & spread
โ ์์ ์ค๋ช
ํ๋ Array rest์ spread ์ฐ์ฐ์๊ฐ Object์๋ ์ฌ์ฉํ ์ ์๊ฒ ๋์์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Object rest const { fname, lname, ...rest } = { fname: 'Hemanth', lname: 'HM', location: 'Earth', type: 'Human', } fname // "Hemanth" lname // "HM" rest // { location: "Earth", type: "Human" } // Object spread const rest = { location: 'Earth', type: 'Human', } const info = { fname, lname, ...rest } info // { fname: "Hemanth", lname: "HM", location: "Earth", type: "Human" }
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Promise.prototype.finally
โ ์์ ์ค๋ช
ํ๋ Promise์ then, catch ๋ค์์ผ๋ก finally ๊ตฌ๋ฌธ์ ์คํํ ์ ์๊ฒ ๋์์ด์. ์ด์ then๊ณผ catch์์ ํ๋ clean up ์์
์ finally ๊ตฌ๋ฌธ์์ ์ผ๊ด์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
function testFinally() { return new Promise((resolve,reject) => resolve()) } testFinally() .then(() => console.debug('resolved')) .finally(() => console.debug('finally')) // resolved // finally
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
ES10 โ http://www.ecma-international.org/ecma-262/10.0/
Array flat & flatMap
โ Array์ ๋ฐฐ์ด์ ๋ฐฐ์ด์ flat ์ํฌ ์ ์๋ conversion helper๊ฐ ์ถ๊ฐ ๋์์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// Array flat let arr = ['a', 'b', ['c', 'd']] let flattened = arr.flat() console.log(flattened) // ["a", "b", "c", "d"] arr = ['a', , , 'b', ['c', 'd']]; flattened = arr.flat() console.log(flattened) // ["a", "b", "c", "d"] arr = [10, [20, [30]]] console.log(arr.flat()) // [10, 20, [30]] console.log(arr.flat(1)) // [10, 20, [30]] console.log(arr.flat(2)) // [10, 20, 30] // Array flatMap const arr = [4.25, 19.99, 25.5] console.log(arr.map((value) => [Math.round(value)])) // [[4], [20], [26]] console.log(arr.flatMap((value) => [Math.round(value)])) // [4, 20, 26]
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Object fromEntries
โ ์ด์ ์ ์ค๋ช
ํ์๋ Object์ entries()๋ก ๋ฆฌํฐ๋ด ๊ฐ์ฒด์ property pair๋ฅผ ๋ฐฐ์ด๋ก ๊ฐ์ ธ์ฌ ์ ์์์ด์. ๊ทธ๋ฐ๋ฐ ๋ฐ๋๋ก ๊ทธ ๋ฐฐ์ด๋ก ๋ฆฌํฐ๋ด ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ ํจ์๊ฐ ์๊ฒผ์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
const myArray = [['one', 1], ['two', 2], ['three', 3]] const obj = Object.fromEntries(myArray) console.log(obj) // {one: 1, two: 2, three: 3}
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
String trimStart & trimEnd
โ ์ฌ์ค ์ด String์ ํจ์๋ค์ ๊ฐ๊ฐ trimLeft์ trimRight์ ๊ฐ์์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
const str = ' string ' // ES10 console.log(str.trimStart()) // "string " console.log(str.trimEnd()) // " string" // the same as console.log(str.trimLeft()) // "string " console.log(str.trimRight()) // " string"
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
Optional catch binding
โ ์ด์ catch binding์ ์๋ตํ ์ ์๊ฒ ๋์์ด์.
์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์?
// ์ด์ ์ด๋ ๊ฒ catch๊ฐ ์ด๋ค Error ๊ฐ์ฒด๋ฅผ ๋ฐ๋์ง ์๋ตํ๊ณ ํ์ํ ์์ ์ ์ํํ ์ ์์ด์! try { // ์๋ฌ๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ ์์ ๋ค์ ์ํ } catch { // ์๋ฌ์ ์ข ๋ฅ์ ์๊ด์์ด ํ์ํ ์์ ์ ์ํ }
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
๊ฐ๋จํ ๊ฒ์ ๊ธฐ๋ฅ ๋ง๋ค์ด๋ณด๊ธฐ feat. RxJS
์ค๋น๋ฌผ
- Editor(or IDE)
- npm
- Browser(Chrome์ ๊ถ์ฅํฉ๋๋ค)
ย
๋ฌด์์ ๋ง๋๋์?
์ด๋ฒ ์ธ์
์๋ ์์ ์ดํด๋ณด์๋ ES6๋ถํฐ 10๊น์ง ์ผ๋ถ ๊ธฐ๋ฅ์ ์ด์ฉํ๊ณ ๊ณต๊ฐ ๋์ด์๋ Public API(์์ ๋ ์ํผ API)๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ ๊ฒ์ UI๋ฅผ ๋ง๋ค์ด ๋ณผ๊ฑฐ์์. ๋จผ์ Vanilla JS๋ก ๋ง๋ค์ด๋ณด๊ณ , ๊ทธ ๋ค์ RxJS๋ฅผ ํ์ฉํด ์ฝ๋๋ฅผ ์ปจ๋ฒํ
ํด๋ณผ๊ฒ์.
ย
์ด๋ป๊ฒ ๋ฐ๋ผํ๋ฉด ๋๋์?
์ด๋ฒ ์ธ์
์ ์์ ์ธ์
๊ณผ ๋ฌ๋ฆฌ API ํธ์ถ์ ํ๊ณ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ npm์ผ๋ก ๊ฐ์ ธ์ ์น์ ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ HTML, CSS, JS ํ์ผ์ ํ๋์ฉ ๋ง๋ค๊ณ , JS ํ์ผ์ webpack์ผ๋ก ๋น๋ํด์ index.html์์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ผ๋ก ์งํํ๊ฒ ๋ ๊ฑฐ์์. ๋๋ต์ ์ธ ๊ตฌ์กฐ๋ ์๋์ ๊ฐ์ต๋๋ค.
- ํ๋ก์ ํธ ํด๋์ index.html, app.js, app.css ํ์ผ์ด ์๋ค.
- webpack์ผ๋ก app.js ํ์ผ์ ๋น๋ํด์ index.html์์ ๋ถ๋ฌ์ ์ฌ์ฉํ๋ค.
webpack -p --watch ./*.js
- index.html์ ๋ก์ปฌ์์ ์๋นํ๋ค.
- Ex)
npx http-server
- ๋ธ๋ผ์ฐ์ ์์ ๋ก์ปฌํธ์คํธ๋ก ์ ์ํด ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ธํ๋ค.
ย
๋จผ์ ํ๋ก์ ํธ๋ฅผ ์ํ ํด๋๋ฅผ ์ํ๋ ์ด๋ฆ์ผ๋ก ๋ง๋ค์ด์ฃผ์ธ์.
mkdir awesome-search cd awesome-search
๊ทธ๋ฆฌ๊ณ ํ๋ก์ ํธ ํด๋ ์์์ ํจํค์ง ๊ด๋ฆฌ๋ฅผ ์ํด์ npm init์ ํด์ฃผ์ธ์.
npm init
๊ฒ์ ๊ธฐ๋ฅ์ ๋ง๋ค๊ธฐ ์ํด์ lodash๋ฅผ ์ฌ์ฉํด๋ณผ๊ฒ์. npm์ผ๋ก lodash๋ฅผ ์ค์นํด์ฃผ์ธ์. ์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ง๋ค๊ณ ๋ฐ๋ผํ์
๋ ๋ฉ๋๋ค!
npm install lodash-es --save
ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ์ผ๋ ์ด์ HTML ํ์ผ์ ๋ง๋ค์ด๋ณผ๊ฒ์.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="./app.css"> <title>DevFest WebTech 2019 CodeLab</title> </head> <body> <div class="container"> <header> Find your recipes! </header> <main> <input class="searchInput" type="text"> <ul class="recipeList"></ul> </main> </div> <script src=""></script> </body> </html>
* { box-sizing: border-box; } html, body { margin: 0; padding: 0; } .container { display: flex; flex-direction: column; align-items: center; min-width: 100vw; min-height: 100vh; margin-top: 20px; } header { display: flex; width: 450px; height: 50px; font-size: 30px; font-weight: bolder; border: 2px solid seagreen; align-items: center; justify-content: center; } .searchInput { margin-top: 30px; width: 450px; height: 40px; font-size: 20px; font-weight: 400; padding: 0 15px; border: 2px solid darkorange; } .searchInput:focus { outline: none; } .recipeList { display: flex; flex-direction: column; width: 450px; list-style: none; padding: 5px; height: 500px; overflow-y: scroll; border: 2px solid seagreen; } .recipe { display: flex; flex-direction: row; min-height: 300px; width: 100%; border-bottom: 2px solid gold; margin-bottom: 4px; } .recipeColumn { width: 270px; padding-left: 10px; } .recipeInstructions { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
๋ญ๊ฐ ์ด๋ผํด ๋ณด์ด์ฃ ? CSS๋ก ์ํ๋๋๋ก ๋์์ธ ํด์ฃผ์ธ์! ํน์ ์ด CSS๋ฅผ ์ฌ์ฉํ์
๋ ๋ฉ๋๋ค. ์ด์ JS ํ์ผ์ ๋ง๋ญ์๋ค. ์ด๋ฆ์ ์๋ฌด๋ ๊ฒ ๋ง๋ค์ด์ฃผ์ธ์.
// app.js const searchInput = document.querySelector('.searchInput') const recipeList = document.querySelector('.recipeList') const inputHandler = (event) => { // ... } searchInput.addEventListener('input', inputHandler)
์ฐ๋ฆฌ์ ๋ชฉํ๋ input tag์์ ๋ฌธ์์ด์ ์
๋ ฅ๋ฐ์ ์ํ๋ ํ์ด๋ฐ์ API๋ฅผ ํธ์ถํ๊ณ , ์๋ต ๊ฒฐ๊ณผ๋ฅผ ul tag์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด๋ ์ด๊ธฐ์ ์๋ง ์ด๋ฐ ๋ชจ์ต์ ํ๊ณ ์๊ฒ ์ฃ ?
๊ทธ๋ฐ๋ฐ ๋งค๋ฒ input tag์์ ํค๊ฐ ์
๋ ฅ๋ ๋๋ง๋ค ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ํธ์ถ์ด ๋ ๊ฑฐ์์. ์ด ์ฝ๋ฐฑ ํจ์ ์์์ API ํธ์ถ์ ํ ํ
๋ฐ ํค๊ฐ ์
๋ ฅ๋ ๋๋ง๋ค ํธ์ถ์ด ์ผ์ด๋๋ฉด Network IO๊ฐ ๋๋ฌด ์ง๋์น๊ฒ ์ผ์ด๋๋ ๋ญ๋น๋ฅผ ์๋์ฒ๋ผ ์ค์ฌ๋ณผ๊ฒ์.
import { debounce } from 'lodash-es' const debouncedInputHandler = debounce((event) => { // ... }, 1000)
์ด๋ ๊ฒ ๊ธฐ์กด์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ lodash์ debounce์ ๋ฃ์ด์ฃผ๋ฉด, debounce๋ ๋ด๋ถ์ ์ผ๋ก setTimeout์ ํ์ฉํด ๋๋ฒ์งธ ์ธ์๋ก ๋์ด์จ ๋ฐ๋ฆฌ์ธ์ปจ๋ ๋งํผ์ ์๊ฐ ์์์ ๊ฐ์ฅ ๋ง์ง๋ง์ ๋ฐ์ํ event๋ฅผ ๋ฐํํ๋ ์๋ก์ด ํจ์๋ฅผ ๋ฐํํด์. ๋ค์ ๋งํด, ์ด ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์ด์ ํค๋ณด๋๋ฅผ ์ฐ์์ผ๋ก ํ์ดํ ํ์๋ 1์ด ๋ฏธ๋ง์ ๊ฐ๊ฒฉ์ผ๋ก ํ์ดํ๋ผ์ ๋์ด์ค๋ event๋ ๋ฌด์ํ๊ฒ ๋ ๊ฑฐ์์.
์, ์ด์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ํ์ด๋ฐ์ API๋ฅผ ํธ์ถํ ์ ์๊ฒ ๋์์ผ๋ API ํธ์ถ์ ํด๋ณผ๊น์?
fetch('some url', { method: 'GET', }) .then((response) => ...)
๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ๋์ด ์๋ fetch ํจ์๋ฅผ ์ฌ์ฉํ์ด์. ๊ธฐ์กด์ ์๋ XMLHttpRequest๊ณผ๋ ํํ์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ์ฌ๋ญ ๋ค๋ฅผ ๊ฑฐ์์. fetch๋ ๋น๋๊ธฐ์ ์ผ๋ก ์๋ํ๊ณ Promise๋ฅผ ๋ฐํํด์. ๊ทธ๋์ ์ด fetch๋
async await
๊ณผ๋ ํจ๊ป ์ฌ์ฉํ ์ ์์ด์.์ด์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์์์ผ๋ ๋ฆฌ์คํธ ํํ๋ก ๊ทธ๋ ค๋ด
์๋ค.
const apiEndPoint = 'https://www.themealdb.com/api/json/v1/1/search.php' fetch(`${apiEndPoint}?f=${inputValue}`, { method: 'GET', }) .then((response) => response.json()) .then(({ meals }) => { recipeList.innerHTML = meals .map((meal) => ...) })
์ด๋ meals ๋ฐฐ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก DOM์ผ๋ก ๋ฐ๊ฟ์ ๊ทธ๋ ค์ผ ํ๋๋ฐ, ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ ๊ฒ ๊ฐ์์. JSON์ html string์ผ๋ก ์ ํํด์ฃผ๋ ์ ํธ ํจ์๋ฅผ ๋ง๋ค์ด์ map์ ์ ์ฉํด๋ ๋ ๊ฒ ๊ฐ๊ณ , ์ง์ ๊ทธ ๋ด์ฉ์ ์ฝ๋ฐฑ ํจ์์ ์ ์ด๋ ๋ ๊ฒ ๊ฐ์์. ์ด๋ฒ์๋ ์ด meal ํ๋๋ฅผ ํํํ Class๋ฅผ ์๋ก์ด meal.js ๋ผ๋ ํ์ผ์ ๋ง๋ค์ด์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋ ค๋ณผ๊ฒ์. ๋ง๋๋ ๋ฐฉ๋ฒ์ ์์ ์ด๋ ๊ทธ๋ฅ ๋ณด๊ธฐ๋ง ํ์
๋ ๋ฉ๋๋ค!
class Meal { constructor(mealData) { this.mealData = mealData } renderToString() { const { strArea, strCategory, strIngredient1, strMeasure1, strInstructions, strMeal, strMealThumb, strTags, } = this.mealData return ` <li class="recipe"> <div> <img width="130px" height="130px" src=${strMealThumb} > </div> <div class="recipeColumn"> <span>${strArea}</span> <span>${strCategory}</span> <br /> <span>${strMeal}</span> <br /><br /> Ingredient List <ul> <li>${strIngredient1}: ${strMeasure1}</li> </ul> <br /> Instruction <div class="recipeInstructions">${strInstructions}</div> <br /><br /> <span>${strTags}</span> </div> </li> ` } } export default Meal
์ด Meal ํด๋์ค๋ ์ธ์คํด์ค๋ก ๋ง๋ค์ด์ง๋ ์์ ์ ํํํ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ , ๋์ค์ ์์ ์ด ๊ฐ๊ณ ์๋ ๋ฐ์ดํฐ๋ก ํ๋ฉด์ ๊ทธ๋ ค์ง ์ ์๋๋ก ๋ด๋ถ์ ์ผ๋ก renderToString ์ด๋ผ๋ ํจ์๋ฅผ ๊ฐ๊ณ ์์ด์. ์ ๋ง ๊ฐ๋จํ ํด๋์ค๋ค์! ์ด์ ์ด ํด๋์ค๋ฅผ ํ์ฉํด์ ์๊น์ fetch ํจ์์์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋ ค๋ณผ๊ฒ์. ์! ์ด ํด๋์ค๋ meal.js ๋ผ๋ ํ์ผ์ ์์ฑ๋์ด ์์ผ๋ app.js์์ ์ด ํ์ผ์ ๊ฐ์ ธ์์ผ๊ฒ ์ฃ ?
import Meal from './meal'
Meal ํด๋์ค๋ default๋ก export ๋์ด์์ผ๋ ์ด๋ ๊ฒ ๊ฐ์ ธ์ค๋ฉด ๋ ๊ฑฐ์์.
์, ์ด์ ์ ๋ง ๊ทธ๋ ค๋ด
์๋ค.
fetch(`${apiEndPoint}?f=${inputValue}`, { method: 'GET', }) .then((response) => response.json()) .then(({ meals }) => { recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') })
์ด๋ ๊ฒ ๋ณด๋ new ํค์๋๋ก ์ธ์คํด์คํ ํ ๋ฟ, ํจ์๋ก ์์ฑํ๋ค๊ณ ํ์๋์ ํฌ๊ฒ ๋ฌ๋ผ๋ณด์ด์ง๋ ์์ฃ ? ์ค์ํ ๊ฑด, ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ์น ์ ํ๋ฆฌ์ผ์ด์
์์ ์ฃผ๋ก ๋ค๋ฃฐ ๋ฐ์ดํฐ๋ฅผ ํํํ๊ณ , ์ด ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ๋ค๋ค์ ธ์ผ ํ๋์ง์ ๋ํด์ ์ฝ๋๋ก ๋ช
์ํ๋ค๋ ์ ์ด์์. ๊ทธ๊ฒ์ ํจ์๋ก ํด๋ ๋๊ณ , ํด๋์ค๋ก ํด๋ ๋ผ์. ๋ฌผ๋ก ์ ๋ ํจ์๋ฅผ ์ ํธํฉ๋๋ค..
ย
์ ์ด์ ์ฌ๊ธฐ๊น์ง ํ์ผ๋ฉด input tag์ ํ์ดํ์ ํ๋ค๊ฐ ๋ฉ์ถ๋ฉด ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ํ๋ฉด์ ๊ทธ๋ ค์ง ๊ฑฐ์์! ๊ทธ๋ฐ๋ฐ, ๋ง์ฝ apple pie๋ก ๊ฒ์ํ๋ค๊ฐ, ํ์ดํ์ ๋ค์ ๋ง ํ๋๋ฐ ๊ฒฐ๊ตญ ์ต์ข
์
๋ ฅ ๊ฐ์ apple pie๋ก ์ด์ ๊ณผ ๋๊ฐ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ์ง๊ธ์ ๋๊ฐ์ query๋ก API๊ฐ ํธ์ถ๋ ๊ฑฐ์์. ๋ญ๊ฐ ๋ญ๋น๊ฐ ์๊ธด ๊ฒ ๊ฐ์์. ์ด์ ์ ๊ฒ์ํ๋ ๊ฐ์ ์ด๋๊ฐ์ ์ ์ฅํด ๋์๋ค๊ฐ ๋ฐ๋์์๋๋ง API๋ฅผ ํธ์ถํด๋ด
์๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ฌด๊ฒ๋ ์
๋ ฅ๋์ง ์์ ๊ฒฝ์ฐ์๋ API๊ฐ ํธ์ถ๋์ง ์๋๋ก ๋ง์๋ด
์๋ค.
let previousInputValue = '' const debouncedInputHandler = debounce((event) => { if (!inputValue || previousInputValue === inputValue) { return } fetch(`${apiEndPoint}?f=${inputValue}`, { method: 'GET', }) .then((response) => response.json()) .then(({ meals }) => { recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') }) previousInputValue = inputValue }, 1000)
์, ์ด๋ ๊ฒ ํ๋ฉด ์ด์ ๊ฐ์ด ์๊ฑฐ๋, ์ด์ ์ ๊ฒ์ํ๋ ๊ฐ์ ๊ฑด๋๋ฐ๊ฒ ๋์์ด์. ๋ญ๊ฐ ์กฐ๊ธ์ ๊ฐ์ ๋ ๊ฒ ๊ฐ์์!
๊ทธ๋ฐ๋ฐ ๋ง์ฝ API๊ฐ ์ด๋ค ์ด์ ๋ก ์๋ฌ๋ฅผ ๋ด๊ฒ ๋๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ์ฐ๋ฆฌ์ ์ฝ๋๋ ์์ง ์๋ฌ๋ฅผ ๋ฐ์๋ค์ผ ์ค๋น๊ฐ ๋์ง ์์์ด์. ๋คํํ๋ ์ฐ๋ฆฌ๋ Promise๋ฅผ ๋ฐํํ๋ fetch๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์๋์ฒ๋ผ ํธ๋ค๋ง ํ ์ ์์ ๊ฑฐ์์. ๋ฌผ๋ก try catch ๊ตฌ๋ฌธ์ ์ฌ์ฉํด๋ ๋ฉ๋๋ค.
.then(({ meals }) => { recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') }) .catch(() => { recipeList.innerHTML = '<h3>An error has occurred when fetching data...</h3>' })
ํ , ๊ทธ๋ฐ๋ฐ ๊ฒ์ API๊ฐ ๋์ฐฉํ๊ธฐ ์ ๊น์ง๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋ฉ๋๊ณ ์๋ค๊ณ ํ์ํด์ฃผ๋ฉด ์ข์ ๊ฒ ๊ฐ์์. ๊ทธ ํ์๋ ์ธ์ ํด์ผ ํ ๊น์? API๋ฅผ ํธ์ถํ์๋ง์ ๋ณด์ฌ์ฃผ๊ณ , API๊ฐ ์ ๋์ฐฉํ๊ฑฐ๋ ์คํจํ ์ดํ์ ์๋ง์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ๋์ง ์์๊น์?
recipeList.innerHTML = '<h3>Loading...</h3>' fetch(`${apiEndPoint}?f=${inputValue}`, { method: 'GET', }) .then(({ meals }) => { recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') }) .catch(() => { recipeList.innerHTML = '<h3>An error has occurred when fetching data...</h3>' })
์ด๋ฏธ API๊ฐ ์ ๋์ฐฉํ ์ํฉ๊ณผ ์คํจํ ์ํฉ ๋ชจ๋๋ฅผ then๊ณผ catch์์ ์ก์ ์ ์ ํ ํ์๋ฅผ ํด์ฃผ๊ณ ์์ด์ ์ฐ๋ฆฌ๋ ๊ทธ๋ฅ fetch ํ๊ธฐ ์ง์ ์ ๋ฐ๋ก Loading Indicator๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ๋ผ์!
์ ๊ทธ๋ผ ์ด์ Loading Indicator๋ ์๊ณ ์๋ฌ๋ ํธ๋ค๋งํ ์ ์์ด์ ๋ ๊ตฌ์์ด ๊ฐ์ถ์ด์ก์ด์. Promise๋ฅผ ๋ง๋ณด์์ผ๋ ์ด์ ์ธ์
์์ ๋ณด์๋ async await๋ฅผ ์ฌ์ฉํด๋ด
์๋ค.
const debouncedInputHandler = debounce(async ({ target: { value: inputValue } }) => { if (!inputValue || previousInputValue === inputValue) { return } recipeList.innerHTML = '<h3>Loading...</h3>' try { const response = await fetch(`${apiEndPoint}?f=${inputValue}`, { method: 'GET', }) const { meals } = await response.json() recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') } catch { recipeList.innerHTML = '<h3>An error has occurred when fetching data...</h3>' } previousInputValue = inputValue }, 1000)
async await๋ก ๋ง๋ ๋์ผํ ๋ฒ์ ์ ์ฝ๋์์. ์ด์ ๊ณผ ๋ฌ๋ผ์ง ์ ์ debounce์ ๋๊ธฐ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ฝ๋ฐฑ ํจ์๊ฐ async๋ก ์ ์ธ๋์ด ์๋ค๋ ๊ฒ, Promise๋ฅผ ๋ฐํํ๋ ์ฝ๋ ์์ await๊ฐ ์ฐ์ฌ ๋ง์น ๋๊ธฐ์ ์ธ ์ฝ๋๋ก ๋ณด์ด๋ ๊ฒ, Promise๋ฅผ ๋ฐํํ๋ ์ฝ๋ ์ ์ฒด๋ฅผ try catch ๊ตฌ๋ฌธ์ด ๊ฐ์ธ๊ณ ์๋ ๊ฒ์ด์์. ์๋ฌ๋ ์ฌ๊ธฐ์ ์ก์๋ ๋๊ณ ๋ ์์ ๊ณต๊ฐ์์ ์ก์๋ ๊ด์ฐฎ์์. ๊ทธ๊ฒ์ ์ฑ์ ์ด๋ป๊ฒ ์ค๊ณํ๋๋์ ๋ฌ๋ ค์๋ต๋๋ค.
ย
์, ์ด์ Vanilla JS๋ก๋ ๋ค ๋ง๋ค์ด ๋ณด์์ผ๋ ์ด๋ฒ์ RxJS๋ฅผ ํ์ฉํด์ ๋์ผํ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค์ด ๋ด
์๋ค. ์์ํ๊ธฐ์ ์์, ๋จผ์ RxJS์์ ์ฃผ๋ก ์ฌ์ฉ๋๋ ๊ฐ๋
์ ๋ํด์ ์์ฃผ ๊ฐ๋จํ๊ฒ ์์๋ณผ๊ฒ์.
ย
Observable
์ฐ๋ฆฌ๊ฐ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค๋ ๋ฐ์ดํฐ๋ API์์๋ ์ค๊ณ , ์ฌ๋ฌ DOM tag์ ๋ฌ๋ฆฐ ์ด๋ฒคํธ ๋ฆฌ์ค๋์์๋ ์ค๊ฒ ์ฃ ? ํ์ง๋ง ์ธ์ ์ฌ์ง๋ ์๋ฌด๋ ๋ชจ๋ฅด์ฃ . ๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ Browser์
๋ฑ๋ก
์ ํด์ ์ด๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋์ง Browser๊ฐ ๋์ ์ฐ๋ฆฌ๊ฐ ํ์ํ ์์
(์ฝ๋ฐฑ ํจ์)์ ์คํํด ์ฃผ์ฃ . API๋ ๋ง์ฐฌ๊ฐ์ง์์. ์ธ์ ์ฌ์ง ๋ชจ๋ฅด๋ ๋น๋๊ธฐ์ฑ ํธ์ถ์ ๋์ฐฉํ์๋ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํด Promise๋ก API๋ฅผ ํธ๋ค๋ง ํ์ด์.RxJS๋ ์ด๋ฐ ๋ชจ๋ ์ข
๋ฅ์ ๋ฐ์ดํฐ์ ํ๋ฆ์ ํํํด์ฃผ๋ ๊ฐ์ฒด๋๋๋ค. ์ ํํ๋
์๊ฐ์ ์ถ์ผ๋ก ์ฐ์์ ์ธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์๋ ๊ฐ์ฒด
์
๋๋ค. ์ฐ๋ฆฌ๊ฐ input tag์ awesome์ด๋ผ๊ณ ํ์ดํ์ ํ๋ฉด a โ aw โ awe โ awes โ aweso โ awesom โ awesome ์์ผ๋ก ๋ฐ์ดํฐ๊ฐ ๋์ด์ต๋๋ค. API๋ค๋ ํธ์ถํ ์์์ ๋ฐ๋ผ ์ปจํธ๋กคํด์ผ ํ๋ ํ์์ฑ์ด ์์ฃ ? Observable์ ์ด๋ ๊ฒ ์๊ฐ์์ผ๋ก ์ค๋ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํ๋ค๊ณ ์ดํดํ์๋ฉด ๋ฉ๋๋ค. Operator
๊ฐ์ฅ ์ค์ํ Observable์ ์ดํดํ์ผ๋ ์ด๊ฑด ์ข ๋ ์ฝ์ต๋๋ค. ์ด๋ ๊ฒ ์๊ฐ์์ผ๋ก ์ฐ์์ ์ผ๋ก ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ฆฌ๋ ๊ทธ๋๋ก ์ฌ์ฉํ๊ธฐ๋ ํ์ง๋ง ๊ณ์ฐ์ ํ๊ฑฐ๋ log๋ฅผ ๋จ๊ธฐ๊ธฐ๋ ํ์ฃ . ์ด๋ ๊ฒ ๋ฐ์ดํฐ์ ํ๋ฆ ์ฌ์ด ์ฌ์ด์ ์ด๋ค ํน์ ํ
์์
์ ํด์ผํ๋๋ฐ, ์ด ์์
์ ํด์ค ์ ์๊ฒ ํด์ฃผ๋ ๋
์์ด ๋ฐ๋ก Operator ์์(Operator๋ Observable์ ๋ง๋ค์ด๋ด๊ธฐ๋ ํฉ๋๋ค). ์ข ๋ ์ ํํ๋, ์ด Operator๋ Observable์ ๋ฐ์ ์ด๋ค ์์
์ ํ๊ณ ์๋ก์ด Observable์ ๋ค๋ก ๋๊น๋๋ค. ๊ทธ๋ผ ๋ค์ Operator๊ฐ ๋ฐ๊ฒ ์ฃ !Observer
์ด๋ ๊ฒ Observable๊ณผ Operator๋ก ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ํ๋ฌ์ ์ด๋ป๊ฒ ๋ณํ๋์ง๋ฅผ ํํํ์ผ๋, ์ด ๋ฐ์ดํฐ๋ฅผ ๊ฒฐ๊ตญ์ ์ด๋๊ฐ์์ ๊บผ๋ด์ ํ์ฉ์ ํด์ผ๊ฒ ์ฃ ? ๊ทธ ํ์ฉ์ ํ๋ ๋
์์ด ๋ฐ๋ก Observer์์. ๊ทธ๋์ ๊ด์ฐฐ์์ธ๊ฑฐ์ฃ ! ๋ค์ ์ฝ๋๋ก ๋ณด๋ฉด ๋ ์ฝ๊ฒ ์ดํด๊ฐ ๊ฐ๊ฒ ์ง๋ง, ๋ ์ ํํ ์ค๋ช
ํ๋ฉด Observer๋ next, error, complete ํจ์๋ฅผ ํ๋กํผํฐ๋ก ๊ฐ๋ ๊ฐ์ฒด๋ฅผ ์๋ฏธํด์. ์ด ๊ฐ์ฒด๋ฅผ
observableInstance.subscribe()
์ด๋ ๊ฒ subscribe ํจ์ ์์ ๋ฃ์ด์ฃผ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ด์ค๊ธฐ ์์ํด์.Subscription
Observer๊ฐ ๋ฐ์ดํฐ๋ฅผ
observableInstance.subscribe()
๋ก ๊บผ๋ด์ค๊ธฐ ์์ํ์ด์. ๊ทธ๋ฐ๋ฐ ์ด๋ ๊ฒ ์ธ์ ์ฌ์ง ๋ชจ๋ฅด๋ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ด์จ๋ค๋ ๊ฒ์ RxJS๊ฐ Browser ์ด๋๊ฐ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๋ฌ์๋จ๋ค๋ ๊ฒ์ ์๋ฏธํ๊ฒ ์ฃ ? ์ฆ, ๋ฉ๋ชจ๋ฆฌ ์์์ด ์ฌ์ฉ๋์๋ค๋ ๊ฑด๋ฐ, ๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ์ง ์์์ผ ํ๋ ์๊ฐ์ด ์ค๋ฉด(ํ์ด์ง๋ฅผ ์ด๋ํ๋ ๋ฑ) ์ฌ์ฉํ ๋ฉ๋ชจ๋ฆฌ ์์์ ๋ค์ ๋ฐํํด์ผ ํด์. ๊ทธ๋ด ๋๋ฅผ ์ํด Observable ์ธ์คํด์ค์ subscribe ํจ์๋ subscription ๊ฐ์ฒด๋ฅผ ๋ฐํํด์. ์ด ๊ฐ์ฒด๋ฅผ ์ด์ฉํด subscription.unsubscribe()
์ ๊ฐ์ด ์ฌ์ฉํ ๋ฉ๋ชจ๋ฆฌ ์์์ ํด์ ํ ์ ์์ด์.ย
์ด์ธ์๋ RxJS์๋ Subject, Scheduler ๋ฑ ๊ฐ๋
์ด ๋ ์์ง๋ง ์ง๊ธ์ ์ด 4๊ฐ๋ง์ผ๋ก ์ถฉ๋ถํฉ๋๋ค! ์ด ์ฝ๋๋ฉ์ ํตํด Reactive Programming๊ณผ RxJS์ ๋ํด ๊ด์ฌ์ ๊ฐ๊ฒ ๋์
จ๋ค๋ฉด ์ ๋ ๊ธฐ์ฉ๋๋ค! ์ผํธ
๊ด์ฌ์ด ์์ผ์ ๋ถ์ ์๋ ๋ ๋งํฌ์์ ๋ ๋ง์ด ์์๋ณด์๋ฉด ์ข๊ฒ ์ด์!
ย
๊ฐ๋
์ ์์๋ณด์์ผ๋ Vanilla JS๋ก ๋ง๋ค์๋ ์ฝ๋๋ฅผ ์ปจ๋ฒํ
ํด๋ด
์๋ค. ๋ฌด์๋ถํฐ ํด์ผํ ๊น์? ์ด์ ๋ ์์์ ์ค๋ช
ํ๋ RxJS์ ์ฃผ์ ๊ฐ๋
์ ์ด์ฉํด์ ๋ฐ์ดํฐ์ ํ๋ฆ์ ํํํด์ผ๊ฒ ์ฃ ?
const inputStream = fromEvent(searchInput, 'input')
fromEvent
๋ผ๋ ํจ์๋ DOM EventTarget๊ณผ ์ด ํ๊ฒ์์ ๋ฐ์ดํฐ๋ก ์ผ์ ์ด๋ฒคํธ๋ช
์ ์ธ์๋ก ๋ฐ๊ณ , ๊ทธ ์ด๋ฒคํธ๋ช
์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ผ๋ก ํํํ๋ Observable ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. ๊ทธ๋์ ์ ์ฝ๋์ ๊ฐ์ด Observable์ inputStream์ด๋ผ๊ณ ์ด๋ฆ์ ์ง์์ด์. ๊ทธ๋ผ ์ด์ ์ด ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ ์ ํ ๋ณ๊ฒฝํด์ API ํธ์ถ์ ํ๊ณ , ์๋ต์ ๋ฐ์ ๊ณ์ฐํ๊ณ , ๊ณ์ฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ ๋๋ง์ ํ๋ ์์
์ ํด๋ณผ๊ฒ์.const inputStream = fromEvent(searchInput, 'input') .pipe( ... )
fromEvent๊ฐ Observable์ ๋ฐํํ๋ค๊ณ ํ์ฃ ? Observable ๊ฐ์ฒด์๋
pipe
๋ผ๋ ํจ์๊ฐ ์์ด์. ์ด ํจ์๋ฅผ ํตํด์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด ์ด๋ป๊ฒ ๋ณํํ๋์ง๋ฅผ ํํํ ๊ฑฐ์์. ๊ทธ๋ฆฌ๊ณ ์ด ํจ์๋ ๋ค์ ์๋ก์ด Observable ๊ฐ์ฒด๋ฅผ ๋ฐํํด์. ๊ทธ๋์ ์ต์ข
์ ์ผ๋ก inputStream์๋ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด ์ด๋ป๊ฒ ๋ณํํ๋์ง๋ฅผ ๋ชจ๋ ํํํ๊ณ ์๋ ์๋ก์ด Observable ๊ฐ์ฒด๊ฐ ํ ๋น๋ ๊ฑฐ์์. ์ด์ pipe ์์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ๋ณํ๋ฅผ ํํํด๋ด
์๋ค.const inputStream = fromEvent(searchInput, 'input') .pipe( map((event) => event.target.value), debounceTime(1000), distinctUntilChanged(), tap(() => recipeList.innerHTML = '<h3>Loading...</h3>'), switchMap((inputValue) => ajax(`${apiEndPoint}?f=${inputValue}`, { method: 'GET' }) .pipe( map(({ response }) => response ? response.meals : []), ), ), )
์ฌ๊ธฐ์ ์ฌ์ฉ๋ ํจ์๋ค(RxJS์์ ์์ฃผ ์ฌ์ฉ๋๊ธฐ๋ ํ๋)์ ๋ํด์ ๊ฐ๋จํ๊ฒ ์๊ฐ๋ฅผ ํด๋๋ฆด๊ฒ์.
map์ ์ด๋ฆ ๊ทธ๋๋ก ์ด๋ค ๊ฐ์ ๋ฐ์์ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋งคํ์ ํด์ฃผ๋ ์ญํ ์ ํด์. ์ฆ, a๋ผ๋ ๊ฐ์ ๋ฐ์์ ๊ณ์ฐ์ ํ ๋ค์ b๋ผ๋ ๊ฐ์ ๋ฐํํด์.
debounceTime์ ์ฐ๋ฆฌ๊ฐ Vanilla JS๋ก ๋ง๋ค์๋ ์ฝ๋์์ lodash์ debounce์ ๊ฐ์ ์ญํ ์ ํ๋ ํจ์์์. ์ฃผ์ด์ง ๋ฐ๋ฆฌ์ธ์ปจ๋๋งํผ ๋๋ฐ์ด์ฑ์ ํฉ๋๋ค.
distinctUntilChanged๋ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ฝ๋์์ previousInputValue๋ก ์ด์ ๊ฐ๊ณผ ์ ๊ฐ์ด ๊ฐ์ผ๋ฉด API ํธ์ถ์ ๋ง์๋ ์ญํ ์ ํด์. map, debounceTime ์ ๊ฑฐ์ณ ๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ๋๋ฐ ์ด์ ๊ฐ๊ณผ ๊ฐ๋ค๋ฉด ๋ค์ Operator๋ก ๋์ด๊ฐ์ง ์๊ฒ ๋ง๋ค์ด์!
tap์
๊ฐ๋ณ๊ฒ ๋๋๋ฆฌ๋ค
๋ผ๋ ๋ป์ธ๋ฐ, ์ฌ๊ธฐ์๋ ๋์ผํ ์๋ฏธ๋ก ์ฌ์ฉ๋ผ์. ์ด tap์ map๊ณผ ๋ฌ๋ฆฌ ๋์ฐฉํ ๋ฐ์ดํฐ์ ์ด๋ ํ ๋ณ๊ฒฝ๋ ํ์ง ์์์. ๊ทธ๋ฅ ๋ฐ์ดํฐ๊ฐ tap์ ๋์ฐฉํ๋ฉด ๋ฐ์ดํฐ์ ์ํฅ์ ์ฃผ์ง ์๋ ์ด๋ค ์์
์ ์ํํ๊ณ ๋ค์ Operator๋ก ๋ฐ์ดํฐ๊ฐ ๋์ด๊ฐ์. ๊ทธ๋์ ์ด tap์์ log๋ฅผ ๋จ๊ธฐ๊ฑฐ๋ ํ ์ ์์ด์.์ง๊ธ ์ฐ๋ฆฌ๋ searchInput์ด ๋ด๋ณด๋ด๋
input
์ด๋ฒคํธ๋ฅผ Observable๋ก ํํํ๊ณ ์์ด์. ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ์ ๋ชฉํ๋ ์ด input ๊ฐ์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์์ ์ API ํธ์ถ์ ํด์ผํ์ฃ ? ์์ ๋งํ๋ฏ์ด API๋ฅผ ํธ์ถํ๊ณ ๊ทธ ์๋ต์ ๋ฐ๋ ๊ฒ๋ ํ๋์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด์์. RxJS์์๋ Observable ์์์ ์๋ก์ด Observable์ ํํํ ์ ์์ด์. ๊ทธ๋ฌ๋๊น [input tag์ ๊ฐ์ด ํ๋ฌ์ API๋ฅผ ํธ์ถํ๋] ํํ์ธ๊ฑฐ์ฃ . ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ๋ ์ด๋ฐ ์ค์ฒฉ๋ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ๊ตฌ์กฐ์ ์๊ด์์ด [input tag์ ๊ฐ์ด ํ๋ฌ์ API๋ฅผ ํธ์ถํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๊ณ ] ์ถ์ด์. ๊ทธ๋์ ์ด๋ ๊ฒ ๋ฐ๊นฅ Observable์์ ๋ด๋ถ Observable์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํ ๋ mergeMap์ด๋ผ๋ Operator๋ฅผ ์ฌ์ฉํด์. ๊ทธ๋ฐ๋ฐ ๋ง์ฝ apple pie๋ก ๊ฒ์ํ API๊ฐ ์์ง ๋์ฐฉํ์ง ์์๋๋ฐ peacan pie๋ผ๋ ๊ฐ์ผ๋ก ์๋ก API๋ฅผ ํธ์ถํ๋ฉด ์ด์ API๋ ๋ฌด์ํ๊ฑฐ๋ ์ทจ์๋ฅผ ํด์ผ๊ฒ ์ฃ ? mergeMap์ ์ญํ ์ ํ๋ฉด์ ์ ๊ฐ์ด ๋ค์ด์ค๋ฉด ์ด์ ์์
์ ์ทจ์ํด์ฃผ๋ Operator๊ฐ switchMap์ด์์!ajax๋ ์ด๋ฆ์์๋ ์ ์ ์๋ฏ์ด API ์์ฒญ์ ํ๋ ํจ์์์! ๊ทธ๋ฆฌ๊ณ ์์ ๋งํ๋ฏ์ด ์ด ๋
์์ด API ๋ฐ์ดํฐ์ ํ๋ฆ์ ํํํ๋ Observable์ ๋ฐํํด์.
์, ์ด์ input tag์์ ์ค๋ ๊ฐ์ ๋ฐ์ API๋ฅผ ํธ์ถํ๊ณ , ์๋ต ๊ฒฐ๊ณผ๋ฅผ ์ ์ ํ๊ฒ ๊ฐ์ ธ์ค๋ ๊ฒ๊น์ง Observable๊ณผ Operator๋ฅผ ์ฌ์ฉํด ํํํ์ด์. ์ด์ ์ด ๋ฐ์ดํฐ ์คํธ๋ฆผ์์ ์ค์ ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋ ๋๋ง์ ํด์ผํฉ๋๋ค.
inputStream.subscribe({ ... })
์ด๋ ๊ฒ inputStream Observable ๊ฐ์ฒด์
subscribe
ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ์์ํด์! ๊ทธ๋ฐ๋ฐ ์ด subscribe ํจ์๋ next, error, complete ๋ผ๋ ํจ์ ํ๋กํผํฐ๋ฅผ ๊ฐ๋ ๊ฐ์ฒด๋ฅผ ๋ฐ๊ฑฐ๋ ์ด ๊ฐ๊ฐ์ ํจ์ ์ธ๊ฐ๋ฅผ ์ฐ๋ฌ์ ๋ฐ์์. next๋ ๋ค์ ๊ฐ์ด ์ฌ๋๋ง๋ค ํธ์ถ์ด ๋๊ณ , error๋ ์ด ๋ฐ์ดํฐ ์คํธ๋ฆผ ๊ณผ์ ์ ์๋ฌ๊ฐ ์์๋ ํธ์ถ์ด ๋๊ณ , complete๋ Observable์ ๊ตฌ๋
์ด ์๋ฃ๋์์๋ ํธ์ถ์ด ๋ฉ๋๋ค. ์ฐ๋ฆฌ๋ ๋ ์ํผ๋ฅผ next์์ ๋ฐ์ ๋ ๋๋ง ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ next ํจ์๋ฅผ ์์ฑํด๋ณผ๊ฒ์. ๊ทธ๋ฆฌ๊ณ ์๋ฌ๊ฐ ๋ฌ์๋๋ ์ ์ ํ๊ฒ ํํ์ ํด๋ด
์๋ค.inputStream.subscribe({ next: (meals) => { recipeList.innerHTML = meals .map((meal) => new Meal(meal)) .map((mealInstance) => mealInstance.renderToString()) .join('') }, error: () => { recipeList.innerHTML = '<h3>An error has occurred when fetching data...</h3>' }, })
next ํจ์ ๋ฐ๋์ ๋ด์ฉ์ด ์ด์ ์ฝ๋์ ๋์ผํ์ฃ ? ๋ธ๋ผ์ฐ์ ๋ก ํ์ธํด๋ณด๋ฉด ์ด์ ์ฝ๋์ ๋์ผํ๊ฒ ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ด์.
ย
๊ฐ๋จํ ์ฝ๋๋ฅผ RxJS๋ฅผ ์ด์ฉํด ๋ค์ ํํ์ ํด๋ณด์๋๋ฐ, ์ ํ๋ฆฌ์ผ์ด์
์์ ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ํ๋ฅด๊ณ , ์ด๋ค ์์ ์ ์ด๋ค ๋์์ ํด์ผํ๋์ง ์ดํดํ๊ณ ์๋ค๋ฉด RxJS๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ์ ํ๋ฆ์ ๊ด๋ฆฌํ๋ ๊ฒ๋ ์ข์ ๊ฒ ๊ฐ์ง ์๋์? ์ด๋ฏธ react + redux + redux-observable์ ์ฌ์ฉํ๊ณ ๊ณ์ ๋ถ๋ค์ ๋ ์ฒด๊ฐํ์ค ์๋ ์์ ๊ฒ ๊ฐ๋ค์! ์ด์, DevFest WebTech 2019 CodeLab ์ด์์ต๋๋ค!
ย
์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฌธ์
ย