MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React组件的PWA(渐进式Web应用)支持

2023-06-122.2k 阅读

什么是 PWA

渐进式 Web 应用(Progressive Web App,简称 PWA)是一种使用现代 Web 技术构建的 Web 应用程序,它旨在提供类似于原生应用的体验。PWA 具有以下几个核心特性:

  1. 可靠:即使在网络不稳定或离线的情况下,PWA 也能可靠地加载,这得益于 Service Workers 技术,它可以拦截网络请求并从缓存中提供资源。
  2. 快速:PWA 加载速度极快,通过预缓存和优化资源加载,能快速响应用户操作,提升用户体验。
  3. 可安装:用户可以将 PWA “安装” 到设备主屏幕,就像安装原生应用一样,无需经过应用商店。这使得 PWA 可以像原生应用一样在后台运行,并接收推送通知。

React 与 PWA 的结合

React 是目前最流行的前端 JavaScript 框架之一,它以组件化的方式构建用户界面,使得代码易于维护和扩展。将 React 与 PWA 结合,可以充分发挥两者的优势,为用户提供高性能、高质量的 Web 应用体验。

在 React 项目中启用 PWA 支持

  1. 创建 React 项目 首先,我们需要创建一个新的 React 项目。可以使用 create - react - app 工具,它会自动配置好一个基本的 React 项目结构,并集成了一些常用的工具和依赖。
npx create - react - app my - pwa - app
cd my - pwa - app
  1. 添加 PWA 支持 create - react - app 生成的项目默认已经集成了 PWA 支持。在项目根目录下,打开 package.json 文件,你会看到 workbox - build 相关的配置。workbox - build 是一个用于生成 Service Worker 代码的工具,它可以自动缓存项目中的静态资源。
{
  "name": "my - pwa - app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing - library/jest - dom": "^5.16.4",
    "@testing - library/react": "^13.3.0",
    "@testing - library/user - event": "^13.5.0",
    "react": "^18.2.0",
    "react - dom": "^18.2.0",
    "react - scripts": "5.0.1",
    "web - vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react - scripts start",
    "build": "react - scripts build",
    "test": "react - scripts test",
    "eject": "react - scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react - app",
      "react - app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "workbox - build": "^6.5.4"
  }
}
  1. 生成 Service Worker 运行 npm run build 命令,create - react - app 会自动使用 workbox - build 生成 Service Worker 代码,并将其包含在构建后的项目中。生成的 Service Worker 会缓存项目的静态资源,如 HTML、CSS、JavaScript 文件以及图片等。
npm run build
  1. 注册 Service Worker 在 React 项目的入口文件(通常是 src/index.js)中,create - react - app 已经为我们添加了注册 Service Worker 的代码。
import React from'react';
import ReactDOM from'react - dom';
import App from './App';
import reportWebVitals from './reportWebVitals';

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service - worker.js')
    .then((registration) => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      })
    .catch((err) => {
        console.log('ServiceWorker registration failed: ', err);
      });
  });
}
ReactDOM.render(<App />, document.getElementById('root'));
reportWebVitals();

这段代码检查浏览器是否支持 Service Worker。如果支持,在页面加载完成后,会尝试注册 Service Worker。如果注册成功,会在控制台打印成功信息;如果注册失败,会打印失败信息。

React 组件中的 PWA 优化

  1. 缓存策略 Service Worker 支持多种缓存策略,常见的有以下几种:
    • Cache - Only:只从缓存中获取资源,不发起网络请求。这种策略适用于那些很少更新的静态资源,如一些长期不变的样式文件或图片。
    • Network - Only:只从网络获取资源,不使用缓存。这种策略适用于实时性要求极高的资源,如实时数据接口。
    • Stale - While - Revalidate:先从缓存中返回资源,同时发起网络请求更新缓存。这种策略可以保证快速响应,同时尽量保持资源的最新性,适用于大多数静态资源。
    • Network - First:先尝试从网络获取资源,如果网络请求失败,再从缓存中获取。这种策略适用于经常更新的资源。 在 React 项目中,可以通过配置 workbox - build 来指定不同资源的缓存策略。例如,以下配置将 HTML 文件的缓存策略设置为 Network - First,将其他静态资源的缓存策略设置为 Stale - While - Revalidate
const { generateSW } = require('workbox - build');

generateSW({
  // 项目构建输出目录
  swDest: 'build/service - worker.js',
  globDirectory: 'build',
  globPatterns: ['**/*.{html,js,css,png,jpg,gif}'],
  runtimeCaching: [
    {
      urlPattern: /\.html$/,
      handler: 'NetworkFirst',
      options: {
        cacheName: 'html - cache',
        expiration: {
          maxEntries: 50
        }
      }
    },
    {
      urlPattern: /\.(js|css|png|jpg|gif)$/,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName:'static - assets - cache',
        expiration: {
          maxEntries: 500
        }
      }
    }
  ]
}).then(() => {
  console.log('Service Worker generated successfully');
}).catch((error) => {
  console.log('Error generating Service Worker: ', error);
});
  1. 预缓存与懒缓存 预缓存是指在 Service Worker 安装时,将一些指定的资源缓存起来。这样在用户首次访问应用时,这些资源可以直接从缓存中获取,提高加载速度。create - react - app 生成的 Service Worker 默认会预缓存构建后的所有静态资源。 懒缓存是指在用户访问某个资源时,才将该资源缓存起来。这种方式可以避免预缓存过多不必要的资源,节省存储空间。可以通过 workbox - buildruntimeCaching 配置来实现懒缓存。例如,以下配置将图片资源设置为懒缓存。
generateSW({
  swDest: 'build/service - worker.js',
  globDirectory: 'build',
  globPatterns: ['**/*.{html,js,css,png,jpg,gif}'],
  runtimeCaching: [
    {
      urlPattern: /\.(png|jpg|gif)$/,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'image - cache',
        expiration: {
          maxEntries: 100
        }
      }
    }
  ]
});
  1. 处理缓存更新 当应用有更新时,需要处理好缓存的更新,以确保用户获取到最新的资源。一种常见的做法是在 Service Worker 安装时,先清除旧的缓存,再缓存新的资源。以下是一个简单的示例:
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('my - app - cache - v2')
    .then((cache) => cache.addAll([
        '/index.html',
        '/static/js/main.js',
        '/static/css/main.css'
      ]))
    .then(() => caches.keys().then((cacheNames) => {
        return Promise.all(
          cacheNames.filter((cacheName) => cacheName.startsWith('my - app - cache - ') && cacheName!=='my - app - cache - v2')
          .map((cacheName) => caches.delete(cacheName))
        );
      })
  );
});

在这个示例中,当 Service Worker 安装时,先打开一个新的缓存 my - app - cache - v2,并将指定的资源缓存到这个新缓存中。然后,删除所有旧版本的缓存(缓存名以 my - app - cache - 开头且不是 my - app - cache - v2 的缓存)。

React 组件的离线支持

  1. 创建离线页面 为了提供良好的离线体验,我们需要创建一个离线页面。当用户离线访问应用时,显示这个离线页面。在 React 项目中,可以创建一个新的组件 OfflinePage.js
import React from'react';

const OfflinePage = () => {
  return (
    <div>
      <h1>你已离线</h1>
      <p>请检查你的网络连接。</p>
    </div>
  );
};

export default OfflinePage;
  1. 检测网络状态 在 React 组件中,可以使用 window.navigator.onLine 属性来检测网络状态。当 onLinetrue 时,表示网络连接正常;当 onLinefalse 时,表示网络断开。我们可以通过 useEffect 钩子来监听网络状态的变化,并根据状态切换显示的组件。
import React, { useEffect } from'react';
import App from './App';
import OfflinePage from './OfflinePage';

const NetworkStatus = () => {
  const [isOnline, setIsOnline] = React.useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline? <App /> : <OfflinePage />;
};

export default NetworkStatus;

在这个示例中,NetworkStatus 组件使用 useState 钩子来保存网络状态。useEffect 钩子在组件挂载时添加 onlineoffline 事件监听器,当网络状态变化时,更新 isOnline 状态。根据 isOnline 的值,决定显示 App 组件(正常页面)还是 OfflinePage 组件(离线页面)。 3. 使用 Service Worker 实现离线数据存储 除了显示离线页面,我们还可以使用 Service Worker 实现离线数据存储。例如,可以使用 IndexedDB 来存储用户的一些临时数据或离线操作记录。以下是一个简单的示例,展示如何在 React 组件中使用 IndexedDB 存储数据。

import React, { useEffect } from'react';

const IndexedDBExample = () => {
  useEffect(() => {
    const request = window.indexedDB.open('my - database', 1);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      const objectStore = db.createObjectStore('my - store', { keyPath: 'id', autoIncrement: true });
      objectStore.add({ data: 'Initial data' });
    };

    request.onsuccess = (event) => {
      const db = event.target.result;
      const transaction = db.transaction(['my - store'],'readwrite');
      const objectStore = transaction.objectStore('my - store');
      objectStore.add({ data: 'New data' });
    };

    request.onerror = (event) => {
      console.log('IndexedDB error: ', event.target.error);
    };
  }, []);

  return (
    <div>
      <h1>IndexedDB 示例</h1>
      <p>数据已存储到 IndexedDB。</p>
    </div>
  );
};

export default IndexedDBExample;

在这个示例中,IndexedDBExample 组件在挂载时打开一个名为 my - database 的 IndexedDB 数据库。如果数据库版本需要升级(首次打开或版本号变化),会创建一个名为 my - store 的对象存储空间,并添加一条初始数据。在数据库打开成功后,会再添加一条新数据。

React 组件的可安装性

  1. 配置 Web App Manifest Web App Manifest 是一个 JSON 文件,用于定义 PWA 的相关元数据,如应用名称、图标、启动画面等。在 React 项目的 public 目录下,创建一个 manifest.json 文件。
{
  "name": "My PWA App",
  "short_name": "My PWA",
  "icons": [
    {
      "src": "icon - 192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon - 512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000"
}

name 是应用的完整名称,short_name 是应用在设备主屏幕上显示的短名称。icons 数组定义了应用的图标,不同尺寸的图标适用于不同设备。start_url 是应用启动时加载的页面。display 属性指定应用的显示模式,standalone 模式会使应用看起来像原生应用,没有浏览器地址栏等。background_colortheme_color 分别定义了应用的背景颜色和主题颜色。 2. 更新 HTML 文件public/index.html 文件中,引入 manifest.json

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf - 8">
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
  <meta name="viewport" content="width=device - width, initial - scale = 1">
  <title>My PWA App</title>
</head>
<body>
  <noscript>
    你需要启用 JavaScript 才能运行此应用。
  </noscript>
  <div id="root"></div>
</body>
</html>

通过 <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> 这一行代码,将 manifest.json 引入到 HTML 文件中。 3. 检测可安装性 在 React 组件中,可以检测当前浏览器环境是否支持安装 PWA,并根据检测结果提供相应的交互。例如,显示一个 “安装” 按钮,当用户点击按钮时,触发安装流程。

import React, { useEffect } from'react';

const InstallButton = () => {
  const [canInstall, setCanInstall] = React.useState(false);
  let deferredPrompt;

  useEffect(() => {
    window.addEventListener('beforeinstallprompt', (event) => {
      event.preventDefault();
      deferredPrompt = event;
      setCanInstall(true);
    });
  }, []);

  const handleInstall = () => {
    if (deferredPrompt) {
      deferredPrompt.prompt();
      deferredPrompt.userChoice.then((choiceResult) => {
        if (choiceResult.outcome === 'accepted') {
          console.log('用户已安装应用');
        } else {
          console.log('用户取消安装');
        }
        deferredPrompt = null;
        setCanInstall(false);
      });
    }
  };

  return (
    <div>
      {canInstall && <button onClick={handleInstall}>安装应用</button>}
    </div>
  );
};

export default InstallButton;

在这个示例中,InstallButton 组件使用 useEffect 钩子监听 beforeinstallprompt 事件。当该事件触发时,说明当前环境支持安装 PWA,保存事件对象 deferredPrompt 并设置 canInstalltrue。当用户点击 “安装应用” 按钮时,调用 deferredPrompt.prompt() 显示安装提示框,并根据用户的选择进行相应处理。

React 组件的推送通知

  1. 配置推送服务 要实现推送通知,需要一个推送服务。常用的推送服务有 Firebase Cloud Messaging(FCM)等。这里以 Firebase Cloud Messaging 为例,首先需要在 Firebase 控制台创建一个项目,并配置好 Web 应用。
  2. 在 React 项目中集成 Firebase 安装 Firebase 相关依赖:
npm install firebase

在 React 项目中初始化 Firebase:

import firebase from 'firebase/app';
import 'firebase/messaging';

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

export { messaging };
  1. 请求推送权限 在 React 组件中,需要请求用户授权以接收推送通知。
import React, { useEffect } from'react';
import { messaging } from './firebase';

const PushNotification = () => {
  useEffect(() => {
    const askPermission = async () => {
      const permission = await Notification.requestPermission();
      if (permission === 'granted') {
        const token = await messaging.getToken();
        console.log('推送令牌: ', token);
      } else {
        console.log('用户拒绝推送通知权限');
      }
    };

    askPermission();
  }, []);

  return (
    <div>
      <h1>推送通知示例</h1>
      <p>已请求推送通知权限。</p>
    </div>
  );
};

export default PushNotification;

在这个示例中,PushNotification 组件使用 useEffect 钩子在组件挂载时调用 askPermission 函数。askPermission 函数首先请求用户的推送通知权限,如果用户授予权限,会获取推送令牌(token),这个令牌可以发送到服务器,用于向该用户发送推送通知。 4. 处理推送通知 在 Service Worker 中,可以处理接收到的推送通知。以下是一个简单的示例:

self.addEventListener('push', (event) => {
  event.waitUntil(
    self.registration.showNotification('新消息', {
      body: '你有一条新的推送消息',
      icon: '/icon.png'
    })
  );
});

在这个示例中,当 Service Worker 接收到推送事件时,会显示一个简单的通知,通知标题为 “新消息”,内容为 “你有一条新的推送消息”,并显示指定的图标。

性能优化与最佳实践

  1. 代码拆分 React 支持代码拆分,可以将应用的代码拆分成多个小块,按需加载。这样可以减少初始加载的代码量,提高应用的加载速度。可以使用 React.lazySuspense 来实现代码拆分。
import React, { lazy, Suspense } from'react';

const BigComponent = lazy(() => import('./BigComponent'));

const App = () => {
  return (
    <div>
      <Suspense fallback={<div>加载中...</div>}>
        <BigComponent />
      </Suspense>
    </div>
  );
};

export default App;

在这个示例中,BigComponent 是一个较大的组件,通过 React.lazy 进行异步加载。Suspense 组件在 BigComponent 加载时显示一个加载提示。 2. 图片优化 对于图片资源,使用合适的图片格式(如 WebP)可以在保证图片质量的前提下,大幅减小图片文件大小。同时,可以使用 loading="lazy" 属性来实现图片的懒加载,提高页面的加载性能。

<img src="image.jpg" alt="示例图片" loading="lazy">
  1. 避免内存泄漏 在 React 组件中,要注意避免内存泄漏。例如,在使用事件监听器或定时器时,要在组件卸载时及时清理。
import React, { useEffect } from'react';

const MemoryLeakExample = () => {
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('定时器运行中');
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return (
    <div>
      <h1>避免内存泄漏示例</h1>
      <p>已设置定时器,并在组件卸载时清理。</p>
    </div>
  );
};

export default MemoryLeakExample;

在这个示例中,MemoryLeakExample 组件使用 useEffect 钩子设置了一个定时器。在 useEffect 的返回函数中,通过 clearInterval 清理定时器,避免在组件卸载后定时器继续运行导致内存泄漏。

  1. 优化 CSS 尽量减少 CSS 文件的大小,避免使用过多的全局样式。可以使用 CSS - in - JS 方案(如 styled - components)来实现组件级别的样式管理,提高样式的可维护性和复用性。
import React from'react';
import styled from'styled - components';

const StyledButton = styled.button`
  background - color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border - radius: 5px;
`;

const ButtonComponent = () => {
  return (
    <StyledButton>点击我</StyledButton>
  );
};

export default ButtonComponent;

在这个示例中,使用 styled - components 创建了一个 StyledButton 组件,它有自己独立的样式,不会影响其他组件。

通过以上步骤和优化方法,我们可以在 React 组件中实现全面的 PWA 支持,为用户提供高性能、可靠且具有原生应用体验的 Web 应用。无论是离线支持、可安装性还是推送通知等功能,都能极大地提升用户对应用的满意度和使用频率。在实际开发中,还需要根据项目的具体需求和特点,灵活运用这些技术和最佳实践,不断优化应用的性能和体验。同时,要关注 PWA 技术的发展动态,及时更新和改进应用,以适应不断变化的前端技术生态。