如何在 React 应用程序中实现“添加到主屏幕按钮”?

Ahm*_*rib 13 javascript manifest reactjs progressive-web-apps manifest.json

我有一个具有安全连接 (https) 的已部署应用程序。
我设法使用 Google Chrome 中的此功能将其添加到我的 Android 主屏幕:
在此输入图像描述

问题是,如果我不在应用程序中添加执行此操作的按钮,用户不太可能执行此操作。

经过一番研究后,我尝试将此处的代码改编为我的 React 应用程序。
所以我改变了这个:

let deferredPrompt;
const addBtn = document.querySelector(".add-button");
addBtn.style.display = "none";
window.addEventListener("beforeinstallprompt", (e) => {
  // Prevent Chrome 67 and earlier from automatically showing the prompt
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
  // Update UI to notify the user they can add to home screen
  addBtn.style.display = "block";

  addBtn.addEventListener("click", (e) => {
    // hide our user interface that shows our A2HS button
    addBtn.style.display = "none";
    // Show the prompt
    deferredPrompt.prompt();
    // Wait for the user to respond to the prompt
    deferredPrompt.userChoice.then((choiceResult) => {
      if (choiceResult.outcome === "accepted") {
        console.log("User accepted the A2HS prompt");
      } else {
        console.log("User dismissed the A2HS prompt");
      }
      deferredPrompt = null;
    });
  });
});  
Run Code Online (Sandbox Code Playgroud)

对此:

export class AddToHomeScreenButton extends Component {
  constructor() {
    super();
    this.state = {
      deferredPrompt: null,
    };
    this.onClick = this.onClick.bind(this);
  }
  componentDidMount() {
    let deferredPrompt;

    window.addEventListener("beforeinstallprompt", (e) => {
      console.log(
        " ~ file: AddToHomeScreenButton.js:46 ~ AddToHomeScreenButton ~ window.addEventListener ~ beforeinstallprompt"
      );
      // Prevent Chrome 67 and earlier from automatically showing the prompt
      e.preventDefault();
      // Stash the event so it can be triggered later.
      deferredPrompt = e;
      console.log(
        " ~ file: AddToHomeScreenButton.js:22 ~ AddToHomeScreenButton ~ window.addEventListener ~ deferredPrompt",
        deferredPrompt
      );
      this.setState({ deferredPrompt });
   
    });
  }
  onClick(e) {
    // hide our user interface that shows our A2HS button
    // addBtn.style.display = "none";
    // Show the prompt
    const { deferredPrompt } = this.state;

    console.log(
      " ~ file: AddToHomeScreenButton.js:49 ~ AddToHomeScreenButton ~ onClick ~ deferredPrompt",
      deferredPrompt
    );
    deferredPrompt.prompt();
    // Wait for the user to respond to the prompt
    deferredPrompt.userChoice.then((choiceResult) => {
      console.log(
        " ~ file: AddToHomeScreenButton.js:53 ~ AddToHomeScreenButton ~ deferredPrompt.userChoice.then ~ choiceResult",
        choiceResult
      );
      console.log(
        " ~ file: AddToHomeScreenButton.js:55 ~ AddToHomeScreenButton ~ deferredPrompt.userChoice.then ~ choiceResult.outcome",
        choiceResult.outcome
      );
      if (choiceResult.outcome === "accepted") {
        console.log("User accepted the A2HS prompt");
      } else {
        console.log("User dismissed the A2HS prompt");
      }
      this.setState({ deferredPrompt: null });
    });
  }
  render() {
    const { deferredPrompt } = this.state;

    return (
      <span>
        {!!deferredPrompt && (
          <Button
            className=".add-button"
            color="danger"
            type="button"
            onClick={this.onClick}
          >
            Add to home screen
          </Button>
        )}
      </span>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,什么也没发生,我也不知道为什么。我检查桌面中的控制台以及其中的代码:

window.addEventListener("beforeinstallprompt", (e) => {
Run Code Online (Sandbox Code Playgroud)

不会被执行。

我认为这是有道理的,因为我相信这个功能不应该在桌面上工作。但是,它也不适用于已部署的应用程序中的移动设备。

但是,正如我所说,我设法使用 chrome“添加到主屏幕”按钮将该应用程序添加到我的 Android 主屏幕。它的工作原理就像一个本机应用程序。


在这里发现可能有多种原因导致beforeinstallprompt没有被触发

beforeinstallprompt 仅在某些条件成立时才会被触发:

  1. 不得已安装 PWA 满足用户参与度
  2. 启发式(以前,用户必须与域交互至少 30 秒,这不再是要求)。
  3. 您的 Web 应用程序必须包含 Web 应用程序清单。
  4. 您的 Web 应用程序必须通过安全的 HTTPS 连接提供服务。
  5. 已向 fetch 事件处理程序注册了一个 Service Worker。

所有这些条件都满足。
这是网络应用程序清单:

{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
Run Code Online (Sandbox Code Playgroud)

还有一个 Service Worker 文件:

// This optional code is used to register a service worker.
// register() is not called by default.

// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.


const isLocalhost = Boolean(
  window.location.hostname === 'localhost' ||
    // [::1] is the IPv6 localhost address.
    window.location.hostname === '[::1]' ||
    // 127.0.0.0/8 are considered localhost for IPv4.
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
);

export function register(config) {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
      return;
    }

    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

      if (isLocalhost) {
        // This is running on localhost. Let's check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config);

        // Add some additional logging to localhost, pointing developers to the
        // service worker/PWA documentation.
      } else {
        // Is not localhost. Just register service worker
        registerValidSW(swUrl, config);
      }
    });
  }
}

function registerValidSW(swUrl, config) {
  navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing;
        if (!installingWorker) {
          return;
        }
        installingWorker.onstatechange = () => {
          if (installingWorker.state === 'installed') {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration);
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration);
              }
            }
          }
        };
      };
    })
}

function checkValidServiceWorker(swUrl, config) {
  // Check if the service worker can be found. If it can't reload the page.
  fetch(swUrl, {
    headers: { 'Service-Worker': 'script' }
  })
    .then(response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      const contentType = response.headers.get('content-type');
      if (
        response.status === 404 ||
        (!!contentType && contentType.indexOf('javascript') === -1)
      ) {
        // No service worker found. Probably a different app. Reload the page.
        navigator.serviceWorker.ready.then(registration => {
          registration.unregister().then(() => {
            window.location.reload();
          });
        });
      } else {
        // Service worker found. Proceed as normal.
        registerValidSW(swUrl, config);
      }
    })
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister();
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

当我在桌面浏览器上打开应用程序时,我在“应用程序”>“清单”中看到以下内容: 在此输入图像描述 但是,我想这些与手机浏览器无关。另外,该应用程序确实是使用 chrome“添加到主屏幕”按钮安装的。所以是可以安装的。

知道如何调试为什么我添加的按钮不起作用以及如何修复它吗?

小智 9

我第一次尝试参考其他来源创建 PWA。希望对您有帮助。

以下是我为获得预期输出而遵循的步骤。

create-react-app首先,使用with创建一个 React 应用程序

npx create-react-app pwa-app --template cra-template-pwa
Run Code Online (Sandbox Code Playgroud)

这将为我们的 PWA 创建一些必需的文件/更改,例如,

  • src/serviceWorkerRegistration.js
  • src/service-worker.js
  • 公共/manifest.json
  • src/index.js

通过在 src/index.js 中添加此行来注册 serviceWorker

serviceWorkerRegistration.register(); // by default it will be serviceWorkerRegistration.unregister();
Run Code Online (Sandbox Code Playgroud)

打开 public/manifest.json 以根据需要编辑应用程序名称和图标。

现在,让我们添加一个钩子来创建和调用安装提示符,

src/AddToHomeScreen.jsx

import * as React from "react";

// Taken from: https://gist.github.com/rikukissa/cb291a4a82caa670d2e0547c520eae53
export function useAddToHomescreenPrompt() {
  const [prompt, setState] = React.useState(null);

  const promptToInstall = () => {
    if (prompt) {
      return prompt.prompt();
    }
    return Promise.reject(
      new Error(
        'Tried installing before browser sent "beforeinstallprompt" event'
      )
    );
  };

  React.useEffect(() => {
    const ready = (e) => {
      e.preventDefault();
      setState(e);
    };

    window.addEventListener("beforeinstallprompt", ready);

    return () => {
      window.removeEventListener("beforeinstallprompt", ready);
    };
  }, []);

  return [prompt, promptToInstall];
}
Run Code Online (Sandbox Code Playgroud)

在 src/App.js (或任何组件)中,

import { useAddToHomescreenPrompt } from "./AddToHomeScreen";
Run Code Online (Sandbox Code Playgroud)

调用钩子,

const [prompt, promptToInstall] = useAddToHomescreenPrompt();
Run Code Online (Sandbox Code Playgroud)

添加 onClick 到按钮,

<button onClick={promptToInstall}>Add to Home Screen</button>
Run Code Online (Sandbox Code Playgroud)

现在,在部署站点后,应该安装应用程序。

要检查应用程序是否已安装,请将其添加到 public/manifest.json

"related_applications": [
   {
     "platform": "webapp",
     "url": "https://yourwebsite.com/manifest.json"
   }
],
Run Code Online (Sandbox Code Playgroud)

并使用此功能查找已安装的应用程序,

src/App.js

  const [isAppInstalled, setIsAppInstalled] = useState(false);

  React.useEffect(() => {
    isPWAInstalled();
  }, []);

  const isPWAInstalled = async () => {
    if ("getInstalledRelatedApps" in window.navigator) {
      const relatedApps = await navigator.getInstalledRelatedApps();
      let installed = false;
      relatedApps.forEach((app) => {
        //if your PWA exists in the array it is installed
        console.log(app.platform, app.url);
        if (
          app.url ===
          "https://yourwebsite.com/manifest.json"
        ) {
          installed = true;
        }
      });
      setIsAppInstalled(installed);
    }
  };
Run Code Online (Sandbox Code Playgroud)

在渲染中,

return (
   <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {!isAppInstalled ? (
          <button onClick={promptToInstall}>Add to Home Screen</button>
        ) : (
          <div>Thanks for installing our app</div>
        )}
        {status.map((text) => (
          <div style={{ fontSize: "14px", marginTop: "5px" }}>{text}</div>
        ))}
      </header>
    </div>
);
Run Code Online (Sandbox Code Playgroud)

此外,我们可以在 public/manifest.json 文件中添加快捷方式、屏幕截图。

我希望我没有错过任何事情。

我的参考资料:

https://create-react-app.dev/docs/making-a-progressive-web-app/

https://gist.github.com/rikukissa/cb291a4a82caa670d2e0547c520eae53

检查用户是否已将 PWA 安装到 Chrome 的主屏幕?

https://web.dev/install-criteria/