css
및 class
로 정의해서 사용 import _ from 'lodash'; // bad :(
import array from 'lodash/array'; // good :)
촬영된 이미지의 경우 png보다 jpg, jpeg 의 이미지크기가 더 작고, 만들어진 이미지의 경우 jpg보다 png의 사이즈가 더 작기에 여러방면으로 확장자 선택 시 고려
애니메이션 요소의 경우 gif보다 video 태그로 mp4파일을 사용하는것이 더 적은 용량의 리소스 사용.
이미지를 하나로 묶어 한번의 리소스 요청을 통해 가져와 background-position 속성으로 원하는 부분만 표시
Intersection Observer의 options 객체 생성
Intersection Observer의 callback 함수 생성
IntersectionObserver(callback, options) 인스턴스 생성
IntersectionObserver 인스턴스에 타겟 요소들을 등록
// Custom hook
function useLazyImageObserver(target) {
useEffect(() => {
if (!target.current) {
return;
}
let observer = new window.IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
observer.unobserve(lazyImage);
}
});
});
observer.observe(target.current);
return () => {
if (observer) {
observer.disconnect();
observer = null;
}
};
}, [target]);
}
// Component
function App() {
const target = useRef(null);
useLazyImageObserver(target);
return (
<section>
<div style={{ height: "2000px" }} />
<img ref={target} data-src="https://placeimg.com/320/100/any" alt="" />
</section>
);
}
목표는 레이아웃을 최대한 빠르게, 최대한 적게 발생하는것.
계산된 값을 반환하기 전에 변경된 스타일이 계산 결과에 적용되어 있지 않으면 변경 이전 값을 반환하기 때문에 브라우저는 동기로 레이아웃을 해야만 한다
const tabBtn = document.getElementById("tab_btn");
tabBtn.style.fontSize = "24px";
console.log(testBlock.offsetTop); // offsetTop 호출 직전 브라우저 내부에서는 동기 레이아웃이 발생한다.
tabBtn.style.margin = "10px";
한 프레임 내에서 강제 동기 레이아웃이 연속적으로 발생하면 성능이 더욱 저하된다.
// before
function resizeAllParagraphs() {
const box = document.getElementById("box");
const paragraphs = document.querySelectorAll(".paragraph");
for (let i = 0; i < paragraphs.length; i += 1) {
paragraphs[i].style.width = box.offsetWidth + "px";
}
}
// after
function resizeAllParagraphs() {
const box = document.getElementById("box");
const paragraphs = document.querySelectorAll(".paragraph");
const width = box.offsetWidth;
for (let i = 0; i < paragraphs.length; i += 1) {
paragraphs[i].style.width = width + "px";
}
}
상위 DOM요소를 사용하면 내부 하위 DOM요소에도 영향을 미치므로 가급적 하위 DOM요소를 변경한다.
// before
const parentNode = document.getElementById("parent")
const cnt = 10;
for (let i=0;i<cnt;i++) {
const newNode = document.createElement('li');
newNode.innerText = `this is ${i}-content`;
parentNode.appendChild(newNode);
}
// after
const parentNode = document.getElementById("parent")
const cnt = 10;
const fragNode = document.createDocumentFragment();
for (let i=0;i<cnt;i++) {
const newNode = document.createElement('li');
newNode.innerText = `this is ${i}-content`;
fragNode.appendChild(newNode);
}
parentNode.appendChild(fragNode);
domFragment에 추가된 요소들을 parentNode에 append하면 한 번만 DOM객체에 접근하면 되므로 효율적이다. documentFragment는 실제 DOM 트리에 포함되는 요소가 아니므로 reflow나 repaint를 발생시키지 않는다.
한 프레임 처리가 16ms(60fps) 내로 완료되어야 렌더링 시 끊기는 현상 없이 자연스러운 렌더링을 만들어낼 수 있다. 자바스크립트 실행 시간은 10ms 이내에 수행되어야 레이아웃, 페인트 등의 과정을 포함했을 때 16ms 이내에 프레임이 완료될 수 있다. 애니메이션을 구현할 때 네이티브 자바스크립트 API를 사용하는 것보다 CSS 사용을 권장한다.
애니메이션이 있는 요소는 다른 요소에 영향을 미칠 수 있으니 position:absolute;
, position:fixed
로 고정한다.
가장 많이 사용하는 속성인 top,left, right, bottom
이나 width, height
조정대신 transform
속성을 활용하면 레이어만 분리해 합성만 일어나게됨.
실제 데이터가 로드되기 전에 영역을 표현할 스켈레톤 UX를 활용하면 체감 로드속도를 향상시킬 수 있음.
React에서 React.lazy를 통해 코드 스플리팅과 동시에 Suspense의 fallback props에 스켈레톤 Component를 설정해 컴포넌트가 로드되기 전 스켈레톤 이미지를 띄울 수 있다.
const BotItem = React.lazy((_) => import("./BotItem"));
const BotList = React.memo((props) => (
props.data.map((bot) => (
<Suspense key={bot._id} fallback={thumbnail}>
<BotItem {...bot} />
</Suspense>
))
)
이 함수로 component를 감싸주면 이전 props와 현재 props의 각 필드를 비교해 업데이트 여부를 결정함. props의 타입에 따라 적용 방식이 달라진다.
function Hotel(props) {
const { id, rooms } = props;
return (
<section>
{rooms.map(room => <HotelRoom key={room.id} room={room} />)}
</section>
);
}
function areEqual(prevProps, nextProps) {
return prevProps.id === nextProps.id;
}
export default React.memo(Hotel, areEqual);
// rooms가 재생성된 배열이라도 id값이 변경된 경우에만 렌더링됨.
function App(props) {
const { name, rooms } = props;
const children = useMemo(() => <HotelRooms rooms={rooms} />, [rooms]);
return (
<section>
<Hotel name={name}>{children}</Hotel>
</section>
);
}
function Hotel(props) {
const { name, children } = props;
return (
<section>
<h1>{name}</h1>
{children}
</section>
);
}
export default React.memo(Hotel);