react native stopwatch 구현하기

박은정·2023년 12월 14일
0

TIL

목록 보기
70/72

스톱워치 만들기

100ms마다 시간을 업데이트하려고 하기 때문에
아래와 같이 지정한 주기마다 함수를 실행하는 방법을 찾았습니다.

setIntervalsetTimeout
함수의 실행시간은 반복주기에 영향을 주지 않는다.실행한 함수가 종료된 이후부터 다시 지정한 시간 간격까지 기다린다.

🎃 setInterval

setInterval(실행할함수, 100);

여기서 실행할함수는 300ms 소요된다.
setInterval 메서드는 100ms마다 실행할함수를 task queue에 넣지만 실행할함수는 300ms가 소요되기 때문에
실제로는 300ms마다 함수가 반복되는걸 확인할 수 있다.

이처럼 100ms마다 task queue에 쌓이지만 300ms마다 task queue에서 꺼내서 실행하기 때문에
task queue에는 많은 task가 쌓이게 되고 STOP버튼을 눌렀을때 바로 실행되지 못하는 문제가 있었습니다.

🎃 setTimeout

setTimeout 메서드 내부에선 업데이트된 state값에 접근할 수 없는 문제가 있었는데 해당 블로그의 해결방법이 있었습니다.

출처: https://upmostly.com/tutorials/settimeout-in-react-components-using-hooks

일정 시간이 지난 후 함수나 코드 블록을 실행하려면 React 컴포넌트에서 setTimeout 을 사용하면 됩니다.
React에서 setTimeout을 사용하는 방법을 살펴보겠습니다.

React 컴포넌트에서 setTimeout 사용

setTimeout 메서드는 두 번째 인수를 사용해서 지정한 일정 시간이 지난 후 함수를 호출하거나 일부 코드를 실행합니다.
React 컴포넌트 내에서 setTimeout 을 사용하는 것은 일반 자바스크립트 메서드이기 때문에 충분히 쉽습니다.

예를들어, Hook을 사용하는 함수형 리액트 컴포넌트 내부에서 setTimeout 을 사용해보겠습니다.
클래스 컴포넌트의 componentDidMount 생명주기 메서드에 해당하는 useEffect Hook 내부에서 setTimeout을 호출할 것입니다.

const React, { useEffect } from 'react'

const App = () => {
	useEffect(()=>{
		const timer = setTimeout(()=>{
			console.log('1초뒤 실행된다');
		},1000);
		return () => clearTimeout(timer);
	},[]);
	
	return null;
}

App 컴포넌트가 처음 마운트될 때 timer라는 새로운 setTimeout 을 예약합니다.

결과적으로 setTimeout 메서드의 두 번째 매개변수로 전달된 1000ms 값에 표시된 대로 1초후에 setTimeout 블록 내부의 코드가 실행됩니다.

setTimeout clean작업

그렇지 않으면, 코드에서 부작용이 발생할 수 있기 때문에 setTimeout timer를 지우고 올바르게 처리해야 합니다.
timer를 지우거나 취소하려면 clearTimeout 메서드에 timer 객체를 전달해서 호출해야 합니다.

✨ setTimeout 초과문제

setTimeout 메서드 내부에선 해당 state의 현재값이 사용되지 않습니다.
setTimeout 메서드 내부의 state에 접근하려고 하면 setTimeout 메서드 및 state와 관련해서 이상한 문제를 발견했습니다.

import React, { useEffect, useState } from 'react';

const App = () => {
	const [count, setCount] = useState(0);
	const [countInTimeout, setCountInTimeout] = useState(0);
	
	useEffect(()=>{
		setTimeout(()=>{
			// 여기서 카운트는 0이다.
			setCountInTimeout(count);
		},3000);
		
		// setTimeout 이 예약된 후 count state를 5로 업데이트한다.
		setCount(5);
	},[]);
	
	return null;
}

countInTimeout state값은 5로 예상했지만, 실제로는 0이 됩니다.
setTimeout은 클로저이기 때문에 setTimeout이 예약될 때 정확한 시점의 카운트 값, 즉 초기값인 0을 사용합니다.
이러한 문제를 해결하기 위해서는 useRef Hook을 사용하면 됩니다.

const App = () => {
	const [count, setCount] = useState(0);
	const [countInTimeout, setCountInTimeout] = useState(0);
	const countRef = useRef(count);
	countRef.current = count;
	
	const getCountTimeout = () => {
		setTimeout(()=>{
			setCountInTimeout(countRef.current);
		},3000);
	}
}

이 솔루션은 해당 깃허브의 토론 덕분입니다.

setTimeout 내부에서 state 가 업데이트되지 않습니다.

setTimeout 이 예약될 때 예약된 시점의 state값을 사용합니다.
클로저에 의존해서 비동기적으로 카운트에 접근합니다.
컴포넌트가 리렌더링되면 새로운 클로저가 생성되지만 처음에 닫힌 값은 변경되지 않습니다.

setTimeout 메서드 내부에서 업데이트된 state 에 접근하려면??

업데이트된 state를 쓰고, 이후에 timeout되었을 때 읽을 수 있는 container 를 사용해야하는데, 이는 useRef의 활용법 중 하나입니다.
state값을 ref.current와 동기화하면 timeout에서 업데이트된 state값을 읽을 수 있습니다.

// 비동기콜백에서 현재 state값에 접근하려면 ref를 사용합니다.
const countRef = useRef(count)
countRef.current = count

const getCountTimeout = () => {
	setTimeout(()=>{
		setCountInTimeout(countRef.current)
	},2000)
}
profile
새로운 것을 도전하고 노력한다

0개의 댓글