
프론트엔드 개발에서 데이터를 fetch나 axios 같은 라이브러리를 이용해 API로부터 받아오는 작업은 아주 흔하다. 하지만 비동기 데이터 로드의 특성상, 데이터를 제대로 받아오기 전까지는 데이터 상태가 null 또는 undefined일 수 있다. 이 때문에 자칫 잘못하면 런타임 에러가 발생할 수 있는데 이를 막기 위해 옵셔널 체이닝을 사용할 수 있다.
API로 데이터를 받아오는 비동기 작업에서는 세 가지 상태가 존재한다.
null이나 undefined로 설정된 상태이 과정에서 종종 발생하는 문제가 바로 초기 상태에서 데이터에 접근하려 할 때 발생하는 에러이다.
// 초기 데이터 상태가 null인 상황
const todos = null;
// 데이터가 없는데 map 메서드를 호출하면 에러가 발생
todos.map((todo) => <div>{todo.title}</div>);
// TypeError: Cannot read properties of null (reading 'map')
옵셔널 체이닝은 Javascirpt에서 제공하는 안전한 데이터 접근 방법으로, 객체의 속성이나 배열의 메서드에 접근하기 전에 해당 값이 null 또는 undefined인지 확인 해준다.
//옵셔널 체이닝 기본 문법
object?.property
array?.[index]
function?.()
?.는 해당 값이 null 또는 undefined일 경우 undefined를 반환하며, 에러를 방지한다.
옵셔널 체이닝 대신 if문이나 && 연산자를 사용해 데이터를 안전하게 처리할 수도 있긴 하다.
// todos가 존재하는 경우에만 map 메서드를 실행
if (todos) {
todos.map((todo) => <div>{todo.title}</div>);
}
// 또는 && 연산자를 이용한 방법
todos && todos.map((todo) => <div>{todo.title}</div>);
이러한 방식을 통해 에러를 방지할 수 있지만, 매번 조건문들 작성해야 하기 때문에 코드가 길어져 가독성이 낮아지고 휴먼 에러 가능성이 높다.
옵셔널 체이닝을 사용하면 데이터가 없는 경우에도 안전하게 접근 가능하며, 코드 또한 매우 간결해진다.
todos?.map((todo) => <div>{todo.title}</div>);
위 코드에서 todos 가 null이나 undefined라면 map 메서드는 실행되지 않고, 대신 undefined를 반환할 것이다. 따라서 에러 없이 나머지 요소들을 렌더링 할 수 있다.
const todos = null;
// 옵셔널 체이닝 사용
const result = todos?.map(todo => todo.title); // result는 undefined
// 옵셔널 체이닝을 사용하지 않으면 에러 발생
const resultWithoutOptional = todos.map(todo => todo.title); // TypeError 발생
fetch를 사용하든 axios를 사용하든 옵셔널 체이닝을 활용하는 방식은 동일하다.
최근에 axios에 대해 배웠으니 axios를 통해 비동기로 데이터를 받아올 때 옵셔널 체이닝을 활용하는 예시를 들어보고자 한다.
import axios from "axios";
import { useEffect, useState } from "react";
const App = () => {
const [todos, setTodos] = useState(null);
const [todo, setTodo] = useState({
title: "",
});
useEffect(() => {
const fetchPost = async () => {
try {
const { data } = await axios.get("http://localhost:4000/todos");
setTodos(data);
} catch (error) {
console.error(error);
}
};
fetchPost();
}, []);
console.log("todos =>", todos);
const onSubmitHandler = async (todo) => {
const response = await axios.post("http://localhost:4000/todos", todo);
console.log("response", response);
};
return (
<>
<h3>axios 연습</h3>
<form
onSubmit={(e) => {
e.preventDefault();
onSubmitHandler(todo);
}}
>
<input
type="text"
onChange={(e) => {
setTodo({ ...todo, title: e.target.value });
}}
/>
<button type="submit">추가하기</button>
</form>
{todos?.map((todo) => { // 옵셔널 체이닝!!
return (
<div key={todo.id}>
<p>{todo.title}</p>
</div>
);
})}
</>
);
};
export default App;
위 코드의 동작 과정은 다음과 같다.
axios를 통해 todos 데이터를 비동기로 받아온다.
데이터를 받아오기 전에는 todos가 null 상태이기 때문에 todos?.map을 사용하여 안전하게 렌더링 한다.
데이터가 로드된 후, setTodos를 통해 상태가 업데이트되면 화면에 todo.title이 출력된다.
옵셔널 체이닝을 사용하지 않았을 때:

데이터가 로드되기 전 상태에서 todos.map을 호출하면, todos가 null이므로 TypeError: Cannot read properties of null와 같은 런타임 에러가 발생
옵셔널 체이닝을 사용했을 때:

옵셔널 체이닝을 사용하면 데이터가 없을 때(todos가 null일 때)에도 안전하게 렌더링이 진행된다.
이 경우 초기 렌더링에서는 <h3> axios 연습 </h3>부분만 렌더링 되었다가(맵 부분은 undefined 반환), 데이터가 로드되면 todos 상태가 업데이트되어 정상적으로 todos까지 화면에 렌더링될 것이다.
옵셔널 체이닝은 데이터를 비동기로 받아오는 작업에서 안정성과 간결성을 제공하는 강력한 도구이며, 특히 fetch나 axios를 활용해 API 데이터를 받아올 때, 에러를 방지하는 데 큰 도움을 준다.
따라서 앞으로 프로젝트들을 진행할 때 적극적으로 활용하면 좋을 것 같다!
(남발할 경우 코드 가독성을 저하시킬 수 있으니 적절히 활용하기😉)