24년 react 소식 ( React 컴파일러, useFormStatus, useFormState, useOptimistic ...)
이번에 올라온 react 업데이트 소식에 꽤나 재미있는 내용들이 몇개 보여서 이에 대해 간단하게 공유하고자 가져왔습니다. 보다 자세한 내용은 2024-02-15 react-lab-blog 에서 확인 가능합니다.
React 컴파일러
React만을 위한 컴파일러를 개발중
이리고 합니다. 실제로 ** instagram 에서는 이미 React 컴파일러를 사용** 하였다고 하고 조만간 오픈소스로 출시 준비중이라고 말하는것으로 보아 머지않은 시점에 공개하지 않을까 생각됩니다.
가장 큰 장점으로 생각되는건 useMemo
, useCallback
,memo
와 같은 메모이지에이션을 사용하지 않아도 자동으로 최적화 해준다는 부분인데요. 그동안 React를 통해 개발하면서 위의 훅들을 개발하는 개발자가 적절한 판단을 하여 최적화가 필요한 곳에 사용하였는데 사람마다 그 최적화의 기준이 다르기도 하고 일일히 수동 최적화를 해주어야 하는 부분이 상당히 피로도가 많았는데 아주 행복한 소식이라고 생각합니다. (나중엔 react 컴파일러가 아닌 react 만 사용해도 자동으로 최적화 되었으면 더욱 좋을것 같습니다.)
useOptimistic
useOptimistic
은 optimistic update
를 편하게 지원하게 나온 훅으로 간단하게 optimistic update
에 대한 예시를 보면 좋아요 액션에 대한 예시가 적절한 예시로 생각됩니다.
optimistic update 예시 흐름
- 사용자가 좋아요 버튼 누름
- 좋아요 처리에 대한 http 네트워크 호출과 동시에 성공 응답으로 좋아요에 대한 상태를 true로 변경하여 좋아요 상태 즉각 반영
- 이후 네트워크 처리에서 에러가 발생할 경우 좋아요에 대한 상태를 다시 원복
이러한 데이터 처리에 대해 편하게 제어할수있도록 useOptimistic
가 새롭게 추가되었습니다.
// ./actions.js export async function deliverMessage(message) { await new Promise((res) => setTimeout(res, 1000)); return message; } ------------------------------------------------------ import { useOptimistic, useState, useRef } from "react"; import { deliverMessage } from "./actions.js"; function Thread({ messages, sendMessage }) { const formRef = useRef(); async function formAction(formData) { addOptimisticMessage(formData.get("message")); formRef.current.reset(); await sendMessage(formData); } const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [ ...state, { text: newMessage, sending: true } ] ); return ( <> {optimisticMessages.map((message, index) => ( <div key={index}> {message.text} {!!message.sending && <small> (Sending...)</small>} </div> ))} <form action={formAction} ref={formRef}> <input type="text" name="message" placeholder="Hello!" /> <button type="submit">Send</button> </form> </> ); } export default function App() { const [messages, setMessages] = useState([ { text: "Hello there!", sending: false, key: 1 } ]); async function sendMessage(formData) { const sentMessage = await deliverMessage(formData.get("message")); setMessages((messages) => [...messages, { text: sentMessage }]); } return <Thread messages={messages} sendMessage={sendMessage} />; }
- 네트워크 전송중인 sending 시점에 응답에 대한 데이터를 미리 보여주는것을 볼 수 있습니다.
Form 개선 훅 useFormStatus, useFormState
그동안 React 에서 가장 힘든 처리중 하나가 Form 데이터에 대한 처리였습니다. React로만 작업하려면 보통 아래 케이스 처럼 개발하였습니다.
- form 속성마다 useState 추가해서 사용
- useState에 1개의 객체를 넣어서 처리
- useReducer를 통한 처리
- 복잡도 관리를 편하게 하기 위해 context를 활용
- 최적화를 위해 비제어 컴포넌트 활용
그러나 어떠한 방법을 사용하더라도 React를 통한 form 데이터 처리는 상당히 귀찮고 최적화하기도 힘들었습니다.
때문에 보통 잘 구현된 라이브러리 (react-hook-form
등.) 를 많이 사용하곤 했습니다. 이러한 불편함을 해결해주기 위해 Form 관련 지원 hook 들이 추가되었습니다.
- useFormStatus: 폼 상태의 정보 (폼이 제출완료되었는지, 유효성검사가 끝난거지, 폼처리중 상태인것인지) 와 같은 부분에 사용합니다.
- useFormState: 폼데이터에 대한 데이터 관리 (입력값에 대한 제어, 유효성 검사) 등에 사용합니다.
이번글에선 간단한 사용 예시만 보여드리고 다음 글에서 해당 훅들에 대한 여러 예시를 살펴볼 예정입니다.
useFormStatus
./actions.js "use server"; export async function addToCart(prevState, queryData) { const itemID = queryData.get('itemID'); if (itemID === "1") { return "Added to cart"; } else { return "Couldn't add to cart: the item is sold out."; } } ------------------------------------------------------------------------------------ import { useState } from "react"; import { useFormState } from "react-dom"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { const [message, formAction] = useFormState(addToCart, null); return ( <form action={formAction}> <h2>{itemTitle}</h2> <input type="hidden" name="itemID" value={itemID} /> <button type="submit">Add to Cart</button> {message} </form> ); } export default function App() { return ( <> <AddToCartForm itemID="1" itemTitle="JavaScript: The Definitive Guide" /> <AddToCartForm itemID="2" itemTitle="JavaScript: The Good Parts" /> </> ) }
- 위의 From 에서 add to cart 버튼 클릭시 addToCart 함수가 호출되며 각 input값에 대해 노출되는것이 아래 이미지를 통해 확인됩니다.
useFormStatus
./aptions.js import UsernameForm from './UsernameForm'; import { submitForm } from "./actions.js"; import {useRef} from 'react'; export default function App() { const ref = useRef(null); return ( <form ref={ref} action={async (formData) => { await submitForm(formData); ref.current.reset(); }}> <UsernameForm /> </form> ); } ------------------------------------------- import {useState, useMemo, useRef} from 'react'; import {useFormStatus} from 'react-dom'; export default function UsernameForm() { const {pending, data} = useFormStatus(); return ( <div> <h3>Request a Username: </h3> <input type="text" name="username" disabled={pending}/> <button type="submit" disabled={pending}> Submit </button> <br /> <p>{data ? `Requesting ${data?.get("username")}...`: ''}</p> </div> ); }
- action 시 이에 대한 loading 상태 제어 케이스 예시
"use client", "use server" 의 추가
이제 nextjs가 아닌 react에서도 동일하게 서버컴포넌트를 사용할수 있게 되도록 업데이트 예정입니다.
메타 태그 지원
React Helmet 을 통해 하던 메타 태그에 대한 작업을 자체적으로 지원하도록 추가될 예정 입니다. 물론 클라이언트,SSR , RSC를 포함한 모든 환경 전부 동일하게 작동합니다.
asset 데이터 로딩 처리
style, link, script 태그를 사용하여 폰트, css 와 같은 에셋 데이터 들에 대해서도 완전히 로드될떄까지 이에 대한 로딩 처리를 지원 하도록 preload, preinit 와 같은 api가 새로 추가됩니다.
- example
import { preload } from 'react-dom'; function AppRoot() { preload("https://example.com/font.woff2", {as: "font"}); }
참조
- https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024