1213 - 1219
1213
리액트 수업 8일 차
Asset Modules
참고
webpack.kr/guides/asset-modules/
webpack 5 이전에는 아래의 로더를 사용하는 것이 일반적
- file-loader : 파일을 출력 디렉터리로 내보낼 때
- url-loader : 파일을 data URI 형식으로 번들에 인라인 추가할 때
data URI
- 이미지 등의 외부 바이너리 파일을 웹페이지에 인라인으로 넣기 위해 사용
- 외부 데이터를 별도의 파일로 두지 않고 HTML 파일로 관리할 수 있음
- 요청 수를 줄여 빠른 전송 효과를 볼 수 있음
- base64 인코딩의 추가적인 작업으로 인해 용량이 아닌 메모리 및 처리시간 등으로 인한 성능 저하가 단점이다.
그러나 webpack 5에서는 새로운 모듈 유형이 추가됨.
module: {
rules: [
// assets
{
test: /\.(jpe?g|gif|png|webp|bmp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 4 * 1024, // 4kb
},
},
},
];
}
- asset/resource는 별도의 파일을 내보내고 URL을 추출함. 과거 file-loader처럼 동작
- asset/inline은 에셋의 data URI를 내보냄. 이전에는 url-loader를 사용하여 처리함
- asset만 지정해주면 파일 크기가 8kb 이하일 때는 data URI를, 이상일 때는 별도의 파일 내보내기 중에서 자동으로 선택함.
- 빌드했을 때는 8kb 이상의 파일만 내보내진다.
- dataUrlCondition을 사용해서 8kb 기준 값을 변경할 수 있다.
이미지를 특정 디렉토리 안에 특정 이름으로 내보내는 설정
// config.dev.js
output: {
...
assetModuleFilename: 'assets/[name].[contenthash][ext]',
},
- assets 폴더 내에 파일이름.hash값.확장자명 순으로 파일을 내보낸다.
- 여기까지 설정하면 리액트 컴포넌트 단위로 에셋을 설정할 수 있다. 어차피 빌드하면 내보내지기 때문에!
A11yHidden 컴포넌트
UI 화면에는 표시하지 않지만, 스크린 리더 사용자에게 정보를 제공할 수 있는 컴포넌트
스타일 코드
.a11yHidden {
overflow: hidden;
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
clip-path: circle(0);
width: 1px;
height: 1px;
margin: -1px;
white-space: nowrap;
}
caption.a11yHidden {
position: static;
}
.a11yHidden.focusable:focus {
overflow: initial;
position: static;
clip: auto;
clip-path: unset;
width: initial;
height: initial;
margin: initial;
white-space: initial;
}
재사용 가능한 컴포넌트 만들기
import "./A11yHidden.css";
import React from "react";
import { classNames } from "../../utils";
export function A11yHidden({ as, className, focusable, ...restProps }) {
return React.createElement(as, {
className: classNames(
"a11yHidden",
{
focusable: focusable,
},
className
),
...restProps,
});
}
A11yHidden.defaultProps = {
as: "span",
className: "",
focusable: false,
};
- a11yHidden 컴포넌트는 다양한 태그에 사용될 수 있으므로 독립적으로 동작하기 위해서는 태그 이름을 넘겨줘야 한다.
- 태그를 JSX로 as로 받아서 렌더링하면 그냥 태그 이름이 as인 컴포넌트가 생성된다.
- 위와 같이 react api인 createElement를 사용하면 원하는대로 동작하게 할 수 있다.
- 클래스는 기존에 작성된 것이 덮어써지지 않도록 주의해야 한다.
JSX로 재사용 가능한 컴포넌트 만들기
export function A11yHidden({
as: ComponentName,
className,
focusable,
...restProps
}) {
return (
<ComponentName
className={classNames("a11yHidden", { focusable }, className)}
{...restProps}
/>
);
}
- JSX 방식으로 리턴하고 싶다면 위와 같이 as에 uppercase로 시작하는 별칭을 주어서 사용하면 원하는대로 작동한다.
- 컴포넌트 타입이름을 바꿀때 JSX는 소문자가 아니어야 한다.
-> 구조분해 할당해서 별칭 이름 할당하는 방식으로 하면 해결이 된다.
postcss 설정
- postcss는 sass와 다르게 표준이다!
npm i -D postcss postcss-loader postcss-preset-env
// postcss.config.js
module.exports = {
plugins: [
[
"postcss-preset-env",
{
stage: false, // 0 ~ 4
browsers: "default, > 5% in KR, ie 10-11",
autoprefixer: {
grid: true,
},
features: {
"nesting-rules": true,
"custom-properties": true,
"custom-selectors": true,
"color-functional-notation": true,
},
},
],
],
};
- stage는 0~4까지 존재하는데, false로 지정하면 기존에 잘 안되던 문제를 해결할 수 있다는 답변이 있길래 false로 지정했다.
// .browserslistrc
defaults
> 5% in KR
not dead
ie 11
- 브라우저 지원 범위 설정
// config.dev.js
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
],
},
Sass 설정
npm i -D sass sass-loader
// config.dev.js
{
test: /\.(css|s[ac]ss)$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
- importLoaders는 CSS로더 이전에 적용할 로더의 허용수를 설정
jest 설정 변경
module.exports = {
...
moduleNameMapper: {
'\\.s?[ac]ss$': 'identity-obj-proxy',
},
}
svgr 설정
npm i -D @svgr/webpack
- svg 파일을 모듈로 불러와서 내보내주는 형태
- html에 svg를 삽입할 때, 컴포넌트를 계속해서 만들지 않기 위해 사용하는 플러그인
// config.dev.js
// create-react-app 툴체인과 동일한 방식
{
test: /\.svg$/i,
issuer: /\.jsx?$/,
use: [
{
loader: '@svgr/webpack',
options: {
prettier: false,
titleProp: true,
svgo: true,
},
},
'url-loader',
],
},
- url 로더를 활용한 방법. cra 툴체인과 동일한 방식
// config.dev.js
// Webpack v5부터 사용 가능한 방식
{
test: /\.svg$/i,
oneOf: [
{
issuer: /\.jsx?$/,
resourceQuery: /react/, // *.svg?react
use: [
{
loader: '@svgr/webpack',
options: {
prettier: false,
titleProp: true,
svgo: true,
},
},
],
},
{
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 40 * 1024, // 40kb
},
},
},
],
},
// App.js
import ReactLogo from "../../assets/react.svg?react";
import reactLogoPath from "../../assets/react.svg";
export default function App({ greetingMessage }) {
return (
<div className="app">
<img src={reactLogoPath} alt="리엑트" />
<ReactLogo title="React UI Library" />
<h1>{greetingMessage}</h1>
</div>
);
}
- 위와 같이 설정하면 ?파일이름을 뒤에 붙였을 때는 컴포넌트, 그냥 가져왔을 때는 data URI방식으로 동작한다.
1214
모의 코딩테스트 10회 회고
실전 코딩 테스트 이전 마지막 모의 테스트!
약 1분을 남기고 모든 문제를 해결했다. 이로서 4연속 모의 코딩테스트는 만점이다.
그런데 완벽하게 풀지는 못한 것 같다는 생각이 들어 그리 후련하지는 않다.
이게 곧 다가올 실전 테스트에 약간은 떨리기도 하지만, 긴장하지만 않고 하던대로 한다면 좋은 결과가 있을 것이라고 생각한다.
Partition Array into Disjoint Intervals
var partitionDisjoint = function (nums) {
const n = nums.length;
const minR = Array(n).fill(0);
minR[n - 1] = nums[n - 1];
for (let i = n - 2; i >= 0; i--) {
minR[i] = Math.min(minR[i + 1], nums[i]);
}
let currentMax = nums[0];
for (let i = 1; i < n; i++) {
currentMax = Math.max(currentMax, nums[i - 1]);
if (currentMax <= minR[i]) return i;
}
};
- O(n)으로 풀기 위해서는 추가로 특정 배열을 작업해줘야만 했다.
- minR은 우측부터 현재 인덱스까지의 최솟값을 저장하는 배열이다.
- 인덱스 1부터 n전까지 최댓값을 구하고, 해당 최댓값이 minR의 i번째값보다 작거나 같다면, i-1까지의 모든 수가 minR의 i번째부터의 최솟값보다 작다는 의미이므로 i를 반환해 값을 구했다.
Prison Cells After N Days
var prisonAfterNDays = function (cells, n) {
let tmp = [...cells];
let answer = Array(8).fill(0);
const cnt = n % 14 ? n % 14 : 14;
for (let i = 0; i < cnt; i++) {
for (let j = 1; j <= 6; j++) {
answer[j] = tmp[j - 1] === tmp[j + 1] ? 1 : 0;
}
tmp = [...answer];
}
return answer;
};
- 총 8개의 셀 중, 맨 끝 두 개는 0이기 때문에 총 경우의 수는 64가지였다.
- 입력값이 일억이었기에 이분탐색으로 해결하거나, 일정한 규칙이 있을 것이라고 판단하였다.
- 이분탐색을 사용하기는 어렵다고 생각해서 일정한 규칙을 찾은 결과 14번마다 반복된다는 사실을 깨닫게 되었다.
- 반복 횟수를 14로 나눈 나머지만큼 반복하여 문제를 해결했다.
가장 밸런스 좋은 팀 구성
function solution(nums) {
let answer = Number.MAX_SAFE_INTEGER;
const n = nums.length;
const total = nums.reduce((acc, cur) => acc + cur, 0);
const ch = Array(n).fill(0);
function DFS(L, s, t, sum1, sum2) {
if (L === 6) {
const arr = [sum1, sum2, total - sum1 - sum2];
answer = Math.min(answer, Math.max(...arr) - Math.min(...arr));
} else if (L >= 3 && L < 6) {
for (let i = t; i < n; i++) {
if (ch[i] === 0) {
ch[i] = 1;
DFS(L + 1, s, i + 1, sum1, sum2 + nums[i]);
ch[i] = 0;
}
}
} else {
for (let i = s; i < n; i++) {
ch[i] = 1;
DFS(L + 1, i + 1, t, sum1 + nums[i], sum2);
ch[i] = 0;
}
}
}
DFS(0, 0, 0, 0, 0);
return answer;
}
- 발상을 조금 바꿔서 한 DFS 내에서 두 개의 조합을 구하는 방법을 고려해봤다.
- 기존 조합 방식 + 체크 배열을 이용해서 중복되지 않은 두 조합을 구하면 나머지 한 팀은 총 값 - 두 조합 값 이므로 이를 통해 정답을 찾았다.
해석 가능한 가지 수 구하기
function solution(s) {
const len = s.length;
const dy = Array(len).fill(0);
function DFS(L) {
if (dy[L] > 0) return dy[L];
if (s[L] === "0") return 0;
if (L === len - 1) return 1;
if (L === len - 2) {
if (s[L + 1] === "0") return 1;
else if (+s.substring(L, L + 2) <= 26) return 2;
else return 1;
}
if (+s.substring(L, L + 2) <= 26) {
return (dy[L] = DFS(L + 1) + DFS(L + 2));
} else {
return (dy[L] = DFS(L + 1));
}
}
return DFS(0);
}
- a-z까지 1-26으로 해석되는 문자열의 해석 가능한 가지 수를 구하는 문제
- 메모이제이션 기법을 이용해 값을 저장하고 반환하는 방식으로 효율성을 해결했다.
- 그 외에 마지막 길이면 1을, 현재 문자가 0이면 0을 그 외에도 분기 처리하여 적절한 값을 반환해서 문제를 해결했다.
1215
리액트 수업 9일 차
컨테이너 컴포넌트
- 클래스 컴포넌트는 컨테이너 컴포넌트로서 사용된다.
- 컨테이너 컴포넌트는 비즈니스 로직을 가지고 있으며, 프레젠테이셔널 컴포넌트를 포함 구성해 화면을 그리는 최소한의 스타일 정보를 가진다.
- 컴포넌트의 역할을 프레젠테이셔널, 컨테이너로 분리해 각각의 고유한 책임을 부여하여 컴포넌트의 재사용성을 높이고, 디버깅을 손쉽게 처리
- 즉, 컨테이너 컴포넌트는 상태 또는 비즈니스 로직을 포함하여 프레젠테이셔널 컴포넌트에 Props를 전달하여 UI를 렌더링 하도록 한다.
State 종류
리액트의 주된 작업은 애플리케이션 상태를 가져와 DOM 노드로 전환하는 것이다.
- Model 상태 - ex) 도서 목록
- View / UI 상태 - ex) 오름차순 또는 내림차순
- Session 상태 - ex) 로그인 또는 로그아웃
- Communication 상태 - ex) 서버와 통신 중 상태
- 애플리케이션에서 다루는 상태 또는 데이터는 매우 다양함
클래스 vs 함수 컴포넌트
- 클래스 컴포넌트는 소유(this) 멤버를 가질 수 있고, 상태와 라이프 사이클 메서드를 설정 가능
- 함수 컴포넌트는 소유(this) 멤버를 가질 수 없으며, 상태를 가지지 못함
- 함수 컴포넌트는 컴포넌트가 다시 렌더링 될 때마다 함수 몸체가 다시 실행되는데 반해, 클래스 컴포넌트는 생성된 클래스 인스턴스를 통해 다시 렌더링 될 때 이전 값에 접근하거나 조작할 수 있다.
-> 리액트 훅의 등장으로 함수 컴포넌트 또한 컨테이너 역할 수행이 가능하다.
props vs state
- 컴포넌트는 외부로부터 Props를 전달 받아 사용할 수 있지만, 전달 받은 속성은 읽기 전용으로 값을 업데이트 할 수 없다.
- 외부에서 전달받은 속성은 읽기 전용이므로, 변경 가능한 데이터인 컴포넌트의 상태가 필요하다.
- 즉, 업데이트가 필요한 컴포넌트는 상태를 설정해 사용한다.
- state는 컴포넌트가 소유한 로컬 데이터로 적용 범위는 현재 컴포넌트에 한정된다.
컴포넌트 상태
class 컴포넌트의 상태는 constructor 내부에 this.state를 사용해 정의한다.
import React from "react";
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
}
render() {}
}
클래스 필드 선언을 사용하면 생성자 메서드를 사용하지 않고 상태를 손쉽게 설정할 수 있다.
class List extends React.Component {
state = {
items: []
}
...
}
- 제안 문법이고 표준으로 채택되지는 않았지만, Babel이 처리해주니 바로 사용 가능하다.
React 클래스 컴포넌트의 상태는 불변(immutable) 데이터로 setState API를 사용해 업데이트 해야 한다.
this.setState({
list: [ ...this.state.list, { ... } ]
})
- 일반적으로 객체가 참조를 통해 공유되어 있다면 상태가 어디든지 변경 될 가능성도 커져 문제가 된다.
- 변경이 일어난 객체의 프로퍼티만 비교함으로써 React에서 최적화가 가능하다.
- React는 실제 DOM을 제어하는 것이 아니라 중간에 가상 DOM을 두고 관리하기 때문에 빠르게 UI 변화에 대한 관리가 가능하다.
- 일부 side-effect로 인해 원하지 않은 시점에 컴포넌트가 다시 렌더링되는 경우
shouldComponentUpdate
로 최적화가 가능하다.
- 상태값이 변경 불가성이라면 간단한 비교 연산자를 통해 변화를 감지할 수 있지만, 깊은 깊이에 있는 내부 프로퍼티는 비교가 힘들 수도 있다.
- 일일이 비교하기 힘들기 때문에 비용이 좀 들더라도 객체를 복사하여 새로운 객체를 생성한 후 변경하는 작업을 한다.
- 이렇게 하면 메모리 주소가 바뀔때마다 데이터가 변경되었다고 판단할 수 있기 때문이다.
setState()에 전달되는 updater는 함수 타입 설정도 가능하다.
this.setState((prevState) => {
return {
list: [ ...prevState, { ... } ]
}
})
setState()의 두 번째 인자로 콜백함수를 전달할 수 있다.
this.setState(
(prevState) => ({ list: [ ...prevState, { ... } ]}),
// 상태 업데이트 이후 실행
// 업데이트 된 list 상태를 확인한 후, 처리
this.checkList
);
- setState() 메서드는 비동기 방식으로 처리된다. 불필요한 렌더링을 최대한 피하려 React에서 처리하기 때문이다.
- 상태 업데이트가 비동기 처리됨에 따라, 모든 상태가 안정적으로 업데이트 된 이후 실행되도록 콜백 함수를 제공하는 것이다.
- render 메서드에서 setState 메서드를 호출하게 되면 상태가 변하면 render가 다시 호출되기 때문에 무한루프에 빠진다.
태그가 포함된 문자열 인식시키기
getDescription() {
return 'Edit <code>src/App.js</code> and save to reload.';
}
- 위와 같이 html 코드가 포함된 문법을 JSX에 보간법을 사용하여 삽입하면 해당 문자열이 그대로 삽입된다.
getDescription() {
return [
'Edit',
' ',
<code key="appEntryFile">src/App.js</code>,
' ',
'and save to reload.',
];
}
- 이처럼 배열을 반환하여 보간법에 삽입하면 정상적으로 태그가 작동한다.
클래스 컴포넌트 라이프 사이클
자주 사용되는 라이프 사이클
- constructor -> 컴포넌트 생성 시점에 호출
- render -> 컴포넌트 렌더링 시점에 호출
- componentDidMount -> DOM에 마운트 된 이후 시점에 호출
- componentDidUpdate -> 컴포넌트 업데이트 이후 시점에 호출
- componentWillUnmount -> 컴포넌트 제거 예정 시점에 호출
componentDidMount
// 명령형 프로그래밍
// 컴포넌트가 실제 DOM에 마운트 된 이후 실행
componentDidMount() {
console.log('컴포넌트가 실제 DOM에 마운트 된 이후 실행');
console.log('componentDidMount', document.querySelector('.App-header'));
setTimeout(() => {
this.setState({
learnLink: {
...this.state.learnLink,
text: '리액트 함께 배워요!!',
},
});
}, 3000);
}
- 리액트 엘리먼트가 실제 돔에 적용되어서 해당 돔에 접근할 수 있다.
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapShot) {
console.log('컴포넌트가 업데이트 되었습니다!');
}
- componentDidMount에서 setState를 실행하여 새로운 상태가 반영되었을 때 실행된다.
- 기본 매개변수로 이전 props, 이전 state, 스냅샷을 받는다.
- 새로 업데이트 되는 경우 3가지
- new props
- setState()
- forceUpdate()
자주 사용되지 않는 라이프 사이클
- getDerivedStateFromProps -> 전달된 상태 및 속성을 가져와 설정하는 시점에 호출 (업데이트)
- shouldComponentUpdate -> 컴포넌트 업데이트 예정 시점에 호출 (렌더링 할지 말지 결정)
- getSnapshotBeforeUpdate -> 컴포넌트 업데이트 전 스냅샷 가져오는 시점에 호출
getDerivedStateFromProps
파생 상태 생성이 필요한 경우
- props를 통해 state 업데이트
- props와 state를 비교해 일치하지 않아 state 업데이트
static getDerivedStateFromProps(props, state) {
return {
count: props.count ?? 100,
};
}
- static 메서드로 이 안에서는 this에 접근할 수 없다. 파라미터로 접근.
- 반환 값이 컴포넌트의 상태에 합성할 파생 상태가 된다.
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// 부모로부터 전달 받은 props의 brand.label 값이 바뀌면 컴포넌트 렌더링을 하지 않는다.
if (nextProps.brand.label !== this.props.brand.label) {
console.log('부모 컴포넌트의 리 렌더링 요청을 묵살한다.');
return false;
}
return true;
}
- 이 메서드는 오직 성능 최적화만을 위한 것으로 렌더링을 방지하는 목적으로 사용할 경우 버그로 이어질 수 있다.
- false를 반환할 경우 React가 갱신 작업을 건너뛰게 만들 수 있고, true를 반환할 경우 정상적으로 갱신 작업을 수행한다.
getSnapshotBeforeUpdate
- 가장 마지막으로 렌더링된 결과가 DOM 등에 반영되기 전에 호출되는 메서드
- 컴포넌트가 DOM으로부터 스크롤 위치 등과 같은 정보를 변경되기 전에 얻을 수 있다.
- 반환하는 값은 componentDidUpdate()에 인자로 전달된다.
절대경로 지정
cra에서 절대경로 지정
// jsconfig.json
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
- 절대 경로를 사용하여 모듈 가져오기를 지원하도록 애플리케이션을 구성한다.
- src가 절대경로로 존재한다고 생각하고 모듈을 가져올 수 있다.
import Button from 'components/Button';
웹팩에서 절대경로 지정
1217 TIL
리액트 수업 10일 차
dangerouslySetInnerHTML
dangerouslySetInnerHTML
은 브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대체 방법- 리액트에서 직접 HTML을 설정할 수는 있지만, 위험하다는 것을 상기시키기 위해
dangerouslySetInnerHTML
을 작성하고__html
키로 객체를 전달해야 한다.
renderDescriptionHTML() {
return {
__html: 'Edit <code>src/App.js</code> and save to reload.',
}
}
return (
...
<p dangerouslySetInnerHTML={this.renderDescriptionHTML()} />
...
);
타입 관련 패키지 설치
npm i -D @types/react @types/react-dom
cra 지원 브라우저
IE 하위 버전을 지원하기 위해서는 폴리필이 필요하다.
npm i react-app-polyfill
엔트리 파일에서 아래와 같이 설정한다.
import "react-app-polyfill/ie11";
import "react-app-polyfill/stable";
- ie 11까지 지원하도록 하는 설정이다.
오류 라이프 사이클 (Error Boundary)
- 자식 컴포넌트 트리 내의 JS 오류를 감지하고, 해당 오류를 기록하며, 충돌이 발생한 컴포넌트 트리를 대신하여 대체 UI를 표시하는 React 컴포넌트
- Error boundary의 하위 트리에 존재하는 렌더링 과정, 생명주기 메서드, 모든 생성자에 대하여 오류를 감지해낸다.
- 클래스 컴포넌트에
static getDeriedStateFromError
또는componentDidCatch
를 정의할 경우 해당 컴포넌트는 Error Boundary가 된다. - 클래스로만 생성이 가능하다.
- 예외를 처리하여 복구하는 경우에만 사용하고, 제어 흐름을 조작하는 데에는 사용하지 않기를 권장한다.
static getDrivedStateFromError
static getDerivedStateFromError(error)
- 하위의 자손 컴포넌트에서 오류가 발생했을 때 호출된다.
- 이 메서드는 매개변수로 오류를 전달받고, 갱신된 state 값을 반드시 반환해야 한다.
- render 단계에서 호출되므로, 부수 효과를 발생시키면 안된다.
componentDidCatch
componentDidCatch(error, info)
- 이 생명주기 메서드는 자손 컴포넌트에서 오류가 발생했을 때 호출되며, 2개의 매개변수를 전달받는다.
- error - 발생한 오류, info - 어떤 컴포넌트가 오류를 발생시켰는지에 대한 정보를 포함한 componentStack 키를 갖고 있는 객체
- commit 단계에서 호출되므로, 부수 효과를 발생시켜도 된다.
- 오류 로그 기록등을 위해 사용하면 된다.
- 리액트에서 componentDidCatch가 오류를 처리하는 방식은 프로덕션과 개발 빌드가 약간 다르다.
- 개발 빌드에서, 오류는 window까지 전파된다. 이는 onerror가 componentDidCatch에서 잡은 오류를 인터셉트 하는 것을 의미한다.
- 프로덕션 빌드에서 오류는 전파되지 않는다. 즉 상위 오류 핸들러는 componentDidCatch에 의해 명시적으로 잡히지 않은 오류만 받는다.
- 즉 대체 UI 렌더링 제어를 하려면
static getDerivedStateFromError()
를 사용해야 한다.componentDidCatch
에서 setState의 호출을 통해 처리하면 릴리즈에서는 사용할 수 없게 된다.
export class ErrorBoundary extends Component {
state = {
errorMessage: "",
componentStackInfo: "",
};
static getDerivedStateFromError(error) {
return { errorMessage: error.message };
}
componentDidCatch({ message }, { componentStack }) {
this.setState({
errorMessage: message,
componentStackInfo: componentStack,
});
}
render() {
if (this.state.errorMessage) {
return (
<div className="displayError" role="alert">
{this.state.errorMessage}
</div>
);
}
return this.props.children;
}
}
- 위와 같이 구현할 수 있다.
- 하위 컴포넌트에서 에러가 검출되면 두 라이프 사이클 메서드가 실행되어 에러 메시지 상태를 업데이트하고 이후 render 메서드가 실행될 때, 에러가 발생했다면 에러 메시지를 그렇지 않다면 하위 컴포넌트를 정상적으로 렌더링한다.
라이프 사이클이 필요한 이유
React의 성능 최적화를 위한 목적 또는 React가 컨트롤 할 수 없는 사이드 이펙트를 처리하기 위함.
대표적인 사이드 이펙트
- 네트워크 통신 (비동기 통신 요청/응답)
- DOM 컨트롤 (실제 문서 객체 접근/조작)
- 구독/취소 (이벤트 핸들링 등)
- 오류 감지 (ErrorBoundary 컴포넌트 등)
네트워크 통신
간단한 예시
import React from "react";
import { getTiltCard } from "api";
class App extends React.Component {
state = {
tiltCards: [],
};
render() {
return (
<div className="container">
<h1 style={ ... }>틸트 카드 리스트</h1>
</div>
);
}
componentDidMount() {
getTiltCard().then(({ cards }) => {
this.setState({
tiltCards: cards,
});
});
}
}
export default App;
- AJAX 호출을 통한 데이터는 생명주기 메서드 중 componentDidMount 안에 추가되어야 한다.
- 이는 데이터를 받아올 때 setState를 통하여 컴포넌트를 업데이트 하기 위함이다.