在应用程序启动时在后台初始化/预加载 React 组件以供以后使用

Initialize / Eager Load React Component in Background on App Start for later usage

我正在编写一个 React 应用程序,其中有一个 Fallback 组件,当出现问题时会显示该组件,例如:网络已关闭,API 无法访问,未知路线等

此组件将 fetch 一些猫图片的 URL 并显示幻灯片。

当然,当网络中断时,这是不可能的。

虽然我想在应用程序启动时以某种方式在后台创建和初始化这个组件,以便在紧急情况下一切就绪。

附加信息:Fallback 组件将用作不同视图的子组件。所以不能简单的挂载到App.jsx然后用CSSvisibility: hidden / visible隐藏和显示

这可能吗?有人知道怎么做吗?

编辑:示例代码

const Fallback = () => {
  // will contain urls like:
  //   - https://cats.example.org/images/foo.jpg
  //   - https://cats.example.org/images/bar.png
  //   - https://cats.example.org/images/42.gif
  const [urls, setUrls] = useState([]);

  useEffect(() => {
    fetch('https://catpictures.example.org')
      .then(response => response.json())
      .then(data => setUrls(data));
  }, []);

  // this should be cached somehow:
  return (
    <div>
      {urls.map(url =>
        <img src={url} />
      }
    </div>
  );
}

您可以使用预加载的 <link>:

手动将资源添加到缓存
<link rel="preload" as="image" href="https://cats.example.org/images/foo.jpg">

将其放入您的 index.html 并在需要时使用相同的 href.

从缓存中使用它

一个 service worker 可以缓存您的资产,然后在离线时为它们提供服务怎么样?然后您可以发送一条消息 URL 以更改以通知您的“应用程序”它已重新上线并包含一些新内容。

这里有一个工作示例:https://serviceworke.rs/strategy-cache-update-and-refresh_demo.html

var CACHE = 'cache-update-and-refresh';
 
self.addEventListener('install', function(evt) {
  console.log('The service worker is being installed.');
 
  evt.waitUntil(caches.open(CACHE).then(function (cache) {
    cache.addAll([
      './controlled.html',
      './asset'
    ]);
  }));
});

function fromCache(request) {
  return caches.open(CACHE).then(function (cache) {
    return cache.match(request);
  });
}

function update(request) {
  return caches.open(CACHE).then(function (cache) {
    return fetch(request).then(function (response) {
      return cache.put(request, response.clone()).then(function () {
        return response;
      });
    });
  });
}

function refresh(response) {
  return self.clients.matchAll().then(function (clients) {
    clients.forEach(function (client) {

 
      var message = {
        type: 'refresh',
        url: response.url,

        eTag: response.headers.get('ETag')
      };
 
      client.postMessage(JSON.stringify(message));
    });
  });
}

self.addEventListener('fetch', function(evt) {
  console.log('The service worker is serving the asset.');

  evt.respondWith(fromCache(evt.request));

  evt.waitUntil(
    update(evt.request)
 
    .then(refresh)
  );
});

你可以做到这一点,我已经在大型生产应用程序中做到了,只需创建一个新的 Image() 并设置 src。图片将在组件首次渲染时预加载。

const LoadingComponent() {
  useEffect(() => {
    const img = new Image();
    img.src = imgUrl;
  }, []);

  return null; // doesn't matter if you display the image or not, the image has been preloaded
}

它甚至可以成为一个钩子,例如 useImagePreloader(src),但这取决于您。

Here is a Sandbox with a working version.

尝试步骤:

  1. 创建隐身模式 window,打开开发工具并检查网络选项卡,搜索“imgur”。图片已加载。
  2. 将网络设置为离线或断开与 WIFI 的连接。
  3. 单击 Show Image 按钮。图片将正确显示。

如果您的图像缓存设置正确(通常是正确的),此解决方案将始终有效。如果没有,您可以将图像保存到 blobs 并为该 blob 获取 URL,这将 100% 有效。

正如您所指出的,您需要一个图像数组,您可以在一个循环中执行相同的代码,它会工作得很好,图像仍将被缓存:

const images = ['first.jpg', 'second.png', 'etc.gif'];

images.forEach(imageUrl => {
  const img = new Image();
  img.src = image
});

本地存储

  1. 在 page/App 加载中;

    • 获取每张图片
    • base64 数据保存到 localStorage
  2. 网络故障

    • 渲染<FallBack />
  3. <FallBack />

    • 读取本地存储
    • 渲染 base64 个图像

小例子

  • 我们使用fetch获取<App/>组件中的猫图片

  • 将它们保存到 localStorage

    (注意: StackSnippet doesn't allow localStorage,所以请在 JSFiddle 上测试它)

  • 我们用一个useState来'fake'网络状态

// Init
const { useState } = React;

// <Fallback />
const Fallback = () => {

    // Get all localstorage items
    let ls = { ...localStorage };

    // Get all starting with 'image_'
    ls = Object.keys(ls).filter(key => key.startsWith('image_'));
 
    // Render images
    return (
        <div>
            <p>{'FallBack'}</p>
            {
                (ls.length < 1)
                    ? 'Unable to find cached images!'
                    : (
                        ls.map((key) => {
                        
                            // Get current key from localstorage
                            const base64 = localStorage.getItem(key);
                            
                            
                            
                            // Render image    
                            return <img src={base64} />;
                        })
                    )
            }
        </div>
    );
}

// <App />
const App = ({title}) => {
    const [network, setNetwork] = useState(true);
    const [urls, setUrls] = useState([ 'https://placekitten.com/200/300', 'https://placekitten.com/200/300']);

    // Render Fallback on lost network
    if (!network) {
        return <Fallback />;
    }

    // While network is active, get the images
    urls.forEach((url, index) => {
        
        fetch(url)
            .then(response => response.blob())
            .then(blob => {

                // Convert BLOB to base64
                var reader = new FileReader();
                reader.readAsDataURL(blob); 
                reader.onloadend = function() {

                    // Write base64 to localstorage   
                    var base64data = reader.result;     
                    localStorage.setItem('image_' + index, base64data);
                    console.log('Saving image ' + index + ' to localstorage');
                };
            });
    })

    return (
        <div>
            <p>{'App'}</p>
            <p>Press me to turn of the internet</p>
            <button onClick={() => setNetwork(false)}>{'Click me'}</button>
        </div>
    );
};

// Render <App />
ReactDOM.render(<App />, document.getElementById("root"));

JSFiddle Demo


优点;

  • LocalStorage 不会被清除,如果第二天加载相同的应用程序,我们不需要再次获取这些图像

缺点;

  • 有一个 size limit 用于 LocalStorage