리액트는 예전에 여름방학 때 한번 다뤄보고 공부했지만, 너무 어려웠다. 그래서 복습겸 난이도가 조금 낮은 다른 react 책을 사서 공부하려고 한다.
그때 배웠던건 수준이 좀 높았던듯.. 이책은 조금 더 쉬울려나?
공부할 책 : Doit! 리액트 프로그래밍 정석
일단 비쥬얼 스튜디오랑 yarn 설치하는건 동일하고..
아래 플러그인을 설치한다.
Reactjs code snippets 플러그인
Prettier 플러그인
리액트에 필요한 자바스크립트 ES6의 주요 문법을 공부해 보자!
기존 자바스크립트의 문자열 사용 방법
var cart = {name:'도서',price:1500};
var getTotal = function(cart){
return cart.price + '원';
};
var myCart = '장바구니에 ' + cart.name + '가 있습니다. 총 금액은 ' + getTotal(cart) + '입니다.';
ES6의 문자열 사용 방법
작은따음표 대신 백틱으로 템플릿 문자열을 도입했다.
또한 템플릿 문자열에 특수기호 $을 사용하여 변수 또는 식을 포함할 수 있다.
var getTotal = function(cart){
return `${cart.price}원`;
};
var myCart = `장바구니에 ${cart.name} 가 있습니다.
총 금액은 ${getTotal(cart)}입니다.`
문법이 Jquery랑 비슷한 것 같기도 한디 기분탓인가
전개 연산자는 나열형 자료를 추출하거나 연결할 때 사용한다. 사용 방법은 배열이나 객체, 변수명 앞에 마침표 세 개(...)를 입력한다. 다만 배열, 객체, 함수 인자 표현식 안에서만 사용해야 한다.
ES6 예제 :
var objectOne = { one: 1, two: 2, other: 0 };
var objectTwo = { three: 3, four: 4, other: -1 };
var combined = {
...objectOne,
...objectTwo,
};
// combined = { one: 1, two: 2, three: 3, four: 4, other: -1}
var combined = {
...objectTwo,
...objectOne,
};
// combined = { one: 1, two: 2, three: 3, four: 4, other: 0}
var { other, ...others } = combined;
// others = { one: 1, two: 2, three: 3, four: 4}
기존 자바스크립트 문법은 변수 선언에 var 키워드를 사용했지만
ES6에서는 값을 수정할 수 있는 가변 변수를 위한 let 키워드와, 값을 수정할 수 없는 불변 변수 const를 사용한다.
이 책에서는 무결성 제약 규칙을 공부할 겸 가급적 불변 변수만 사용해 실습을 진행한다.
기존 자바스크립트 문법은 prototype 으로 클래스를 표현했다.
하지만 ES6는 클래스를 정의하여 사용할 수 있다.
// ES6 예제
class Shape {
static create(x, y) {
return new Shape(x, y);
}
name = 'Shape';
constructor(x, y) {
this.move(x, y);
}
move(x, y) {
this.x = x;
this.y = y;
}
area() {
return 0;
}
}
JAVA와 거의 흡사한듯..?!
class 키워드로 Shape를 정의한 코드이다. 클래스 함수 정의에는 변수 선언을 위한 키워드(var, let, const)를 사용하지 않는다는 점에 주목하자.
상속은 아래와 같이 간결하게 작성할 수 있다.
class Circle extends Shape {
constructor(x, y, radius) {
super(x, y);
this.radius = radius;
}
area() {
if (this.radius === 0) return super.area();
return this.radius * this.radius;
}
}
var c = new Circle(0, 0, 10);
c.area(); // 100
화살표 함수는 ES6에 추가된 표현식을 사용하는 함수로, '=>' 로 선언한다.
//익명 함수
function addNumber(num) {
return function (value) {
return num + value;
};
}
//화살표 함수
var addNumber = num => value => num + value;
화살표 함수는 익명 함수를 선언하여 변수에 대입하는 방법과 유사하다.
기존 자바스크립트에서는 객체와 객체의 값을 선언하기 위해 키 이름과 값을 각각 할당했다.
기존 자바스크립트 :
var x = 0;
var y = 0;
var obj = { x: x, y: y};
var randomKeyString = 'other';
var combined = {};
combined['one' + randomKeyString] = 'some value';
var obj2 = {
methodA: function() { console.log('A'); },
methodB: function() { return 0; },
};
ES6에서는 아래와 같이 개선했다.
ES6 :
var x = 0;
var y = 0;
var obj = { x, y };
var randomKeyString = 'other';
var combined = {
['one' + randomKeyString]: 'some value',
};
var obj2 = {
x,
methodA() { console.log('A'); },
methodB() { return 0; },
};
구조 할당은 앞에서 배운 전개 연산자를 함께 사용한다. ES6의 구조 분해와 구조 할당은 함수 인잣값을 다루거나 JSON 데이터를 변환할 때 유용하게 사용하므로 꼭 기억하자.
ES6 구조 할당 :
var [item1, ...otherItems] = [0, 1, 2];
var { key1, ...others } = { key1: 'one', key2: 'two' };
// otherItems = [1, 2]
// others = { key2: 'two' }
기존 자바스크립트에서
const count = props.count
ES6에서
const { count } = props
자바스크립트는 라이브러리나 모듈을 관리하는 방법이 매우 불편하다.
<script src="./app.js" language="javascript"></script>
또한 참조를 할 때 순서에 따른 의존성 문제가 발생할 수 있다.
ES6은 script 엘리먼트 없이 import 구문을 사용한다.
import MyModule from './MyModule.js';
기존 자바스크립트로 문자열을 분리하는 코드
const qs = 'banana=10&apple=20&orange=30';
function parse(qs) {
var queryString = qs.substr(1); // querystring = 'banana=10&apple=20&orange=30'
var chunks = queryString.split('&'); // chunks = ['banana=10', 'apple=20', 'orange=30']
var result = {};
for(var i = 0; i < chunks.length; i++) {
var parts = chunks[i].split('=');
var key = parts[0];
var value = Number.isNaN(Number(parts[1])) ? parts[1] : Number(parts[1]);
result[key] = value;
}
return result;
}
ES6의 forEach() 함수를 사용하면 반복문의 i++과 배열 크기 저장하는 과정을 생략할 수 있다.
const params = parse(qs); // params = { banana: 10, apple: 10, orage: 30};
function parse(qs) {
const queryString = qs.substr(1); // querystring = 'banana=10&apple=20&orange=30'
const chunks = queryString.split('&'); // chunks = ['banana=10', 'apple=20', 'orange=30']
let result = {};
chunks.forEach((chunk) => {
const [ key, value ] = chunk.split('='); // key = 'banana', value = '10'
result[key] = value; // result = { banana: 10 }
});
return result;
}
만약 불변 변수만을 사용하려면 map() 함수를 사용하자.
각 배열 요소는 정의된 함수를 통해 변환한 결과값들로 배열을 반환한다.
쉽게 말해 배열을 가공해 새 배열을 만드는 함수이다.
아래 코드는 위의 예제와 같은 결과값을 출력한다.
function parse(qs) {
const queryString = qs.substr(1);
const chunks = queryString.split('&');
const result = chunks.map((chunk) => {
const [ key, value ] = chunk.split('='); // key = 'banana', value = '10'
return { key: key, value: value }; // { key: 'banana', value: '10' }
});
return result;
앞에서 작성한 코드로 얻은 결괏값은 객체가 아닌 배열이다. 만약 배열을 객체로 변환하고 싶다면 reduce() 함수를 사용하면 된다.
function sum(numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
sum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 55
reduce() 함수를 보면 첫 번째 인자에는 변환 함수, 두번째 인자에는 초깃값을 전달한다.
그러면 reduce() 함수는 변환 함수의 첫 번째 인자를 이전 결괏값, 두 번째 인자를 배열의 각 요솟값으로 생각하여 순환 할당하면서 함수를 실행한다. 초깃값으로 전달한 0은 이전 결괏값인 total 에 할당된다.
하지만 실무에서 reduce() 함수는 보통 배열을 특정 자료형으로 변환하는 데 사용한다. 즉 배열의 총합을 구하는 위 예제는 '단순히 합을 구하는 문제' 가 아니라 '배열을 숫자로 변환한 예제' 로 이해해야 한다.
이 방법을 응용하면 배열을 얼마든지 다른 형태로 변환할 수 있다. 앞에서 map() 함수를 사용한 예제에 reduce() 함수를 응용하여 배열을 객체로 변환해 보면 다음과 같다.
function parse(qs) {
const queryString = qs.substr(1);
const chunks = qs.split('&');
return chunks
.map((chunk)=>{
const [key,value] = chunk.split('=');
return {key,value};
})
.reduce((result,item) => {
result[item.key] = item.value;
return result;
},{})
}
배열을 객체로 변환하기 위해 reduce() 함수의 두 번째 매개변수인 초깃값에 빈 객체 {}를 입력했다.
비동기 함수는 비동기 처리를 위해 사용한다. 비동기 처리란 작업 시간이 많이 필요한 작업 A를 처리하느라 다른 작업(B, C, D, ...)이 대기하고 있는 상태라면 일단 다른 작업들을 먼저 진행하고 작업 A와 작업 A와 연관된 작업을 이후에 처리하는 방식을 말한다. 비동기 처리의 대표적인 예는 서버에 데이터를 요청하고 결과를 기다리는 과정이다. 서버에 데이터를 요청하는 동안 다른 작업을 진행하다가 데이터 요청이 완료되면 본격적으로 작업을 진행하는 식이다. 여러 작업을 효율적으로 처리하려면 비동기 처리를 꼭 할 줄 알아야 한다.
기존 자바스크립트의 비동기 함수 처리 방법은 지연 작업이 필요한 함수에 setTimeout() 함수를 이용했다. 그리고 지연 작업 완료 이후 실행되어야 하는 함수는 지연 작업 함수의 인자로 전달하여 처리했다. 다음은 지연 작업을 위해 setTimeout() 함수를 사용하여 work1(), work2(), work3() 함수를 정의한 것이다. 지연 작업과 상관없이 바로 실행되어야 하는 함수는 urgentWork()이다.
// ES5의 예제
function work1(onDone) {
setTimeout(() => onDone('작업1 완료!'), 100);
}
function work2(onDone) {
setTimeout(() => onDone('작업1 완료!'), 200);
}
function work3(onDone) {
setTimeout(() => onDone('작업3 완료!'), 300);
}
function urgentWork() {
console.log('긴급 작업');
}
// 실제 비동기 함수를 사용하는 예
work1(function(msg1) {
console.log('done after 100ms:' + msg1);
work2(function(msg2) {
console.log('done after 300ms:' + msg2);
work3(function(msg3) {
console.log('done after 600ms:' + msg3);
});
});
});
urgentWork();
위의 코드는 총 3개의 지연 작업 함수가 있는데 이 함수들은 비동기 함수이므로 urgentWork() 함수가 바로 실행된다. 이후 지연 작업 함수가 순서대로 실행된다.
그런데 위의 코드를 보면 콜백 함수가 총 3개의 계단 모양으로 되어 있음을 알 수 있다. 이런 형태를 흔히 콜백 지옥이라 부른다. 만약 지연 작업을 해야 하는 함수가 100개이고 이 작업이 모두 연결되어 있다면 100개의 계단 형태를 띠게 된다.
ES6에서는 이러한 콜백 지옥을 해결할 수 있는 비동기 함수 표현법(Promise 클래스 함수)이 추가되었다.
Promise 객체를 생성할 때는 다음과 같이 클래스의 resolve() 함수 또는 reject() 함수에 해당하는 콜백 함수를 직접 전달해야 한다.
const work1 = () =>
new Promise((resolve) => {
setTimeout(() => resolve('작업1 완료!'), 100);
})
resolve() 함수는 이후 then() 함수에 인자로 전달할 콜백 함수 onDone()과 일치한다.
다음은 Promise 객체를 반환하는 함수를 정의하고 지연 작업을 비동기로 수행해 보는 코드이다. 실행 결과는 앞의 자바스크립트와 동일하지만 콜백 지옥이 없어졌다는 차이가 있다.
const work1 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업1 완료!'), 100);
});
const work2 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업2 완료!'), 200);
});
const work3 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업3 완료!'), 300);
});
function urgentWork() {
console.log('긴급 작업');
}
const nextWorkOnDone = msg1 => {
console.log('done after 100ms:' + msg1);
return work2();
};
work1()
.then(nextWorkOnDone)
.then(msg2 => {
console.log('done after 200ms:' + msg2);
return work3();
})
.then(msg3 => {
console.log(`done after 600ms:${msg3}`);
});
urgentWork();
이렇게 이어지는 지연 작업을 콜백 지옥 없이 구현할 수 있다는 것이 Promise 클래스의 장점이다.
then() 함수가 Promise 객체를 반환하므로 이를 응용하면 각 지연 작업들을 나누거나 합칠 수 있다.
비동기 처리 방식은 처음에 어렵게 느껴질 수 있지만, 아주 중요한 내용이니 잘 이해되지 않는 부분은 한 번 더 복습하고 넘어갈 것을 권장한다.
디바운스와 스로틀은 ES6 문법은 아니지만, 앞에서 말한 '지연 처리' 를 효율적으로 구현할 수 있으므로 여기서 다룬다. 두 개념은 서버의 데이터를 요청하는 등의 작업에서 생기는 부하를 크게 줄여주므로 알아두는 것이 좋다.
디바운스는 어떤 내용을 입력하다가 특정 시간동안 대기하고 있으면 마지막으로 입력된 내용을 바탕으로 서버 요청을 하는 방법이다. 연관 검색어 창을 떠올리면 이해가 쉽다.
위 그림을 코드로 구현하면 아래와 같다.
export function debounce(func, delay) {
let inDebounce;
return function(...args) {
if (inDebounce) {
clearTimeout(inDebounce);
}
inDebounce = setTimeout(
() => func(...args),
delay);
}
}
디바운스 개념과 비슷하지만 '입력되는 동안에도 바로 이전에 요청한 작업을 주기적으로 실행한다는 점'이 다르다. 예를 들면 페이스북의 스크롤을 내리는 동안 다음 내용이 출력되는 타임라인이 있다.
위의 과정에 맞게 구현한 코드는 다음과 같다.
function throttle(func, delay){
let lastFunc;
let lastRan;
return function(...args){
const context = this;
if(!lastRan){
func.call(context, ...args);
lastRan = Date.now();
} else {
if (lastFunc) clearTimeout(lastFunc);
lastFunc = setTimeout(function(){
if((Date.now() - lastRan) >= delay) {
func.call(context, ...args);
lastRan = Date.now();
}
}, delay - (Date.now()-lastRan));
}
}
}
var checkPosition = () => {
const offset = 500;
const currentScrollPosition = window.pageYOffset;
const pageBottomPosition = document.body.offsetHeight - window.innerHeight - offset;
if (currentScrollPosition >= pageBottomPosition){
console.log('다음 페이지 로딩');
}
}
var infiniteScroll = throttle(checkPosition, 300);
window.addEventListener('scroll',infiniteScroll);
여기까지 리액트 개발에 자주 사용되는 문법을 공부했다.
여기서 다루지 않은 다른 유용한 문법은 구글링 ㄱㄱ
으엌.. 여름방학때 공부했는데도 조금 어렵드아..