當前位置: 妍妍網 > 碼農

React 19 即將推出的 4 個全新 Hooks,很實用!

2024-01-26碼農

近日,React 團隊成員在社交平台表示,團隊正在開發 React v19 版本,並且沒有計劃釋出 v18.3 版本。

React 19 預計將推出 4 個全新 Hooks,這些 Hooks 主要關註 React 中的兩個痛點: 數據獲取 表單。 這些 Hooks 目前在 React 預覽版本中作為實驗性 API 提供,預計會成為 React 19 的一部份,但是最終釋出之前,API 可能會有所變化。

新的 Hooks 包括:

  • use

  • useOptimistic

  • useFormState

  • useFormStatus

  • use

    use 是一個實驗性 React Hook,它可以讓讀取類似於 Promise 或 context 的資源的值。

    const value =use(resource);

    官方文件 https://zh-hans.react.dev/reference/react/use

    use(Promise)

    use 可以在客戶端進行「掛起」的 API。可以將一個 promise 傳遞給它,React 將會在該 promise 解決之前進行掛起。它的基本語法如下:

    import{ use }from'react';
    functionMessageComponent({ messagePromise }){
    const message =use(messagePromise);
    // ...
    }

    下面來看一個簡單的例子:

    import*as React from'react';
    import{ useState, use, Suspense }from'react';
    import{ faker }from'@faker-js/faker';
    exportconstApp=()=>{
    const[newsPromise, setNewsPromise]=useState(()=>fetchNews());
    consthandleUpdate=()=>{
    fetchNews().then((news)=>{
    setNewsPromise(Promise.resolve(news));
    });
    };
    return(
    <>
    <h3>
    新聞列表
    <button onClick={handleUpdate}>重新整理</button>
    </h3>
    <NewsContainer newsPromise={newsPromise}/>
    </>
    );
    };
    let news =[...newArray(4)].map(()=> faker.lorem.sentence());
    constfetchNews=()=>
    newPromise<string[]>((resolve)=>
    // 使用 setTimeout 模擬數據獲取
    setTimeout(()=>{
    // 每次重新整理時添加一個標題
    news.unshift(faker.lorem.sentence());
    resolve(news);
    },1000)
    );
    constNewsContainer=({ newsPromise })=>(
    <Suspense fallback={<p>請求中...</p>}>
    <News newsPromise={newsPromise}/>
    </Suspense>
    );
    constNews=({ newsPromise })=>{
    const news = use<string[]>(newsPromise);
    return(
    <ul>
    {news.map((title, index)=>(
    <li key={index}>{title}</li>
    ))}
    </ul>
    );
    };

    在上面的例子中,每次重新整理時,都會先顯示「請求中...」,請求到數據後進行展示:

    官方文件中,關於 <Suspense> 有一個警告:

    目前尚不支持在不使用固定框架的情況下進行啟用 Suspense 的數據獲取。實作支持 Suspense 資料來源的要求是不穩定的,也沒有文件。React 將在未來的版本中釋出官方 API,用於與 Suspense 整合資料來源。

    在新版本中, use 可能就是用於與 Suspense 整合資料來源的官方 API。

    這個全新的 use hook 與其他 Hooks 不同,它可以在迴圈和條件語句中像 if 一樣被呼叫。這意味著我們可能不再需要依賴像 TanStack Query 這樣的第三方庫在客戶端進行數據獲取。然而,這仍需進一步觀察,因為 Tanstack Query 的功能遠不止解析 Promise 這麽簡單。

    use(Context)

    這個 use hook 也可以用來讀取 React Context。它與 useContext 作用完全相同,只是可以在迴圈(如 for)和條件語句(如 if)中呼叫。

    import{ use }from'react';
    functionHorizontalRule({ show }){
    if(show){
    const theme =use(ThemeContext);
    return<hr className={theme}/>;
    }
    returnfalse;
    }

    這將簡化某些場景下的元件層級結構,因為在迴圈或條件語句中讀取 context ,之前唯一的方法就是將元件一分為二。

    在效能方面,這一改進也是巨大的進步,因為現在即使 context 發生變化,我們也可以有條件地跳過元件的重新渲染。

    useOptimistic

    useOptimistic Hook 允許在進行送出動作的同時,能夠樂觀地更新使用者介面,提升使用者體驗。其語法如下:

    import{ useOptimistic }from'react';
    functionAppContainer(){
    const[optimisticState, addOptimistic]=useOptimistic(
    state,
    // 更新函式
    (currentState, optimisticValue)=>{
    // 合並並返回帶有樂觀值的新狀態
    },
    );
    }

    樂觀更新 :一種更新應用程式中數據的策略。這種策略通常會先更改前端頁面,然後向伺服器發送請求,如果請求成功,則結束操作;如果請求失敗,則頁面回滾到先前狀態。這種做法可以防止新舊數據之間的跳轉或閃爍,提供更快的使用者體驗。

    下面來看一個添加購物車的例子:

    import{ useState, useOptimistic }from'react';
    constAddToCartForm=({ id, title, addToCart, optimisticAddToCart })=>{
    constformAction=async(formData)=>{
    optimisticAddToCart({ id, title });
    try{
    awaitaddToCart(formData, title);
    }catch(e){
    // 捕獲錯誤
    }
    };
    return(
    <form action={formAction}>
    <h2>{title}</h2>
    <input type="hidden" name="itemID" value={id}/>
    <button type="submit">添加到購物車</button>
    </form>
    );
    };
    type Item ={
    id: string;
    title: string;
    };
    constCart=({ cart }:{cart: Item[]})=>{
    if(cart.length ==0){
    returnnull;
    }
    return(
    <>
    購物車:
    <ul>
    {cart.map((item, index)=>(
    <li key={index}>{item.title}</li>
    ))}
    </ul>
    <hr />
    </>
    );
    };
    exportconstApp=()=>{
    const[cart, setCart]= useState<Item[]>([]);
    const[optimisticCart, optimisticAddToCart]= useOptimistic<Item[], Item>(
    cart,
    (state, item)=>[...state, item]
    );
    constaddToCart=async(formData: FormData, title)=>{
    const id =String(formData.get('itemID'));
    awaitnewPromise((resolve)=>setTimeout(resolve,1000));
    setCart((cart: Item[])=>[...cart,{ id, title }]);
    return{ id };
    };
    return(
    <>
    <Cart cart={optimisticCart}/>
    <AddToCartForm
    id="1"
    title="JavaScript權威指南"
    addToCart={addToCart}
    optimisticAddToCart={optimisticAddToCart}
    />
    <AddToCartForm
    id="2"
    title="JavaScript高級程式設計"
    addToCart={addToCart}
    optimisticAddToCart={optimisticAddToCart}
    />
    </>
    );
    };

    在上面的例子中,將商品添到購物車時,會先在購物車列表看到剛剛添加的商品,而不必等到數據請求完成。這樣,使用者可以更快地看到更新後的購物車內容,提供更加流暢的使用者體驗。

    useFormState

    在介紹這個 Hook 之前,先來看以下這個 Hook 使用的背景。

    <form>

    React 將引入一個新元件: <form> ,它是建立用於送出資訊的互動式控制項,可以將一個函式作為 action 的內容值。當使用者送出表單時,React 將自動呼叫此函式,以執行相應的操作。

    <form action={handleSubmit}/>

    註意,如果在 React 18 中添加 <form action> 內容,就會收到警告:

    ⚠️ Warning: Invalid value for prop action on <form> tag. Either remove it from the element or pass a string or number value to keep it in the DOM.

    這裏的意思是, <form> 標簽上的 prop action 無效。要麽從元素中刪除它,要麽傳遞一個字串或數位值以將其保留在 DOM 中。

    而在新版本中,可以直接在 <form> 標簽上設定 action 內容。例如,在上面的購物車例子中,:

    constAddToCartForm=({ id, title, addToCart })=>{
    constformAction=async(formData)=>{
    try{
    awaitaddToCart(formData, title);
    }catch(e){
    // 捕獲錯誤
    }
    };
    return(
    <form action={formAction}>
    <h2>{title}</h2>
    <input type="hidden" name="itemID" value={id}/>
    <button type="submit">添加到購物車</button>
    </form>
    );
    };

    addToCart 函式並不是在伺服器端執行的,而是在客戶端(例如使用者的瀏覽器)上執行的。這個函式可以是一個異步函式,如網路請求,而不阻止其他程式碼的執行。透過使用 addToCart 函式,開發者可以更簡單地處理React中的AJAX表單,例如在搜尋表單中。然而,這可能還不足以完全擺脫像 React Hook Form 這樣的第三方庫,因為它們不僅處理表單送出,還包括驗證、副作用等多種功能。

    看完這個新功能,下面就來看看這一部份要介紹的新 Hook: useFormState

    useFormState

    useFormState 是一個可以根據某個表單動作的結果更新 state 的 Hook。

    const[state, formAction]=useFormState(fn, initialState);

    只有在表單送出觸發 action 後才會被更新的值,如果該表單沒有被送出,該值會保持傳入的初始值不變。

    例如,這可以用來顯示由表單操作返回的確認訊息或錯誤訊息。

    import{ useState }from'react';
    import{ useFormState }from'react-dom';
    constAddToCartForm=({ id, title, addToCart })=>{
    constaddToCartAction=async(prevState, formData)=>{
    try{
    awaitaddToCart(formData, title);
    return'添加成功';
    }catch(e){
    return"添加失敗:賣完啦";
    }
    };
    const[message, formAction]=useFormState(addToCartAction,null);
    return(
    <form action={formAction}>
    <h2>{title}</h2>
    <input type="hidden" name="itemID" value={id}/>
    <button type="submit">添加到購物車</button>&nbsp;
    {message}
    </form>
    );
    };
    type Item ={
    id: string;
    title: string;
    };
    exportconstApp=()=>{
    const[cart, setCart]= useState<Item[]>([]);
    constaddToCart=async(formData: FormData, title)=>{
    const id =String(formData.get('itemID'));
    awaitnewPromise((resolve)=>setTimeout(resolve,1000));
    if(id ==='1'){
    setCart((cart: Item[])=>[...cart,{ id, title }]);
    }else{
    thrownewError('Unavailable');
    }
    return{ id };
    };
    return(
    <>
    <AddToCartForm
    id="1"
    title="JavaScript權威指南"
    addToCart={addToCart}
    />
    <AddToCartForm
    id="2"
    title="JavaScript高級程式設計"
    addToCart={addToCart}
    />
    </>
    );
    };

    效果如下:

    註意 useFormState 需要從 react-dom 中匯入,而不是從 react 中匯入。

    useFormStatus

    useFormStatus 用於獲取上次表單送出的狀態資訊。

    const{ pending, data, method, action }=useFormStatus();

    它不接收任何參數,會返回一個包含以下內容的 status 物件:

  • pending :布爾值。如果為 true ,則表示父級 <form> 正在等待送出;否則為 false。

  • data :包含父級 <form> 正在送出的數據;如果沒有進行送出或沒有父級 <form> ,它將為 null

  • method :字串,可以是 'get' 或 'post'。表示父級 <form> 使用 GET 或 POST HTTP 方法 進行送出。預設情況下, <form> 將使用 GET 方法,並可以透過 method 內容指定。

  • action :一個傳遞給父級 <form> action 內容的函式參照。如果沒有父級 <form> ,則該內容為 null 。如果在 action 內容上提供了 URI 值,或者未指定 action 內容, status.action 將為 null

  • 下面來繼續看購物車的例子,將商品添加到購物車成功前,禁用添加按鈕:

    import{ useState }from'react';
    import{ useFormStatus }from'react-dom';
    constAddToCartForm=({ id, title, addToCart })=>{
    constformAction=async(formData)=>{
    try{
    awaitaddToCart(formData, title);
    }catch(e){
    // 捕獲錯誤
    }
    };
    return(
    <form action={formAction}>
    <h2>{title}</h2>
    <input type="hidden" name="itemID" value={id}/>
    <SubmitButton />
    </form>
    );
    };
    constSubmitButton=()=>{
    const{ pending }=useFormStatus();
    return(
    <button disabled={pending} type="submit">
    添加到購物車
    </button>
    );
    };
    type Item ={
    id: string;
    title: string;
    };
    constCart=({ cart }:{cart: Item[]})=>{
    if(cart.length ==0){
    returnnull;
    }
    return(
    <>
    購物車:
    <ul>
    {cart.map((item, index)=>(
    <li key={index}>{item.title}</li>
    ))}
    </ul>
    <hr />
    </>
    );
    };
    exportconstApp=()=>{
    const[cart, setCart]= useState<Item[]>([]);
    constaddToCart=async(formData: FormData, title)=>{
    const id =String(formData.get('itemID'));
    awaitnewPromise((resolve)=>setTimeout(resolve,1000));
    setCart((cart: Item[])=>[...cart,{ id, title }]);
    return{ id };
    };
    return(
    <>
    <Cart cart={cart}/>
    <AddToCartForm
    id="1"
    title="JavaScript權威指南"
    addToCart={addToCart}
    />
    <AddToCartForm
    id="2"
    title="JavaScript高級程式設計"
    addToCart={addToCart}
    />
    </>
    );
    };

    添加購物車時效果如下:

    註意 useFormState 需要從 react-dom 中匯入,而不是從 react 中匯入。此外,它僅在父級表單使用 action 內容時才有效。

    往期推薦