Góc của Sơn

    những chuyện 'linh tinh'

    Quay lại

    React 19: Tổng quan về những thay đổi và tính năng mới

    logo

    Hoàng Sơn

    Đăng ngày:26/09/2024 lúc 8:11 AM- lượt xem

    thumbail

    React 19 (React 19 Release Candidate ) vừa được ra mắt, nhiều tính năng đã được giới thiệu ở dạng thử nghiệm trong React 18, nhưng đã được release ổn định trong React 19 này.

    Dưới đây là cái nhìn tổng quan về những gì mà mình tìm hiểu và tổng hợp được.

    1. Server Components

    Server Components có thay đổi lớn nhất, là nền tảng cho các tính nắng mới của React 19, mang lại sự cải tiến đáng kể về:

    • Initial page load times: Bằng cách render các component trên server, giảm tải JavaScript gửi đến client, dẫn đến việc tải trang ban đầu nhanh hơn. Đồng thời, chúng cho phép các truy vấn dữ liệu được thực hiện trên server trước khi trang được gửi đến client.

    • Code portability: dev có thể viết các component có thể chạy cả trên server và client, giảm việc dư thừa code, cải thiện khả năng bảo trì và share logic dễ dàng hơn trong toàn bộ codebase.

    • SEO: Việc render component trên server giúp các công cụ tìm kiếm có thể thu thập và lập index content hiệu quả hơn, từ đó cải thiện khả năng tối ưu hóa tìm kiếm.

    Với Server-Side Rendering (SSR), quá trình render sẽ được thực hiện ở server. HTML được gửi đến client không còn là một empty page nữa, giúp cải thiện tốc độ mà client nhìn thấy giao diện ban đầu. Tuy nhiên vẫn cần đi fetch dữ liệu để render content

    parsed-mdx-image

    Với các khái niệm như SSG (cache và render dữ liệu ở build-time), ISR (cache và render dữ liệu động theo nhu cầu) giúp chúng ta có thể fetch dữ liệu trước khi giao diện được render cho client.

    parsed-mdx-image

    export default async function ProductList() {
    
    	const res = await fetch("https://api.example.com/products");
    
    	const products = res.json();
    
    	return (
    		<>
    			<h1>Products</h1>
    				{products.map((product) => (
    					<div key={product.id}>
    						<h2>{product.title}</h2>
    						<p>{product.description}</p>
    					</div>
    			))}
    		</>
    	);
    }
    

    2. New directives

    Với React Server Components, các bundler cần phân biệt nơi các component và hàm chạy. Để làm được điều này, có hai chỉ thị mới cần lưu ý khi tạo các React component:

    • 'use client': code chỉ chạy trên client. Bạn cần thêm 'use client' vào Client Components khi sử dụng hooks và quản lý state.

    • 'use server' những server-side function có thể được gọi từ phía client, không cần phải thêm 'use server' vào Server Components, chỉ cần thêm vào các Server Actions

    Tham khảo thêm ở đây nhé Directives.

    3. Actions

    Các hàm này thay thế việc sử dụng event handlers và tích hợp với các tính năng transitionsconcurrent features của React.

    Actions có thể được sử dụng cả ở phía client và server

    3.1 Server Action

    Server Actions cho phép các Client Components gọi các hàm async được thực thi trên server. Điều này tiện cho việc call API fetch data, loại bỏ nhu cầu tạo các API endpoint riêng biệt cho UI

    Actions được định nghĩa với 'use server' và tích hợp với các component ở phía client.

    Để gọi một Server Action trong một Client Component, tạo một file mới và import nó vào.

    'use server'
    
    export async function create() {
    	// Insert into database
    }
    
    "use client";
    
    import { create } from "./actions";
    
    export default function TodoList() {
    
    	return (
    		<>
    			<h1>Todo List</h1>
    			<form action={create}>
    				<input type="text" name="item" placeholder="Add todo..." />
    				<button type="submit">Add</button>
    			</form>
    		</>
    	);
    }
    

    Tham khảo thêm ở đây nhé ^^ Server Actions.

    4. New Hooks

    React 19 giới thiệu ba hooks mới giúp việc quản lý state, status... dễ dàng hơn.

    Các hooks này chủ yếu làm việc với form, nhưng cũng có thể sử dụng cho các element khác như button, etc...

    4.1 useActionState

    • useActionState hook đơn giản hóa việc quản lý trạng thái form và việc submit form. Khi sử dụng Actions, hook này thu thập dữ liệu của form, xử lý việc validate và error state, giúp giảm bớt sự cần thiết phải viết logic quản lý state tùy chỉnh.

    • Hook useActionState cũng cung cấp một trạng thái pending, cho phép hiển thị chỉ (loading indicator) trong khi action đang được thực thi.

    "use client";
    
    import { useActionState } from "react";
    import { createUser } from "./actions";
    
    const initialState = {
      message: "",
    };
    
    export function Signup() {
      const [state, formAction, pending] = useActionState(createUser, initialState);
    
      return (
        <form action={formAction}>
          <label htmlFor="email">Email</label>
          <input type="text" id="email" name="email" required />
          {/* ... */}
          {state?.message && <p aria-live="polite">{state.message}</p>}
          <button aria-disabled={pending} type="submit">
            {pending ? "Submitting..." : "Sign up"}
          </button>
        </form>
      );
    }
    

    Tham khảo thêm về useActionState ở đây.

    4.2 useFormStatus

    useSubmissionStatus hook quản lý trạng thái của lần gửi form cuối cùng, và nó phải được gọi từ bên trong một component, mà component đó cũng phải nằm trong một form. Hook này giúp theo dõi quá trình submit form, trả về kết quả của submit form (thành công hay thất bại) và có thể sử dụng để hiển thị thông báo cho người dùng.

    import { useFormStatus } from "react-dom";
    import action from "./actions";
    
    function Submit() {
      const status = useFormStatus();
      return <button disabled={status.pending}>Submit</button>;
    }
    
    export default function App() {
      return (
        <form action={action}>
          <Submit />
        </form>
      );
    }
    

    Một vài lợi ích khi sử dụng hook trên:

    • Khi bạn không cần quản lý trạng thái form, nhưng vẫn cần theo dõi và xử lý trạng thái khi submit form.
    • Tạo các shared component form: Khi cần tạo các component form có thể tái sử dụng và chia sẻ giữa các phần khác nhau của ứng dụng.
    • Có nhiều form trên cùng một trang: useFormStatus chỉ trả về info cho form cha (parent form), giúp quản lý nhiều form mà không cần phải theo dõi tất cả các trạng thái một cách riêng biệt.

    Tham khảo thêm về useFormStatus ở đây.

    4.3 useOptimistic

    useOptimisticState hook cho phép bạn cập nhật UI một cách lạc quan (optimistically ^^) trước khi Server Action hoàn thành việc thực thi, thay vì phải đợi response từ server. Khi async action hoàn thành, giao diện người dùng sẽ được cập nhật với trạng thái cuối cùng từ server.

    Ví dụ, việc thêm một tin nhắn mới vào một room ngay lập tức, trong khi tin nhắn cũng được gửi đến Server Action để lưu trữ dữ liệu:

    "use client";
    
    import { useOptimistic } from "react";
    import { send } from "./actions";
    
    export function Thread({ messages }) {
      const [optimisticMessages, addOptimisticMessage] = useOptimistic(
        messages,
        (state, newMessage) => [...state, { message: newMessage }],
      );
    
      const formAction = async (formData) => {
        const message = formData.get("message") as string;
        addOptimisticMessage(message);
        await send(message);
      };
    
      return (
        <div>
          {optimisticMessages.map((m, i) => (
            <div key={i}>{m.message}</div>
          ))}
          <form action={formAction}>
            <input type="text" name="message" />
            <button type="submit">Send</button>
          </form>
        </div>
      );
    }
    

    Tham khảo thêm về useOptimistic ở đây.

    5. New Api: use

    Hàm use cung cấp first-class support cho các promise và context trong quá trình render. Không giống như các React Hooks khác, use có thể được gọi trong các vòng lặp, câu lệnh điều kiện và các return sớm. Việc xử lý lỗi và trạng thái loading sẽ được quản lý bởi boundary Suspense gần nhất.

    Ví dụ dưới đây hiển thị một thông báo loading trong khi promise lấy danh sách sản phẩm trong giỏ hàng được giải quyết.

    import { use } from "react";
    
    function Cart({ cartPromise }) {
      // `use` will suspend until the promise resolves
      const cart = use(cartPromise);
      return cart.map((item) => <p key={item.id}>{item.title}</p>);
    }
    
    function Page({ cartPromise }) {
      return (
        /*{ ... }*/
        // When `use` suspends in Cart, this Suspense boundary will be shown
        <Suspense fallback={<div>Loading...</div>}>
          <Cart cartPromise={cartPromise} />
        </Suspense>
      );
    }
    

    Điều này cho phép bạn nhóm các component lại với nhau để chúng chỉ được render khi tất cả dữ liệu của các component đó đã sẵn sàng.

    Tham khảo thêm về use ở đây.

    6. Preloading Resources

    Một số API mới nhằm cải thiện hiệu suất tải trang và trải nghiệm người dùng bằng cách loading và preloading các tài nguyên như script, stylesheet, và font.

    • prefetchDNS preloading địa chỉ IP của một tên miền DNS mà bạn dự kiến sẽ kết nối.
    • preconnect kết nối đến một server mà bạn dự kiến sẽ yêu cầu các resources, ngay cả khi các resources chính xác chưa được xác định tại thời điểm đó.
    • preload tải một stylesheet, font, hình ảnh, hoặc external script mà bạn dự kiến sẽ sử dụng.
    • preloadModule tải một ESM module mà bạn dự kiến sẽ sử dụng.
    • preinit load và đánh giá một external script hoặc tải và chèn một stylesheet.
    • preinitModule tải và đánh giá một ESM module.

    Ví dụ, đoạn mã React dưới đây sẽ tạo ra HTML đầu ra sau đây. Lưu ý rằng các liên kết và script được ưu tiên và sắp xếp theo thứ tự mà chúng nên được tải sớm, không phải theo thứ tự mà chúng được sử dụng trong React.

    // React code
    import { prefetchDNS, preconnect, preload, preinit } from "react-dom";
    
    function MyComponent() {
      preinit("https://.../path/to/some/script.js", { as: "script" });
      preload("https://.../path/to/some/font.woff", { as: "font" });
      preload("https://.../path/to/some/stylesheet.css", { as: "style" });
      prefetchDNS("https://...");
      preconnect("https://...");
    }
    
    <!-- Resulting HTML -->
    <html>
      <head>
        <link rel="prefetch-dns" href="https://..." />
        <link rel="preconnect" href="https://..." />
        <link rel="preload" as="font" href="https://.../path/to/some/font.woff" />
        <link
          rel="preload"
          as="style"
          href="https://.../path/to/some/stylesheet.css"
        />
        <script async="" src="https://.../path/to/some/script.js"></script>
      </head>
      <body>
        <!-- ... -->
      </body>
    </html>
    

    Tìm hiểu thêm về Resource Preloading APIs ở đây.

    7. Other improvements

    7.1 Ref as props

    Không còn cần phải sử dụng forwardRef nữa. React sẽ cung cấp một codemod để giúp quá trình chuyển đổi trở nên dễ dàng hơn.

    function CustomInput({ placeholder, ref }) {
      return <input placeholder={placeholder} ref={ref} />;
    }
    
    // ...
    
    <CustomInput ref={ref} />;
    

    7.2 Ref callbacks

    Ngoài việc sử dụng ref như một prop, ref còn có thể trả về một hàm callback để thực hiện dọn dẹp. Khi một component bị unmount, React sẽ gọi hàm dọn dẹp đó.

    <input
      ref={(ref) => {
        // ref created
    
        // Return a cleanup function to reset
        // ref when element is removed from DOM.
        return () => {
          // ref cleanup
        };
      }}
    />;
    

    7.3 Context as provider

    Không còn cần <Context.Provider> nữa. Bạn có thể sử dụng <Context> trực tiếp thay vào đó. React sẽ cung cấp một codemod để chuyển đổi các provider hiện tại.

    const ThemeContext = createContext("");
    
    function App({ children }) {
      return <ThemeContext value="dark">{children}</ThemeContext>;
    }
    

    7.4 useDeferredValue

    Khi được cung cấp, useDeferredValue sẽ sử dụng giá trị đó cho lần render ban đầu và lên lịch re-render background, trả về deferredValue.

    function Search({ deferredValue }) {
      // On initial render the value is ''.
      // Then a re-render is scheduled with the deferredValue.
      const value = useDeferredValue(deferredValue, "");
    
      return <Results value={value} />;
    }
    

    7.5 Document metadata support

    React 19 sẽ tự động nâng cao và render các thẻ title, link, và meta, ngay cả từ các component lồng nhau. Không cần phải sử dụng các giải pháp bên thứ ba (React helmet, etc...) để quản lý các thẻ này nữa.

    function BlogPost({ post }) {
      return (
        <article>
          <h1>{post.title}</h1>
          <title>{post.title}</title>
          <meta name="author" content="Jane Doe" />
          <link rel="author" href="https://x.com/janedoe" />
          <meta name="keywords" content={post.keywords} />
          <p>...</p>
        </article>
      );
    }
    

    7.6 Stylesheet support

    React 19 hỗ trợ quản lý stylesheets trực tiếp, giúp kiểm soát thứ tự và tránh trùng lặp khi chèn stylesheet vào DOM.

    Bạn có thể kiểm soát thứ tự chèn stylesheets bằng thuộc tính precedence. React đảm bảo các stylesheet có độ ưu tiên cao sẽ ghi đè stylesheet khác.

    function ComponentOne() {
      return (
        <Suspense fallback="loading...">
          <link rel="stylesheet" href="one" precedence="default" />
          <link rel="stylesheet" href="two" precedence="high" />
          <article>...</article>
        </Suspense>
      );
    }
    
    function ComponentTwo() {
      return (
        <div>
          <p>...</p>
          {/* Stylesheet "three" below will be inserted between "one" and "two" */}
          <link rel="stylesheet" href="three" precedence="default" />
        </div>
      );
    }
    

    7.7 Async scripts support

    React 19 hỗ trợ việc render các script bất đồng bộ trong bất kỳ component nào. Điều này giúp việc colocating các script gần các component trở nên dễ dàng hơn, và React chỉ tải chúng khi chúng được sử dụng.

    Có một vài điểm cần lưu ý:

    • Nếu bạn render cùng một component ở nhiều nơi trong ứng dụng, React sẽ loại bỏ trùng lặp script và chỉ bao gồm nó một lần trong document.
    • Khi render phía server, các script bất đồng bộ sẽ được bao gồm trong thẻ <head> và được ưu tiên tải sau các tài nguyên quan trọng hơn như stylesheets, fonts và các preload hình ảnh, vì chúng có thể ngừng quá trình paint của trang.
    function Component() {
      return (
        <div>
          <script async={true} src="..." />
          // ...
        </div>
      );
    }
    
    function App() {
      return (
        <html>
          <body>
            <Component>
              // ...
            </Component> // Won't duplicate script in the DOM
          </body>
        </html>
      );
    }
    

    8. Kết luận

    • React 19 RC mang đến nhiều tính năng mới và cải tiến đáng chú ý, hứa hẹn cải thiện đáng kể hiệu quả cho các dự án Frontend.
    • Hy vọng bài viết của mình đã cung cấp cái nhìn toàn diện về các thay đổi trong React 19.
    • Để tận dụng tối đa các tính năng mới này, hãy thử trải nghiệm và đánh giá nhé!

    Cảm ơn đã theo dõi, see ya !

    React19

    React

    Frontend

    Danh mục:

    Frontend ,ReactJS

    logo

    Hoàng Sơn

    Cảm ơn bạn đã dành thời gian đọc qua bài viết trên của mình, nếu có bất kỳ câu hỏi gì, thì cứ nhắn tin cho mình nhé. Hi vọng mình đã giúp ích cho bạn 'một phần nào đấy'.

    Có 0 bình luận ở bài viết này

    Bình luận

    Email của bạn sẽ không được hiển thị công khai. Các trường có dấu * là bắt buộc.