챗봇 프로젝트 과정에서 발화 후, 답변을 응답 받기까지 시간이 걸렸다.
비동기 처리를 해야하는 상황이었는데
이때, 사용자가 기다리는 동안 loading 중 임을 알려주는 UI를 넣고 싶었다.
결론적으로 아래와 같이 구현을 성공했다.
![]()
![]()
redux를 사용한 이유?
컴포넌트 간의 의존성을 줄이고 store 라는 중앙 공간을 통해 상태를 관리하기 위해 redux를 사용했다.
gpt API를 사용했기 때문에 비동기 처리를 해주어야 했다.
하지만 구글링 결과, reducer 함수는 순수 함수로 작성해야 해서 비동기 로직과 함께 사용할 수 없다는 것이었다...
따라서 첫 시도인 순수 redux로 비동기를 다뤘다.
이전 코드를 보면.. 부끄럽지만
발화 및 답변 데이터를 반복문을 사용해서 보여주었는데
발화와 답변이 묶여있어 답변 응답이 올 때까지 발화도 UI에 보이지 않는 문제가 발생했다.
챗봇의 작동원리를 생각해보면 아주 잘못된 코드였다.
그래서 구글링을 통해, map() 함수를 이용해서 동적인 배열을 렌더링하도록 수정하였다.
export default function ChatBody(props) {
const [messages, setMessages] = useState([]);
useEffect(() => {
setMessages(props.message);
}, [props.message]);
return (
<>
...
<div className="chat-body">
{messages && messages.map((message, index) => (
message.class === 'question' ?
<Question question={message.text} />
: <Answer answer={message.text} />
))}
</div>
</>
);
}
또, redux 미들웨어를 사용하지 않고 비동기 처리를 하려고 하니 로딩을 구현 할 방법이 생각나지 않았다.
순수 redux를 사용하면 store 구성이 복잡하고 if문을 통해 action을 관리하는 등 코드가 간단하지 않았다.
redux-toolkit을 이용해서 사용자 발화 후 바로 UI에 나타낸 후, api를 통해 답변 응답이 오면 UI에 나타나도록 수정하였다. 이 과정에서 비동기 처리도 전보다 훨씬 깔끔해졌다.
redux-toolkit을 사용하면서 컴포넌트 라이프사이클에 대해 공부해야겠다고 생각했다.
답변 응답 중에는 status : "loading" 이고 응답이 오면 status : "success"로 바뀌도록 했다.
useEffect에 status를 확인하는 if문을 추가해 구현하였다.
text 배열의 단순한 값을 가져오는 것이므로 ... 연산자를 사용해 객체의 속한 값만 들고 오게 하였다.
원리는 다음과 같고
![]()
코드는 아래와 같다.
const [messages, setMessages] = useState([]);
const text = useSelector(state=>state.chatting.message);
const status = useSelector(state=>state.chatting.status);
useEffect(() => {
if(status === 'loading') {
setMessages([...text, {class: "answer", text: 'loading...'}]);
} else {
setMessages(text);
}
}, [status, text]);
Skeleton UI 를 적용해봐야겠다.React Suspense 공부하기