리액트 문법 필기
본 내용은 실전 리액트 프로그래밍 (저자 : 이재승) 의 책을 읽으며 필요한 것들을 필기한 것입니다.
*저작권에 문제가 될 시 삭제하겠습니다.
필요한 것은 검색해서 찾아보자
Computed property names_______________________________________________________________
계산된 속성명(computed property names) => 객체의 속성명을 동적으로 결정하는 ES6+ 문법
function func(key, values){
return { [key] : values }
}
계산된 속성명을 사용하면 다음과 같이 컴포넌트의 상탯값을 변경할 때 유용하게 사용할 수 있다.
state = {
count1 = 0,
count2 = 0,
count3 = 0
}
onClick = index => {
const key = `count${key}`;
const value = this.state[key];
this.setState({[key] : value+1});
}
전개 연산자________________________________________________________________________________
전개 연산자 : 배열이나 객체의 모든 속성을 풀어놓을 때 사용하는 문법으로, 매개변수가 많은 함수를 호출할 때 유용
Math.max(1,3,5,7);
// 위 코드처럼 입력하지 않는 이상
// 원하는 변수들을 동적으로 매개변수로 전달할 수 없다.
const number = [1,3,5,7];
Math.max(...number);
// number이라는 배열을 동적으로 매개변수로 전달하는 전개 연산자의 사용 예시
** 배열에서 전개연산자를 사용하면 그 순서가 유지된다.
** 객체 리터럴에서는 ES5까지는 중복 속성명을 사용시 에러가 발생했지만 ES6 부터는 허용된다.
배열, 객체 비구조화_______________________________________________________________________
const arr = [1,2];
const [a,b] = arr;
console.log(a); // 1
console.log(b); // 2
이런식으로 새로운 변수로 할당할 수도 있고, 다음 코드처럼 이미 존재하는 변수에 할당할 수도 있다.
let a,b;
[a,b] = [1,2];
보통 두 변수를 swap할때는 제 3의 변수를 사용하게 되는데,
비구조화를 이용하면 1줄의 코드로 해결할 수 있다.
let a = 1;
let b = 2;
[a,b] = [b,a];
특정 위치를 제외한 배열의 나머지 값들은 전개 연산자를 통해 쉽게 분리할 수 있다.
const arr = [1,2,3];
const [first, ...arr2] = arr;
console.log(first); // 1
console.log(arr2); // [2,3]
배열의 경우 비구조화를 이용할 때 순서가 중요하지만 객체의 경우 속성명의 순서가 중요하지 않다.
const obj = { age:21, name : 'mike' };
const {age, name} == const {name, age} = obj;
또한 객체 비구조화의 경우 별칭을 사용할 수 있다.
** 객체 비구조화에서 계산된 속성명을 사용하는 경우 반드시 별칭을 입력해야 한다.
강화된 함수의 기능________________________________________________________________________
ES6부터 함수 매개변수에 기본값을 줄 수 있다.
function func(a = 1){
console.log(a);
}
func(); // 1
위와 같이 인수 없이 함수를 호출하면 a에는 undefined가 입력되고,
기본값이 정의된 매개변수에 undefined를 입력하면 정의된 기본값 1이 a에 입력된다.
객체 비구조화처럼 기본값으로 함수 호출을 넣을 수 있고, 기본값이 필요한 경우에만 함수가 호출된다.
function getDefault(){
console.log('인자가 없습니다');
}
function func(a = getDefault()){
return a;
}
func(10); // 10
func(); // '인자가 없습니다'
나머지 매개변수는 입력된 인수 중에서 정의된 매개변수 개수만큼을 제외한 나머지를 배열로 만들어준다.
function func(a, ...arr){
console.log({a, arr});
}
func(1,2,3); // { a:1, arr : [2,3] }
** ES6 부터는 함수를 화살표 => 를 이용해서 작성할 수 있다.
ES5부터 this의 전역환경 접근을 방지하기 위해 클로저(closure) 를 사용한다
클로저 : 함수가 생성되는 시점에 접근 가능했던 변수들을 생성 이후에도 계속해서 접근 할 수 있게 해주는 기능이다.
접근 할 수 있는 변수는 그 함수를 감싸고 있는 상위 함수들의 매개 변수와 내부 변수들이다.
향상된 비동기 프로그래밍 1: Promise_____________________________________________________
Promise는 비동기 상태를 값으로 다룰 수 있는 객체이다.
promise를 사용하면 비동기 프로그래밍을 할 때 동기 프로그래밍 방식으로 코드를 작성할 수 있다.
promise가 보급되기 전에는 비동기 프로그래밍 코드인 콜백(call back) 패턴이 많이 쓰였다.
ES6부터는 promise가 javascript언어에 포함되었다.
사실 Promise를 사용하기 전에는 func(callback){ callback(data) } .. 식으로 사용했었는데
진짜 읽히지가 않는다. 너무 복잡해서
하지만 promise를 사용하면 원하는 순서대로 순차적 실행이 가능하기 때문에 너무 편해졌다.
func( .. ).then(data => { .. }).then(data => { .. }) 과 같은 코드가 promise를 사용한 코드이다.
사용방법을 알고 있기 때문에 적지는 않겠다.
Promise를 사용하면 데이터 캐싱이 가능하다.
'처리됨' 상태가 되면 그 상태를 유지하는 프로미스의 성질을 이용해서 데이터를 캐싱할 수 있게 되는데,
let cached;
getData => {
cached = cached || requestData(); // requestData()의 return 값은 1이라 가정
return cached;
}
getData().then( res => console.log(res) );
getData().then( res => console.log(res) );
첫번째 getData 함수를 호출할 때에만 requestData가 호출되고
cached에는 1이라는 값과 함께 프로미스가 저장된다.
두번째 getData 함수를 호출하면 이미 프로미스가 저장되어 있기 때문에 재호출하지 않고도 cached를 얻을 수 있다.
** Promise를 병렬로 처리하기 **
만일 promise를 병렬로 처리하지 않고 다음과 같이 체인형식으로 처리한다면
requestData1() => {
fetch() {...}
}
requestData2() => {
fetch() {...}
}
requestData1()
.then(() => {
requestData2() ...
})
requsetData1을 실행 후, promise가 반환되면 requestData2가 실행될 것이다.
작업 중에 비동기로 처리해야하는 경우가 굳이 순차적으로 발생할 필요가 없다면
위와 같은 코드는 시간손해를 보게 된다.
이때는 Promise.all을 이용해서 원하는 작업을 병렬로 처리할 수 있다.
Promise.all([requestData1(), requestData2()])
.then( ([res1, res2]) => console.log(res1, res2); );
Promise.all 은 requestData1과 requestData2 과 같은 입력된 모든 프로미스가 '처리됨' 상태가 되어야
마찬가지로 '처리됨' 상태가 된다.
향상된 비동기 프로그래밍 2: async await________________________________________________
async await는 비동기 프로그래밍을 동기 프로그래밍처럼 작성할 수 있도록 함수에 추가된 기능이다.
promise가 javscript의 표준이 되고 2년 후인 ES7에 async await도 javascript 표준이 되었는데,
async await를 이용해서 비동기 코드를 작성하면 promise의 then 메서드를 호출하는 것보다 가독성이 좋아진다.
** 그렇다고 async await가 promise를 완전히 대체하는 것은 아니다.
** promise는 비동기 상태를 값으로 다룰 수 있기 때문에 async await보다 큰 개념에 속한다.
async await 함수는 반드시 promise를 반환한다.
promise는 객체로 존재하지만 async await는 함수에 적용되는 개념이다.
async function func() {
return 1;
}
func().then(res => console.log(res)); // 1
async 기호를 이용해서 함수를 정의하면 async await function이 된다.
promise를 반환하기 때문에 위와 같이 then을 사용할 수 있다.
await 키워드는 다음과 같이 사용할 수 있다.
function requestData(value) {
return new Promise(resolve => {
setTimeout( () => {
console.log('requestData : ', value);
resolve(value);
}, 1000),
};
}
async function getData(){
const res1 = await requestData(10);
const res2 = await requestData(20);
console.log(res1, res2);
return [res1, res2];
}
getData();
==> requestData : 10
==> requestData : 20
==> 10 20
await 키워드를 사용하였기 때문에
res1이 실행되면, promise 객체를 받기 전까지 res2는 실행되지 않는다.
마찬가지로 res2가 promise 객체를 받기 전까지 console.log(res1, res2)는 실행되지 않는다.
비동기 함수간의 의존성이 높아질수록 promise 와 async await의 가독성 차이는 드러나게 된다.
그렇기 때문에 then catch를 사용하는데 편해지긴 했지만
async await를 연습하는 것이 간결한 코드를 작성하는데 더욱 도움이 될 수 있을 것이다!
** async await도 병렬로 실행가능하다.
단순하게 await 키워드를 나중에 사용하면 된다.
async function func() {
const res1 = asyncfunc1();
const res2 = asyncfunc2();
const data1 = await res1;
const data2 = await res2;
}
// 물론 Promise.all로도 사용가능하다
async function func(){
const [data1, data2] = await Promise.all([asyncfunc1(), asyncfunc2()]);
}
제너레이터..는 아직 언제 쓰여야될지 감을 못잡겠다. 나중에 필요할 때 다시 읽어봐야겠다.
render____________________________________________________________________________________
리액트의 가장 기본적이면서도 가장 중요한 것이 컴포넌트는 상태값과 속성값을 관리하면서 UI를 관리해야 한다는 것
UI 데이터에는 상태값과 속성값이 있는데, UI 데이터가 변경되면 리액트가 렌더 함수를 이용해서 화면을 자동으로 갱신!
** 자식 컴포넌트는 부모 컴포넌트가 렌더링 될때 마다 같이 렌더링 되는게 default이다
* 만약 지정한 어떤 UI 데이터만 변경될 때 렌더링 되기를 원한다면 React.memo, React.PureComponent를 사용한다.
// memo 함수의 인자로 함수형 컴포넌트를 입력시
function Title(props){
return <p>{props.title}</p>
}
export default React.memo(Title);
// PureComponent 를 확장해서 클래스형 컴포넌트를 만들면 memo와 같은 효과를 낼 수 있다.
export default class Title extends React.PureComponent{
render(
return
)
}
위는 title이라는 부모 컴포넌트로부터 받은 속성값이 변경될 때만 재렌더링 되게끔 한다.
지정해주지 않으면 title 값이 바뀌지 않아도 부모컴포넌트가 렌더링되면 Title도 렌더링된다.
setState (비동기로 상태값 변경)
setState는 클래스형 컴포넌트에서 상태값을 변경할 때 호출하는 메서드로,
setState 메서드가 호출되면 리액트는 '해당'컴포넌트를 다시 그리게 된다.
만약 변화된 상태값 중에 자식에게 물려주게 될 속성값이 있다면 자식 컴포넌트로 재렌더링 된다.
다음은 setState를 연속해서 2번 호출하는 코드이다
this.setState({count : this.state.count + 1});
this.setState({count : this.state.count + 1});
이 코드가 의도하는 바는 state의 count를 2 증가시키는 것이다
하지만 의도대로 동작하지 않고 1만 증가한다.
원인은 setState는 비동기로 동작하기 때문!
리액트는 효율적으로 렌더링하기 위해서 여러 개의 setState 메서드를 batch로 처리한다.
리액트가 setState 메서드를 동기로 처리하면 하나의 setState 메서드가 호출될 때마다 화면을 다시 그리기 때문에
성능 이슈가 발생할 수 있다.
위 코드를 원하는대로 2만큼 증가시키기 위해서 setState의 인수로 함수를 입력하여 해결할 수 있다.
this.setState(prevState => ({count : prevState.count + 1});
this.setState(prevState => ({count : prevState.count + 1});
setState 메서드로 입력된 함수는 자신이 호출되기 직전의 상탯값을 매개변수로 받는다.
앞의 코드에서 첫 번째 setState 호출이 변경한 상태값이 두 번째 setState 호출의 인수로 사용된다고 이해할 수 있다.
리액트 요소와 가상 돔
리액트 요소는 리액트가 UI를 표현하는 수단이다.
리액트는 렌더링 성능을 위해 가상 돔을 활용한다.
빠른 렌더링을 위해서는 돔 변경을 최소화해야 한다.
그래서 리액트는 메모리에 가상 돔을 올려 놓고 이전과 이후의 가상 돔을 비교해서 변경된 부분만 실제 돔에 반영한다.
** 생명 주기 메서드 **
모든 컴포넌트는 3가지 단계를 거친다.
1. 초기화 단계
2. 업데이트 단계
3. 소멸 단계
각 단계에서는 몇 개의 메서드들이 정해진 순서대로 호출된다. 이를 생명 주기 메서드라고 한다.
- 초기화 단계
초기화 단계는 최초에 컴포넌트 객체가 생성될 때 '한 번' 수행된다.
constructor() -> static getDerivedStateFromProps() -> render() -> componentDidMount()
- 업데이트 단계
업데이트 단계는 초기화 단계와 소멸 단계 사이에서 반복해서 수행된다.
컴포넌트의 속성값 또는 상태값이 변경되면 업데이트 단계가 수행된다.
static getDerivedStateFromProps() -> shouldComponenetUpdate() -> render() ->
getSnapshotBeforeUpdate() -> componentDidUpdate()
- 소멸 단계
소멸 단계에서는 componentWillUnmount() 가 호출된다.
- 예외
3단계와는 별개로 렌더링 시 예외가 발생하면 다음과 같은 메서드가 호출된다.
static getDerivedStateFromError() -> componentDidCatch()
constructor 메서드
constructor 메서드의 구조는 다음과 같다
constructor(props)
props 매개변수는 컴포넌트의 기본 속성값이 적용된 상태로 호출된다.
constructor 메서드 내부에서 반드시 super() 함수를 호출해야 한다.
super 함수를 호출해야 React.Component 클래스의 constructor 메서드가 호출된다.
따라서 super 함수를 호출하지 않으면 컴포넌트가 제대로 동작하지 않는다.
* constructor 메서드 작성이 필요한 대표적인 예는 초기 속성값으로부터 상탯값을 만드는 경우이다 *
* 상탯값 state를 직접 할당하는 것은 constructor 메서드에서만 허용된다 *
* 지금은 클래스 필드(class field)를 사용하면 constructor 메서드를 사용하지 않고도 상탯값을 초기화할 수 있다 *
* constructor 메서드에서는 setState를 호출하지 말자(무시됨) *
static getDerivedStateFromProps 메서드
getDerivedStateFromProps 메서드는 속성값을 이용해서 새로운 상탯값을 만들 때 사용한다.
정적 메서드이기 때문에 this 객체에 접근할 수 없다. 반드시 상탯값과 속성값만을 이용해서 새로운 상태값을 만든다.
getDerivedStateFromProps 메서드는 주로 시간에 따라 변하는 속성값으로부터 상탯값을 계산하기 위해 추가됐다.
하지만 속성값 변화에 따른 추가적인 처리를 getDerivedStateFromProps 메서드에서만 할 수 있는 것은 아니다.
render 메서드
렌더 함수 내에서 setState를 호출하면 안된다.
렌더 함수의 반환값은 속성값과 상태값만으로 결정되어야 한다.
부수 효과(서버와 통신, 브라우저의 쿠키에 저장하기 등)를 발생시키면 안된다.
componentDidMount 메서드
componentDidMount 메서드는 render 메서드의 첫 번째 반환값이 실제 돔에 반영된 직후 호출된다.
따라서 render 메서드에서 반환한 리액트 요소가 돔에 반영되어야 알 수 있는 값을 얻을 수 있다.
componentDidMount 메서드가 호출될 때는 리액트 요소가 돔 요소로 만들어진 시점이기 때문에
돔 요소에 접근할 수 있게 된다.
이 때 필요한 정보들을 통해서 setState를 호출하면 원하는 방향으로 렌더링 할 수 있다.
** componentDidMount 메서드는 API 호출을 통해 데이터를 가져올 때 적합하다.
그런데 위 설명에 API 호출은 constructor 에서 하지말라고 하였지만
사실 constructor가 componentDidMount 보다 먼저 호출되는건 명백하다.
이 때 promise 객체를 constructor에서 받아두고 componentDidMount 에서 사용하는 방법은 API 호출 응답 시간에
민감한 애플리케이션이라면 좋은 방법이 될 수 있다.
shouldComponentUpdate 메서드
shouldComponentUpdate 메서드는 '성능 최적화' 를 위해서 사용된다.
즉, 성능 이슈가 발생했을 때 메서드를 작성해도 늦지 않다고 한다.
shouldComponentUpdate(nextProps, nextState) 의 구조로 이루어져 있으며,
true, false를 반환하는데 default 값은 true이다.
메서드 내부에서 true 값을 반환하게 되면 render 메서드가 호출된다.
이점을 이용해서 굳이 렌더링 되지 않아도 될 부분을 중지시킴으로써 성능 최적화를 하는 것!
componentDidUpdate 메서드
componentDidUpdate 메서드는 업데이트 단계에서 마지막으로 호출되는데,
* 가상 돔이 실제 돔에 반영된 후 호출된다 *
* 즉 새로 반영된 실제 돔에서의 상탯값을 가장 빠르게 가져올 수 있는 메서드이다 *
componentDidUpdate 메서드는 속성값이나 상탯값이 변경된 경우 API 호출을 위해 사용되기도 한다.
componentWillUnmount 메서드
componentWillUnmount 메서드는 소멸 단계에서 유일하게 호출되는 메서드이다.
끝나지 않은 작업 (ex : addEventListener) 을 처리하기 위해 호출된다.