useOptimistic - This feature is available in the latest Canary

Canary

useOptimistic Hook์€ ํ˜„์žฌ React์˜ Canary ๋ฐ ์‹คํ—˜์ ์ธ ์ฑ„๋„์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. React์˜ ๋ฆด๋ฆฌ์Šค ์ฑ„๋„์— ๋Œ€ํ•œ ์ •๋ณด.

useOptimistic ๋Š” UI๋ฅผ ๋‚™๊ด€์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” React Hook์ž…๋‹ˆ๋‹ค.

const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);

๋ ˆํผ๋Ÿฐ์Šค

useOptimistic(state, updateFn)

useOptimistic์€ React Hook์œผ๋กœ, ๋น„๋™๊ธฐ ์ž‘์—…์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ๋‹ค๋ฅธ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ธ์ž๋กœ ์ฃผ์–ด์ง„ ์ผ๋ถ€ ์ƒํƒœ๋ฅผ ๋ฐ›์•„, ๋„คํŠธ์›Œํฌ ์š”์ฒญ๊ณผ ๊ฐ™์€ ๋น„๋™๊ธฐ ์ž‘์—… ๊ธฐ๊ฐ„ ๋™์•ˆ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋Š” ๊ทธ ์ƒํƒœ์˜ ๋ณต์‚ฌ๋ณธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒํƒœ์™€ ์ž‘์—…์˜ ์ž…๋ ฅ์„ ์ทจํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๊ณ , ์ž‘์—…์ด ๋Œ€๊ธฐ ์ค‘์ผ ๋•Œ ์‚ฌ์šฉํ•  ๋‚™๊ด€์ ์ธ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ƒํƒœ๋Š” โ€œ๋‚™๊ด€์ โ€ ์ƒํƒœ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”๋ฐ, ์‹ค์ œ๋กœ ์ž‘์—…์„ ์™„๋ฃŒํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋”๋ผ๋„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰์‹œ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

import { useOptimistic } from 'react';

function AppContainer() {
const [optimisticState, addOptimistic] = useOptimistic(
state,
// updateFn
(currentState, optimisticValue) => {
// merge and return new state
// with optimistic value
}
);
}

์•„๋ž˜์— ๋” ๋งŽ์€ ์˜ˆ์ œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • state: ์ž‘์—…์ด ๋Œ€๊ธฐ ์ค‘์ด์ง€ ์•Š์„ ๋•Œ ์ดˆ๊ธฐ์— ๋ฐ˜ํ™˜๋  ๊ฐ’์ž…๋‹ˆ๋‹ค.
  • updateFn(currentState, optimisticValue): ํ˜„์žฌ ์ƒํƒœ์™€ addOptimistic์— ์ „๋‹ฌ๋œ ๋‚™๊ด€์ ์ธ ๊ฐ’์„ ์ทจํ•˜๋Š” ํ•จ์ˆ˜๋กœ, ๊ฒฐ๊ณผ์ ์ธ ๋‚™๊ด€์ ์ธ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ ํ•จ์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. updateFn์€ ๋‘ ๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ทจํ•ฉ๋‹ˆ๋‹ค. currentState์™€ optimisticValue. ๋ฐ˜ํ™˜ ๊ฐ’์€ currentState์™€ optimisticValue์˜ ๋ณ‘ํ•ฉ๋œ ๊ฐ’์ž…๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜

  • optimisticState: ๊ฒฐ๊ณผ์ ์ธ ๋‚™๊ด€์ ์ธ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์ž‘์—…์ด ๋Œ€๊ธฐ ์ค‘์ด์ง€ ์•Š์„ ๋•Œ๋Š” state์™€ ๋™์ผํ•˜๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ updateFn์—์„œ ๋ฐ˜ํ™˜๋œ ๊ฐ’๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • addOptimistic: addOptimistic๋Š” ๋‚™๊ด€์ ์ธ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์„ ๋•Œ ํ˜ธ์ถœํ•˜๋Š” ๋””์ŠคํŒจ์น˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์–ด๋– ํ•œ ํƒ€์ž…์˜ optimisticValue๋ผ๋Š” ํ•˜๋‚˜์˜ ์ธ์ž๋ฅผ ์ทจํ•˜๋ฉฐ, state์™€ optimisticValue๋กœ updateFn์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

ํผ์„ ๋‚™๊ด€์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

useOptimistic Hook์€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ๊ณผ ๊ฐ™์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋‚™๊ด€์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํผ์˜ ๋งฅ๋ฝ์—์„œ, ์ด ๊ธฐ์ˆ ์€ ์•ฑ์ด ๋” ๋ฐ˜์‘์ ์œผ๋กœ ๋Š๊ปด์ง€๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํผ์„ ์ œ์ถœํ•  ๋•Œ, ์„œ๋ฒ„์˜ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋Œ€์‹  ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฒฐ๊ณผ๋กœ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ํผ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜๊ณ  โ€œ์ „์†กโ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, useOptimistic Hook์€ ๋ฉ”์‹œ์ง€๊ฐ€ ์‹ค์ œ๋กœ ์„œ๋ฒ„๋กœ ์ „์†ก๋˜๊ธฐ ์ „์— โ€œ์ „์†ก ์ค‘โ€ฆโ€ ๋ผ๋ฒจ์ด ์žˆ๋Š” ๋ชฉ๋ก์— ๋ฉ”์‹œ์ง€๊ฐ€ ์ฆ‰์‹œ ๋‚˜ํƒ€๋‚˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด โ€œ๋‚™๊ด€์ โ€ ์ ‘๊ทผ๋ฒ•์€ ์†๋„์™€ ๋ฐ˜์‘์„ฑ์˜ ๋Š๋‚Œ์„ ์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํผ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์‹ค์ œ๋กœ ์ „์†กํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•˜์Œ์„ ํ™•์ธํ•˜๋ฉด, โ€œ์ „์†ก ์ค‘โ€ฆโ€ ๋ผ๋ฒจ์ด ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

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} />;
}