리액트의 동시성(Concurrent) 모드와 useWorker :: 멀티쓰레드
[ 개요 ]
React는 일반적으로 메인 UI 스레드인 단일 스레드에서 실행됩니다.
그러나 React는 웹 워커(web workers)를 사용하여 여러 개의 스레드를 사용할 수 있으며, 이를 통해 메인 UI 스레드와 별도로 백그라운드 스레드에서 JavaScript를 실행할 수 있습니다.
이렇게 하면 작업을 백그라운드 스레드로 오프로드하여 메인 UI 스레드가 사용자 입력에 더 잘 반응하도록 함으로써 React 애플리케이션의 성능을 향상시킬 수 있습니다.
React 애플리케이션에서 웹 워커를 사용하려면 React 환경에서 웹 워커를 사용하기 위한 간단한 API를 제공하는 workerize 라이브러리 또는 기타 유사한 라이브러리를 사용할 수 있습니다.
useWorker 사이즈는 3KB 밖에 안 됩니다.
[ 리액트의 동시성(Concurrent) 모드 ]
동시 모드는 컴포넌트가 여러 단계로 렌더링하고 업데이트의 우선순위를 지정할 수 있는 React의 새로운 실험적 기능입니다. 업데이트를 처리하는 새로운 방법을 제공하며 느린 렌더링과 낮은 체감 성능과 같은 React 애플리케이션에서 흔히 발생하는 몇 가지 문제를 해결하는 데 도움이 됩니다.
동시 모드에서 React는 여러 컴포넌트를 렌더링하고 업데이트를 점진적으로 표시할 수 있어 더 부드럽고 반응성이 뛰어난 사용자 인터페이스를 구현할 수 있습니다. 이는 렌더링 프로세스를 더 작고 고립된 조각으로 나누고 브라우저가 이러한 조각을 개별적으로 페인팅할 수 있도록 함으로써 달성됩니다. 그 결과 메인 스레드를 보다 효율적으로 사용할 수 있어 사용자가 UI 업데이트를 기다리는 시간이 줄어듭니다.
또한 동시 모드는 '서스펜스'라는 개념을 도입하여 컴포넌트가 렌더링하기 전에 비동기 데이터가 로드될 때까지 '대기'할 수 있도록 합니다. 이를 통해 개발자는 로딩 상태와 폴백을 보다 효율적이고 유연한 방식으로 처리할 수 있습니다.
동시 모드는 아직 실험적인 기능이며 아직 프로덕션용으로 권장되지 않는다는 점에 유의해야 합니다. 하지만 React 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있는 잠재력을 가지고 있으므로 개발 과정을 계속 주시할 가치가 있습니다.
[ useWorker 예제 > workerize-loader ]
다음은 React 컴포넌트에서 workerize-loader 라이브러리의 useWorker 훅을 사용하는 예시입니다:
import React, { useState } from 'react';
import useWorker from 'workerize-loader!./worker.js';
const App = () => {
const [result, setResult] = useState(null);
const worker = useWorker(function () {
this.add = function (a, b) {
return a + b;
};
});
const handleClick = async () => {
const res = await worker.add(1, 2);
setResult(res);
};
return (
<div>
<button onClick={handleClick}>Calculate</button>
{result !== null && <div>Result: {result}</div>}
</div>
);
};
export default App;
이 예제에서는 워커가 생성되고 별도의 파일 worker.js에서 내보내집니다. 그런 다음 컴포넌트에서 useWorker 훅을 사용하여 워커를 인스턴스화하고 컴포넌트는 워커의 메서드를 비동기 함수로 호출하여 워커의 메서드를 호출할 수 있습니다. 이 예시에서는 컴포넌트의 버튼을 클릭하면 worker.add(1, 2)의 결과를 계산하여 페이지에 표시하는 add 메서드가 워커에 정의되어 있습니다.
[ useWorker 예제 > @koale/useworker ]
다음은 React 컴포넌트에서 @koale/useworker 라이브러리를 사용하는 방법의 예시입니다:
import React, { useState } from 'react';
import useWorker from '@koale/useworker';
const App = () => {
const [count, setCount] = useState(0);
const [result, worker] = useWorker(
function() {
// This function will run in a separate thread
self.onmessage = ({ data }) => {
console.log('Message received:', data);
self.postMessage(`Hello from worker! Count: ${data}`);
};
},
[count]
);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<p>Result from worker: {result}</p>
</div>
);
};
export default App;
이 예제에서는 useWorker 훅을 사용하여 별도의 JavaScript 스레드를 실행하는 웹 워커를 생성합니다. 후크는 작업자 함수와 종속성 배열이라는 두 가지 인수를 받습니다. 이 경우 작업자 함수는 메시지를 수신 대기하고 메시지를 메인 스레드로 다시 보내는 반면 종속성 배열에는 카운트 상태가 포함됩니다.
카운트 상태가 업데이트되면 워커는 업데이트된 값으로 다시 생성됩니다. 이를 통해 워커는 메인 UI 스레드를 차단하지 않고도 메인 스레드와 통신하고 백그라운드에서 복잡한 계산을 수행할 수 있습니다.
다음 예시는 @koale/useworker 라이브러리를 사용하여 React 애플리케이션에서 웹 워커를 활용하는 방법을 보여줌으로써 백그라운드에서 무거운 계산을 깔끔하고 효율적으로 수행할 수 있는 방법을 제공합니다.
다음은 별도의 스레드에서 버블 정렬을 수행하기 위해 @koale/useworker 라이브러리를 사용하는 방법의 예입니다:
import React, { useState } from 'react';
import useWorker from '@koale/useworker';
const App = () => {
const [array, setArray] = useState([5, 4, 3, 2, 1]);
const [sortedArray, worker] = useWorker(
function() {
self.onmessage = ({ data }) => {
console.log('Message received:', data);
function bubbleSort(array) {
let isSorted = false;
let lastUnsorted = array.length - 1;
while (!isSorted) {
isSorted = true;
for (let i = 0; i < lastUnsorted; i++) {
if (array[i] > array[i + 1]) {
[array[i], array[i + 1]] = [array[i + 1], array[i]];
isSorted = false;
}
}
lastUnsorted--;
}
return array;
}
const sortedArray = bubbleSort(data);
self.postMessage(sortedArray);
};
},
[array]
);
return (
<div>
<p>Original Array: [{array.join(', ')}]</p>
<button onClick={() => setArray(array.sort((a, b) => b - a))}>
Sort Array
</button>
<p>Sorted Array: [{sortedArray.join(', ')}]</p>
</div>
);
};
export default App;
이 예제에서는 백그라운드에서 배열을 정렬하는 웹 워커를 생성하기 위해 useWorker 훅을 사용합니다. 워커 함수는 메시지를 수신하고 버블 정렬 알고리즘을 사용하여 수신된 배열을 정렬합니다. 그런 다음 정렬된 배열은 메인 스레드로 다시 전송되어 UI에 표시됩니다.
메인 스레드는 정렬된 배열로 상태를 업데이트하고, 워커는 업데이트된 값으로 다시 생성합니다. 이렇게 하면 메인 UI 스레드를 차단하지 않고 작업자가 백그라운드에서 정렬을 수행할 수 있습니다.
이 예시는 @koale/useworker 라이브러리를 사용하여 정렬과 같은 무거운 계산을 별도의 스레드에서 수행하는 방법을 보여줌으로써 React 애플리케이션의 성능을 향상시킬 수 있는 깔끔하고 효율적인 방법을 제공합니다.
[ 퀵 스타트 ]
다음은 React 컴포넌트에서 Concurrent 모드를 사용하는 방법에 대한 빠른 시작 예제입니다:
App.js
import React, { useState, Suspense } from 'react';
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
const App = () => {
const [showComponent, setShowComponent] = useState(false);
return (
<div>
<button onClick={() => setShowComponent(!showComponent)}>
Toggle Component
</button>
{showComponent && (
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
)}
</div>
);
};
export default App;
AsyncComponent.js
import React from 'react';
const AsyncComponent = () => {
return <div>Async Component</div>;
};
export default AsyncComponent;
이 예시에서 App 컴포넌트는 useState 훅을 사용하여 비동기 컴포넌트를 표시할지 여부를 추적합니다. 버튼을 클릭하면 상태가 업데이트되어 컴포넌트가 표시되거나 숨겨집니다. 비동기 컴포넌트는 React.lazy를 사용해 비동기적으로 로드되며, 비동기 컴포넌트가 로드되는 동안 표시될 폴백 컴포넌트를 지정하는 Suspense 컴포넌트로 감싸집니다.
이 예시는 React 애플리케이션을 여러 파일로 분할하고 Concurrent 모드를 사용해 비동기 로딩을 깔끔하고 효율적으로 관리하는 방법을 보여줍니다.
2023.02.06 - [IT 인터넷/React Native & JS] - React를 ChatGPT에게 배우다.