開源日報每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,堅持閱讀《開源日報》,保持每日學習的好習慣。

2024年1月3日,開源日報第1063期:
今日推薦開源項目:《Open-AnimateAnyone》
今日推薦英文原文:《Say Goodbye to Debouncing: Use 「useDeferredValue」 Hook》


開源項目

今日推薦開源項目:《Open-AnimateAnyone》傳送門:項目鏈接

推薦理由:動畫製作的非官方實現,這個倉庫是對Animate Anyone的非官方實現,基於magic-animate和AnimateDiff構建,第一階段訓練基本測試通過,目前正在訓練和測試第二階段,效果圖如下


英文原文

今日推薦英文原文:Say Goodbye to Debouncing: Use 「useDeferredValue」 Hook

推薦理由:本文介紹了React 18引入的新工具——useDeferredValue鉤子,用於優化應用程序性能.該鉤子在處理非同步數據獲取(如網路請求或API數據載入)時很有用,其主要目的是推遲對應用程序中不太關鍵部分的更新,同時立即渲染最重要的部分


Say Goodbye to Debouncing: Use 「useDeferredValue」 Hook

React is a popular JavaScript library for building user interfaces, and it continually evolves to provide developers with tools to optimize performance.

One such tool introduced in React 18 is the useDeferredValue hook, which is designed to improve the performance of your applications by prioritizing rendering updates.

What is the useDeferredValue Hook?

The useDeferredValue hook is a relatively new addition to React's arsenal of performance optimization tools.

It's particularly useful when dealing with asynchronous data fetching, such as network requests or data loading from APIs.

The primary purpose of useDeferredValue is to defer updates to less critical parts of your application while immediately rendering the most important parts.

This can greatly improve the perceived performance of your application by avoiding delays in rendering user interface components.

Basic Usage of useDeferredValue

The basic usage of the useDeferredValue hook involves wrapping a state value and creating a deferred version of it. Here's a simple example:

import { useState, useDeferredValue } from 'react';

function MyComponent() {
  const [data, setData] = useState([]);
  const deferredData = useDeferredValue(data);

  // ...
}

In this example, we have a state variable data, which might be populated with data from an asynchronous operation.

By using useDeferredValue, we create deferredData, a version of data that React will prioritize rendering separately.

This separation ensures that the critical parts of your UI update promptly, while non-critical updates, like rendering lists, can be deferred to reduce the impact on performance.

Usage

When you first render the component, the deferred value will be the same as the value you gave it.

When you update the component, the deferred value will lag behind the latest value. This means that React will first re-render the component with the old deferred value, and then try to re-render it with the new deferred value in the background.

Here is an example of when this is useful:

Imagine you have a search bar that fetches search results as you type. You start typing 「a」, and React renders the search bar with the loading fallback. The search results for 「a」 eventually come back, and React re-renders the search bar with the results.

// app.js
import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search songs:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}
// searchResult.js
import { fetchData } from './data.js';
export default function SearchResults({ query }) {

    if (query === '') {
        return null;
    }
    const songs = use(fetchData(`/search?q=${query}`));

    if (songs.length === 0) {
        return <p>No matches for <i>"{query}"</i></p>;
    }
    return (
        <ul>
            {songs.map(song => (
                <li key={song.id}>
                    {song.title} ({song.year})
                </li>
            ))}
        </ul>
    );
}

function use(promise) {
    if (promise.status === 'fulfilled') {
        return promise.value;
    } else if (promise.status === 'rejected') {
        throw promise.reason;
    } else if (promise.status === 'pending') {
        throw promise;
    } else {
        promise.status = 'pending';
        promise.then(
            result => {
                promise.status = 'fulfilled';
                promise.value = result;
            },
            reason => {
                promise.status = 'rejected';
                promise.reason = reason;
            },
        );
        throw promise;
    }
}
// data.js

let cache = new Map();

export function fetchData(url) {
    if (!cache.has(url)) {
        cache.set(url, getData(url));
    }
    return cache.get(url);
}

async function getData(url) {
    if (url.startsWith('/search?q=')) {
        return await getSearchResults(url.slice('/search?q='.length));
    } else {
        throw Error('Not implemented');
    }
}

async function getSearchResults(query) {
    // Add a fake delay to make waiting noticeable.
    await new Promise(resolve => {
        setTimeout(resolve, 500);
    });

    const allSongs = [{
        id: 1,
        title: "Bohemian Rhapsody",
        year: 1975
    },
    {
        id: 2,
        title: "Imagine",
        year: 1971
    },
    {
        id: 3,
        title: "Hotel California",
        year: 1976
    },
    {
        id: 4,
        title: "Stairway to Heaven",
        year: 1971
    },
    {
        id: 5,
        title: "Let It Be",
        year: 1970
    },
    {
        id: 6,
        title: "Abbey",
        year: 1976
    },
    {
        id: 7,
        title: "A Hard Day's Night",
        year: 2012
    }];

    const lowerQuery = query.trim().toLowerCase();
    return allSongs.filter(album => {
        const lowerTitle = album.title.toLowerCase();
        return (
            lowerTitle.startsWith(lowerQuery) ||
            lowerTitle.indexOf(' ' + lowerQuery) !== -1
        )
    });
}

An alternative UI pattern frequently used involves postponing the update of the results list and persisting the display of the previous results until the new results become available.

To implement this approach, you can utilize the useDeferredValue hook to provide a deferred version of the query as it gets passed down the component hierarchy.

// app.js
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './searchResult.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search Songs:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

img

Now, you edit your search query to 「ab」. React will first re-render the search bar with the old deferred value, which is the search results for 「a」. Then, it will try to re-render the search bar with the new deferred value, which is the search results for 「ab」.

This means that the user will never see the loading fallback again, even if the search results for 「ab」 take a long time to come back.

In other words, deferred values allow you to render your UI immediately, even if you』re waiting for asynchronous data. This can help to improve the user experience by avoiding unnecessary loading fallbacks.

Benefits of useDeferredValue

  1. Improved Performance: Deferred loading and rendering of components or data can lead to faster initial page loads, reduced resource usage, and optimized performance, resulting in a better user experience.
  2. Efficient Resource Utilization: By loading only what』s needed when it』s needed, you can reduce unnecessary API calls, minimize memory and CPU usage, and optimize your application』s resource utilization.
  3. Progressive Loading and Error Isolation: Deferred value strategies enable progressive loading for a more responsive UI and provide better error isolation, ensuring that errors in one part of your application don』t disrupt the entire user experience.

    下載開源日報APP:https://openingsource.org/2579/
    加入我們:https://openingsource.org/about/join/
    關注我們:https://openingsource.org/about/love/