开源日报每天推荐一个 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/