╱╱╭╮╱╱╱╱╱╱╭━━━╮╱╱╱╭╮╱╭╮╱╱╱╱╱╱ ╱╱┃┃╱╱╱╱╱╱┃╭━╮┃╱╱╱┃┃╱┃┃╱╱╱╱╱╱ ╱╱┃┣━━┳━━╮┃┃╱┃┣━╮╱┃╰━╯┣━━┳━╮╱ ╭╮┃┃╭╮┃┃━┫┃╰━╯┃╭╮╮┃╭━╮┃╭╮┃╭╮╮ ┃╰╯┃╭╮┃┃━┫┃╭━╮┃┃┃┃┃┃╱┃┃╭╮┃┃┃┃ ╰━━┻╯╰┻━━╯╰╯╱╰┻╯╰╯╰╯╱╰┻╯╰┻╯╰╯

Javascript/React

[React] React Conference 2024 정리

재안안 2024. 5. 20. 21:45

 

최근에 React Compiler가 생겼다고 해서 관심을 갖고 있었다.

React Compiler에 대해 간략하게 설명하자면,
그동안 불필요한 리렌더링을 방지하려면 useMemo와 useCallback을 통해
컴포넌트의 props를 관리 했어야 하는데
React Compiler는 위의 조취를 자동으로 지원한다고 한다. 😲

 

그러다가 유튜브 알고리즘으로 React Conf 2024 Day1이 떴다.

 

새로나올 React 19의 기능에 대해 설명하는 conference라서 영상에선 React 19에 나오게될 기능들에 대해 설명한다.

궁금해서 봤고, 보면서 간단하게 정리를 하려고 한다.

 

https://www.youtube.com/watch?v=T8TZQ6k4SLE

 

영상이 워낙 길어서 조금씩 나눠서 글을 수정하며 정리하겠다.

영어에 자신있는 편은 아니라 오역이 있을 수 있다.

 

기존 React 18도 아직 익숙하진 않아 완전히 이해하진 못하지만 재밌어 보여서 가볍게 정리해 보겠다.

 

일단 필자에게 영상의 내용은 어려우며, 이글을 잘못된 점이 존재할 수 있는데,

누군가 지적을 해주거나

나중에 성장한 필자가 수정하면 좋겠다.

 

영상을 보면서 느끼는 점은 이런 내용들을 이해하려면 아직 갈 길이 많이 많이 남은것 같다이다.

아직은 그냥 다 어려워서 정리보단 번역하면서 이해하려고 노력한다..

 

[개요]

1. Vanilla React

2. What's new in React 19

3. React Unpacked: A Roadmap to React 19

4. Forget About Memo

5. React for Two Computers

6. Introducing Universal React Server Components in Expo Router

7. Real-time server components

8. React 19 Deep Dive: Coordinating HTML

9. Let’s break React’s rules

10. RedwoodJS, now with React Server Components!

 

 

1. Vanilla React

react-router에 대한 변경점이다. 실제로 발표도 react-router 팀에서 했다.

정확히 말하자면 React Router v7을 사용하면 React18 에서도 된다.

React Router v7 = remix features + vite plug-in

  • automatic code splitting
  • simplified data loading
  • form actinos, server actions
  • simplified pending states
  • optimistic UI
  • server rendering
  • static pre-rendering
  • RSC is coming

 

- code split

react router component를 사용하지 않고
vite config 파일에서 라우팅이(route module) 가능하다고 한다.

이렇게 하면, bundle size를 줄일 수 있다.

- simplified data loading / pending state

SPA에서 UI 렌더링에 필요한 데이터를 비동기 요청을 통해 가져올 때
그동안 데이터가 준비 안됐으면 다른 화면을 보여주다가
useEffect를 통해 state를 바꿔주는 작업을 했다면 (React 18)

{result && <Component/>}

이제는 비동기 요청 응답의 데이터를 그냥 state 값으로 바꿔주기만 하면 된다. (React 19)

const [content, setContent] = useState(API.getDate().result);
...
<Component content />

 

result가 없다면? blank screen (return null)

그냥 {result && <Component/>} 안해도 된다.

transitons are handled by react-router

 

추가적으로, 기존에는 react-router 내부에 16개의 state가 존재했다. (useState)
뭘 fetch하면 내부적으로 많은 state가 바뀌었는데 이제는 내부의 state가 없다.

 

- move rendering to server / static prerendering

vite config 파일에서 ssr을 true로 설정하고,

그냥 loader를 정의후 deployee 하면 된다. (이름 있어야 vite plug-in이 찾을 수 있다)

export function loader() {
    return (
        <div>. . .</div>
    );
}

 

vite config 파일로 static prerendering 또한 설정이 가능하다.

vite config에서 prerender에 라우팅될 컴포넌트의 주소를 반환하게 하는 것 같다.

 

2. What's new in React 19

React 19에 추가되는 새로운 기능들을 소개한다.

 

1. startTasntition, useTransition

// startTransition(async () => {
//     await updateDate();
// })

const [isPending, startTransition] = useTransition();

function handleData() {
    setUrgentState(urgentData);
    startTransition(() => {
        setNonUrgentState(nonUrgentData);
    });
}

 

이제 transition 처리할 때 비동기 함수를 넘겨줄 수 있다. (useEffect는 async 함수를 줄 수 없다)

 

원래 리액트는 necessary components를 모두 single uninterruptable task로 렌더링한다.

업데이트가 복잡해질수록, 리액트가 업데이트를 반영하고 DOM에 commit하기까지 시간이 길어졌다. 

위의 모든 작업이 main thread에서 진행되는 동안 app은 다른 task를 수행하지 못했다. (unresponsive  UI)

 

Transition을 사용하면 update를 single unterruptable task로 수행하는 대신,

리액트는 매 5 milis 마다 main thread를 확인해 처리해야할 task가 있는지 확인하게 된다.

(ex. user interaction)

task가 있다면, render를 잠시 멈추고 broswer가 해당 task를 처리하도록 기다려주게 된다.

추가적으로, update commit을 most performing time에만 한다고 한다.

 

Transition은 위와 같은 방식으로 responsive UI를 보장한다.

Transition hook은 current pending statestart transition function을 반환한다.

이제 요청의 pending state를 직접 처리하지 않아도 된다.

startTransition이 호출되면 pending state는 자동으로 true가 되고 응답이 오면 false가 된다.

 

⚠️ 그런데 아직은 startTransition의 사용에 제약이 있는데,

state update after await 키워드가 보장되어야 정상적으로 실행이 된다는 것이다.

그냥 사용하면 transition으로 mark가 안된다고 한다.

그래서 아직은 startTransition을 중첩해서 사용해야 한다.

async context가 available될 때까지 중첩해서 사용해야 한다는데 이부분은 아직 이해를 못했다.

React 19에서는 중첩없이 사용할 수 있다는 말인가? (아직 18)

startTransition(async () => {
    const date = await updateData(name); // 네트워크 요청 (POST)
    startTransition(() => {
        setDate(data);
    });
});

 

updateData가 여러번 호출되서 multiple transition이 생기면,

모든 transition들을 일괄적으로 처리해(batch)

모든 transition들이 처리한 후 한 번만 commit 한다. (Sinlge Paint)

 

2. useActionState

in React 19, functions trigger transitions are called Actions.

 

useState + startTransition

const [state, action, isPending] = useActionState(
    async (currentValue, formData) => {
        const name = formData.get('name');
        const data = await updateData(name);
        return data;
    },
    null,
);

 

훅의 첫번째 반환 값 state는 함수의 두번째 매개변수(initial state)이거나,

넘겨주는 콜백함수의 반환 값이다.

두번째 반환 값 action은 넘겨준 콜백함수이면서 transition을 trigger하는 함수이다. (호출시)

세번째 반환 값 isPending은 transition이 DOM으로 commit되면 false이다.

 

React DOM을 사용하면 action/formAction props를사용할 수 있다.

prop에는 모든 동기/비동기 함수를 줄 수 있으며, 모두 action으로 인식된다.

해당 action은 자동으로 form data를 받는다. 

<form action={action}>
<button formAction={action}>

 

3. useFormStatus

even easier way to get status information from the last submission

useFormSatus uses the action data from the parent form similar to how context work

 

아래와 같이, useFormStatus를 사용한 컴포넌트를 form 태그 안에만 넣어두면,

해당 action의 데이터로 바로 접근이 가능하다. 

사용자들에게 realtime feedback과 status update를 제공하기 쉬워진다.

 

해당 훅을 form을 생성하는 컴포넌트에 직접 사용하는 것이 아니라

자식 컴포넌트에서 사용해야한다.

function Form() {
    const [data, action] = useActionState( ... );
    return (
        <form action={action}>
            <input name="name">
            <StyledButton />
            <span>{data.success && "Changes saved!"}</span>
        </form>
    );
}

function StyledButton() {
    const { data, pending, method, action } = useFormStatus();
    return (
    <button style={ ... }>
        {pending ? "Updating..." : "Update"}
    </button>
    );
}

 

reusable form element를 만드는 것이 해당 훅의 사용 목적이라고 한다.

 

4. useOptimistic

Optimistically update the UI before action completes.

Show the data as action completed instead of showing pending state.

const [ allValues, addOptimisticValue ] = useOptimistic(
    state,
    updateFunction,
);

 

allValues는 initial state이거나 updateFunction의 반환 값(when pending)이다.

addOptimisticValue는 인자의 콜백함수이다.

function Messages({ messageFromServer }) {
    const [ allMessages, addOptimisticMessage ] = useOptimistic(
        messageFromServer,
        (currentState, optimisticValue) => {
            return [...currentState, optimisticValue];
        },
    );
    async function addMessage(formData) {
        addOptimisticMessage(formData.get("message"));
        await sendMessage(formData.get("message"));
        . . .
    };
    return (
        <ul>
            {allMesages.map(message => <Message {...message} />)}
        </ul>
        <form action={addMessage}>
            <input name="message" />
            <button type="submit">Send</button>
        </form>
    );
}

 

allMessages를 통해 요청이 pending인 상태에도 UI를 렌더링한다.

 

사용자가 form을 통해 새로운 message를 submit 하면,

action이 시작되는데,

addOptimisticMessage를 통해 방금 입력받은 message를

pending일 때도 UI에 렌더링할 수 있다.

action이 complete되면, allMessages가 update되고 rerender가 실행된다. (진짜 optimistic)

 

actions는 기본적으로 비동기로 처리되는데, useOptimistic는 이를 opt out하여 UI에 바로 반영시킨다.

 

5. Server Component

Server Component는 react rerender가 reactory client side를 무조건 rebuild 해야 한다.

React 18의 Server Component는 서버가 serialzed react component tree (JSON like format)을 클라이언트에게 전송하면,

client side renderer는 그걸 설명서 처럼 보고 HTML이나 JS의 도움 없이 rebuild하는 방식이다.

 

이때, Server Component 자체는 클라이언트에 절대 보내지지 않고,

서버에서 render후 return value가 보내지기 때문에 (JSON like format)

컴포넌트 내에서 서버 사이드 기능을 직접 사용할 수 있다.

this means we get to use server side functionality directly within our components like accessing databases and the file system and so on

 

React 19에서는 async 사용이 가능해졌고 async of weight를 사용할 수 있지만, payload는 그대로이다.

 

Data lookup이 오래 걸려도 fetch를 기다렸다가 react tree를 만들어서 클라이언트에게 보낸다.

Server Component를 Suspense로 감싸면,

it allows react to already stream the parts that were faster to the clients.

suspense를 제외한 react tree를 client에게 먼저 보내고 suspense를 stream 형식으로 보낸다는 말인거 같은데

suspense가 뭔지 몰라서 잘 모르겠다. 사실 Server Component로 한번도 안써봤다.

Server Component를 사용하면 import가 많을 때 bundle 사이즈를 줄일 수 있고, data fetch에 유용하다는 것, 실제 Server Side Rendering이 아니라는 것 말고는 잘 모른다.

 

import db from "./database";

async function Posts() {
    const posts = await db.posts.findMany();
    return(
        <ul>
            {posts.map(post => <li>{post.title}</li>)}
        </ul>
    );
}

export default function Page() {
    function(
    <Layout>
        <Header />
        <Suspense fallback={<Header />}>
            <Posts />
        </Suspense>
        <Footer />
    </Layout>

    )
}

 

6. use client, useAPI

기존의 Server Component는 server에서만 렌더되고 바로 JSON으로 변환되기 때문에

client side functionality를 사용할 수 없었다. (hooks, handlers, window등)

 

이제 use client diretive를 사용하면 번들러에서 

client side functionality를 bundle에 추가하고 imports를 포함할 수 있다.

this directive adds the components and its imports to the bundle that chipped client side.

그러면 해당 컴포넌트는 서버가 아닌 클라이언트에서 렌더링된다. 

이렇게 하면 클라이언트에게 return value가 아닌 컴포넌트가 반환되는데,

server component와 같은 방식으로 데이터를 fetch 할 수 없게 된다.

 

기존 server component의 데이터 전달은 fetch후 자식 컴포넌트에게 props로 전달했는데 (React 18)

이젠, use API를 통해 전달할 수 있다. (React 19)

 

아래의 예제에선 클라이언트에게 fetch한 데이터를 주는게 아니라 promise 자체를 준다.

클라이언트에서 promise를 use API에게 전달하면

자동으로 integrates with suspense and error boundary 된다고 한다.

promise가 resolve되면, suspense fallback이 실제 컴포넌트로 바뀌고

reject되면, error boundary가 렌더링된다.

 

async function Comment() {
    const comment = await db.post.getComment();
    const reactionPromise = db.comment.getReactions(comment.id);
    return(
        <div>
            <p>{comment.text}</p>
            <Reactions reactionsPromise={reactionsPromise} />
        </div>
    )
}
'use client'

function Reactions({ reactionsPromise }) {
    const reactions = use(reactionsPromise);
    return(
        <div onClick={ . . . } />
            {reactions.map(reaction => <Reaction {...reaction} />)}
        </div>
    );
}

 

use API를 사용하면, 나머지 서버 컴포넌트는 해당 데이터를 기다릴 필요 없이

클라이언트로 stream된다.

we can also use the use API with context which essentially replaces the use context hook

추가적으로 useAPI를 통해 use context hook을 대체하여 사용할 수 있다.

같은 용도인데 hook은 early return 할 수 없지만 useAPI는 된다.

function ToolTip({ show }) {
    if (show) {
        const theme = use(ThemeContext);
        return <ToolTip theme={theme} />
    }
    return null;
}

 

7. use server

 

8. ref as props

 

9. render stylesheet directly withi the component

 

10. preload API

'Javascript > React' 카테고리의 다른 글

[React] map() 관련 번들 사이즈 줄이기  (0) 2024.07.07
[React] Custom Hooks  (0) 2023.09.18
[React] React.memo, useCallback, useMemo, Closure  (0) 2023.09.09