如何使用 ReactJS 开发渐进式 Web 应用程序?
渐进式 React 应用程序对用户操作的响应非常快。它们加载速度快,就像移动应用程序一样吸引人。他们可以访问移动设备功能,利用操作系统并拥有非常高的覆盖面。它支持可安装性、背景同步、缓存和离线支持以及推送通知等其他功能。使用 React,我们可以非常轻松地逐步增强 Web 应用程序,使其看起来和感觉像原生移动应用程序。
现在让我们看一步一步的实现 在 如何使用 React 开发渐进式 Web 应用程序。
第 1 步:使用 ReactJS,创建一个渐进式 Web 应用程序甚至将现有的 React 项目转换为一个项目变得更加容易。在文本编辑器的终端中,输入以下命令。 CRA 为 Progressive Web App 创建了一个样板,您可以根据需要轻松修改它。
npx create-react-app react-pwa –template cra-template-pwa
cd react-pwa
此命令创建一个名为 react-pwa 的新 React 应用程序并导航到您的应用程序的目录。您可以进一步修改 manifest.json 文件和徽标等其他文件,以自定义应用程序并使其成为您自己的应用程序。
第 2 步:让我们实现 PWA 的功能并向我们的应用程序添加更多功能。在文本编辑器的终端中输入以下命令来安装一些第三方和 npm 包。
npm install –save web-push react-router-dom bootstrap react-bootstrap
项目结构:在public文件夹中添加worker.js和feed.js ,在src文件夹中添加components文件夹,使其看起来像这样。
第 3 步:注册 Service Worker – Service Worker 是一种在浏览器和网络之间工作的特殊脚本文件。它可以帮助我们执行独特的功能并在页面加载时自行注册。要在您的 React 应用程序中注册一个新的服务工作者,请在您的公共文件夹 ( public/worker.js ) 的 worker.js 文件中添加以下代码。
Javascript
var STATIC_CACHE_NAME = "gfg-pwa";
var DYNAMIC_CACHE_NAME = "dynamic-gfg-pwa";
// Add Routes and pages using React Browser Router
var urlsToCache = ["/", "/search", "/aboutus", "/profile"];
// Install a service worker
self.addEventListener("install", (event) => {
// Perform install steps
event.waitUntil(
caches.open(STATIC_CACHE_NAME).then(function (cache) {
console.log("Opened cache");
return cache.addAll(urlsToCache);
})
);
});
// Cache and return requests
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cacheRes) => {
// If the file is not present in STATIC_CACHE,
// it will be searched in DYNAMIC_CACHE
return (
cacheRes ||
fetch(event.request).then((fetchRes) => {
return caches.open(DYNAMIC_CACHE_NAME).then((cache) => {
cache.put(event.request.url, fetchRes.clone());
return fetchRes;
});
})
);
})
);
});
// Update a service worker
self.addEventListener("activate", (event) => {
var cacheWhitelist = ["gfg-pwa"];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
Javascript
Javascript
Javascript
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cacheRes) => {
return (
cacheRes ||
fetch(event.request).then((fetchRes) => {
return caches.open(DYNAMIC_CACHE_NAME)
.then((cache) => {
cache.put(event.request.url, fetchRes.clone());
return fetchRes;
});
})
);
})
);
if (!navigator.onLine) {
if (event.request.url ===
"http://localhost:3000/static/js/main.chunk.js") {
event.waitUntil(
self.registration.showNotification("Internet", {
body: "internet not working",
icon: "logo.png",
})
);
}
}
});
Javascript
import React from "react";
import { InputGroup, Button, Container }
from "react-bootstrap";
const Profile = () => {
return (
<>
Latitude
00
Longitude
00
Location
Pick an Image instead
>
);
};
export default Profile;
Javascript
window.onload = function () {
var photo = document.getElementById("photoBtn");
var locationBtn = document.getElementById("locationBtn");
locationBtn.addEventListener("click", handler);
var capture = document.getElementById("capture");
photo.addEventListener("click", initializeMedia);
capture.addEventListener("click", takepic);
};
function initializeLocation() {
if (!("geolocation" in navigator)) {
locationBtn.style.display = "none";
}
}
function handler(event) {
if (!("geolocation" in navigator)) {
return;
}
navigator.geolocation.getCurrentPosition(
function (position) {
console.log(position);
var lat = position.coords.latitude;
var lon = position.coords.longitude;
console.log(lat);
console.log(lon);
latitude.innerHTML = lat;
longitude.innerHTML = lon;
});
}
function initializeMedia() {
if (!("mediaDevices" in navigator)) {
navigator.mediaDevices = {};
}
if (!("getUserMedia" in navigator.mediaDevices)) {
navigator.mediaDevices.getUserMedia =
function (constraints) {
var getUserMedia =
navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error(
"getUserMedia is not implemented!"));
}
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator,
constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
player.srcObject = stream;
player.style.display = "block";
})
.catch(function (err) {
console.log(err);
imagePicker.style.display = "block";
});
}
function takepic(event) {
canvas.style.display = "block";
player.style.display = "none";
capture.style.display = "none";
var context = canvas.getContext("2d");
context.drawImage(
player,
0,
0,
canvas.width,
player.videoHeight / (player.videoWidth / canvas.width)
);
player.srcObject.getVideoTracks().forEach(function (track) {
track.stop();
});
}
第 4 步:一些旧浏览器可能不支持 Service Worker。但是,大多数现代浏览器(例如 Google Chrome)都内置了对服务人员的支持。在没有支持的情况下,该应用程序将像正常的 Web 应用程序一样运行。为了确保我们不会遇到错误或应用程序不会崩溃,我们需要在客户端的浏览器中检查 Service Worker 的支持状态。为此,请使用以下代码更新公共文件夹 ( public/index.html)中的 index.html 文件。
Javascript
第 5 步:现在,我们有了 Service Worker 基本功能的代码。我们需要注册它。为此,将 src 文件夹 ( src/index.js ) 中 index.js 中的一行从
service-worker.unregister()
到
serviceWorker.register()
我们的 service worker 即 worker.js 现在将成功注册自己。
运行应用程序的步骤:现在,在文本编辑器的终端中输入以下命令。
npm start
输出:这将在浏览器的 localhost://3000 中打开您的 React 应用程序。并且,在开发工具中,在应用程序选项卡下,您可以看到您的服务工作者已在控制台中注册,并显示“工作者注册成功”消息。
解释:我们现在有了基本的服务工作者,它按照我们想要的方式运行。为了实现其他类似原生设备的功能,让我们实现在用户在使用应用程序时离线的情况下发送通知。此外,要查看您的新功能,无需再次运行应用程序,只需单击重新加载按钮即可。
第 6 步:离线时发送推送通知 –推送通知是本机移动功能。并且浏览器会在默认设置中自动请求用户许可。 Web-push是一个第三方包,它将帮助我们使用 VAPID 键来推送通知。现在,我们需要一个VAPID API Key来开始在我们的应用程序中实现推送通知。请注意,每个 VAPID API KEY 对于每个 Service Worker 都是唯一的。
要生成 API 密钥,请在终端中键入以下内容:
./node_modules/.bin/web-push generate-vapid-keys
现在,在您的文本编辑器的终端中,web-push 提供了两个您自己的 vapid 键。我们将使用公共 vapid 密钥来生成推送通知。
修改 index.html 中的脚本。这将对您的 base64字符串VAPID API KEY 进行编码,并将其与 service worker 连接,以便它能够发送通知。
Javascript
第 7 步:让我们使用这个新功能在离线时发送推送通知。在worker.js中修改 fetch 事件并添加以下代码。在显示通知函数中,您可以添加更多属性并根据您的意愿进行修改。
Javascript
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cacheRes) => {
return (
cacheRes ||
fetch(event.request).then((fetchRes) => {
return caches.open(DYNAMIC_CACHE_NAME)
.then((cache) => {
cache.put(event.request.url, fetchRes.clone());
return fetchRes;
});
})
);
})
);
if (!navigator.onLine) {
if (event.request.url ===
"http://localhost:3000/static/js/main.chunk.js") {
event.waitUntil(
self.registration.showNotification("Internet", {
body: "internet not working",
icon: "logo.png",
})
);
}
}
});
self.registration.showNotification函数显示所需的通知,甚至在显示之前请求许可。
第 8 步:要检查离线时同步和缓存是否正常工作,您可以在开发工具中将 Service Worker 上方的状态更改为“离线”或在应用程序上方执行相同操作。现在,每当您下线时,您都会看到一条推送通知,指示您下线了。
请注意,尽管某些功能可能会丢失,但您仍然可以看到这些页面。这是因为这些默认页面和 URL 一旦访问过就会存储在缓存中。因此,每次在开发过程中对文件进行更改时,请确保在应用程序选项卡下取消注册并再次注册。
第 9 步:添加相机和地理位置等原生功能——PWA 支持使用原生功能,如访问网络摄像头并在服务人员的帮助下确定位置。让我们首先为此创建 UI,我们可以在其中使用这些功能,在 'src/Profile.js' 中创建一个Profile.js文件,我们可以使用 React Routes 通过 /profile 导航到该文件。
Javascript
import React from "react";
import { InputGroup, Button, Container }
from "react-bootstrap";
const Profile = () => {
return (
<>
Latitude
00
Longitude
00
Location
Pick an Image instead
>
);
};
export default Profile;
第10步:现在让我们在public/feed.js中添加一个feed.js文件来实现定位和摄像头的功能。
Javascript
window.onload = function () {
var photo = document.getElementById("photoBtn");
var locationBtn = document.getElementById("locationBtn");
locationBtn.addEventListener("click", handler);
var capture = document.getElementById("capture");
photo.addEventListener("click", initializeMedia);
capture.addEventListener("click", takepic);
};
function initializeLocation() {
if (!("geolocation" in navigator)) {
locationBtn.style.display = "none";
}
}
function handler(event) {
if (!("geolocation" in navigator)) {
return;
}
navigator.geolocation.getCurrentPosition(
function (position) {
console.log(position);
var lat = position.coords.latitude;
var lon = position.coords.longitude;
console.log(lat);
console.log(lon);
latitude.innerHTML = lat;
longitude.innerHTML = lon;
});
}
function initializeMedia() {
if (!("mediaDevices" in navigator)) {
navigator.mediaDevices = {};
}
if (!("getUserMedia" in navigator.mediaDevices)) {
navigator.mediaDevices.getUserMedia =
function (constraints) {
var getUserMedia =
navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error(
"getUserMedia is not implemented!"));
}
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator,
constraints, resolve, reject);
});
};
}
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
player.srcObject = stream;
player.style.display = "block";
})
.catch(function (err) {
console.log(err);
imagePicker.style.display = "block";
});
}
function takepic(event) {
canvas.style.display = "block";
player.style.display = "none";
capture.style.display = "none";
var context = canvas.getContext("2d");
context.drawImage(
player,
0,
0,
canvas.width,
player.videoHeight / (player.videoWidth / canvas.width)
);
player.srcObject.getVideoTracks().forEach(function (track) {
track.stop();
});
}
第 11 步:在 ( /src/public ) 文件夹中创建一个名为 feed.js 的新文件。在 feed.js 中,我们分别使用geolocation和mediaDevices来实现位置和摄像头的功能。您还可以使用 Google Geocoder API 将这些纬度和经度转换为地名。
输出:您现在可以导航到 localhost:3000/profile 以拍照并获取位置。
说明:单击“获取位置”按钮将触发处理函数内的 navigator.geolocation.getCurrentPosition,从而使用适当的值填充纬度和经度字段。要获取城市的确切名称,请尝试使用上面提到的 Geocoder API。同样,点击 Take a Picture, Now 按钮将触发 initializeMedia函数内的 navigator.mediaDevices.getUserMedia 从而打开前置摄像头并拍照。这两个函数都将首先添加权限,然后自行执行。