WEB程序员笔记

一个前端开发工程师的个人博客

反应和网络工作者

每天分享一个技巧,直到(可能不是)2020年4月19日瑞士COVID-19隔离所结束。剩下十二天,直到希望更好的日子。


我最近发布了Tie Tracker,这是一个简单的开源免费时间跟踪应用程序⏱。

在其全部功能中,完全脱机模式的开发特别有趣。从体系结构的角度来看,我必须找到一种解决方案,以便出于统计或导出目的而计算用户可能在不阻塞用户界面的情况下可以记录的许多条目。

这就是为什么我有想法借助Web Workers API解决问题。

该应用程序是使用Ionic + React开发的,因此让我与您分享我的食谱😉。


模拟阻塞的用户界面

在尝试使用Web Workers之前,让我们首先尝试开发一个小型应用程序,其中包含实际上阻止用户界面的操作。

在以下组件中,我们将处理两个状态,两个计数器。其中之一在每次单击按钮时增加,而另一个调用一个函数incApple(),该函数会循环一段时间,从而阻止用户交互。

import {
    IonContent,
    IonPage,
    IonLabel,
    IonButton
} from '@ionic/react';
import React, {useState} from 'react';
import {RouteComponentProps} from 'react-router';

import './Page.css';

const Page: React.FC<RouteComponentProps<{ name: string; }>> = ({match}) => {

    const [countTomato, setCountTomato] = useState<number>(0);
    const [countApple, setCountApple] = useState<number>(0);

    function incApple() {
        const start = Date.now();
        while (Date.now() < start + 5000) {
        }
        setCountApple(countApple + 1);
    }

    return (
        <IonPage>
            <IonContent className="ion-padding">
                <IonLabel>Tomato: {countTomato} | Apple: {countApple}</IonLabel>

                <div className="ion-padding-top">
                    <IonButton 
                     onClick={() => setCountTomato(countTomato + 1)}
                     color="primary">Tomato</IonButton>

                    <IonButton 
                     onClick={() => incApple()} 
                     color="secondary">Apple</IonButton>
                </div>
            </IonContent>
        </IonPage>
    );
};

export default Page;

如您在以下动画Gif中所注意到的那样,一旦我启动“ Apple counter”,“ Tomato counter”上的用户交互就不再起作用,也不会触发任何新的组件渲染,因为该函数当前阻止了JavaScript线程。

《反应和网络工作者》


推迟与网络工作者的合作

考虑到上面的示例,让我们尝试使用Web Workers来推迟我们的“ Apple counter”功能。


网络工作者

向应用程序添加Web Worker的最简单方法是将其作为资产发送。在我的Ionic React应用程序中,这些public文件位于目录中,即我们创建了一个新文件./public/workers/apple.js

在解释下面的代码流,两两件事是重要的要注意:

  1. 应用程序和Web Worker是两个独立的事物。它们不共享状态,不共享库,它们是独立的,并且只能通过消息在它们之间进行通信。
  2. 网页工人没有进入GUI,给document,给window

如果您熟悉Firebase,则可以在某种程度上将Web Worker理解为您自己的私有(而不是Cloud)而是本地功能。

我们的Web Worker的入口点onmessage基本上是从应用程序触发的侦听器。在我们正在注册的函数中,我们正在检查是否msg提供了相应的函数,这使我们可以使用Web Worker进行多种用途,并且还在运行与之前相同的函数incApple()之前修改当前计数器值。最后,我们不是通过直接更新状态,而是通过将该值返回给应用程序postMessage

self.onmessage = async ($event) => {
    if ($event && $event.data && $event.data.msg === 'incApple') {
        const newCounter = incApple($event.data.countApple);
        self.postMessage(newCounter);
    }
};

function incApple(countApple) {
    const start = Date.now();
    while (Date.now() < start + 5000) {
    }
    return countApple + 1;
}

与网络工作者互动

要与Web Worker交互,我们首先需要在组件中添加参考点。

const appleWorker: Worker = new Worker('./workers/apple.js');

因为我们正在使用消息进行通信,所以我们应该注册一个侦听器,当Web Worker发出结果时,该侦听器将负责更新计数器状态。

useEffect(() => {
    appleWorker.onmessage = ($event: MessageEvent) => {
        if ($event && $event.data) {
            setCountApple($event.data);
        }
    };
}, [appleWorker]);

最后,我们更新函数incApple()以调用Web Worker。

function incApple() {
    appleWorker
         .postMessage({msg: 'incApple', countApple: countApple});
}

多田,就是这样。现在,即使“阻止程序代码正在运行”,您也应该能够与GUI进行交互。正如您在以下动画Gif中注意到的那样,即使阻塞循环是由网络工作者执行的,我仍然能够增加我的Tomato计数器。

《反应和网络工作者》

如果您需要该组件,请执行以下操作:

import {
    IonContent,
    IonPage,
    IonLabel,
    IonButton
} from '@ionic/react';
import React, {useEffect, useState} from 'react';
import {RouteComponentProps} from 'react-router';

import './Page.css';

const Page: React.FC<RouteComponentProps<{ name: string; }>> = ({match}) => {

    const [countTomato, setCountTomato] = useState<number>(0);
    const [countApple, setCountApple] = useState<number>(0);

    const appleWorker: Worker = new Worker('./workers/apple.js');

    useEffect(() => {
        appleWorker.onmessage = ($event: MessageEvent) => {
            if ($event && $event.data) {
                setCountApple($event.data);
            }
        };
    }, [appleWorker]);

    function incApple() {
        appleWorker
            .postMessage({msg: 'incApple', countApple: countApple});
    }

    return (
        <IonPage>
            <IonContent className="ion-padding">
                <IonLabel>Tomato: {countTomato} | Apple: {countApple}</IonLabel>

                <div className="ion-padding-top">
                    <IonButton 
                     onClick={() => setCountTomato(countTomato + 1)}
                     color="primary">Tomato</IonButton>

                    <IonButton 
                     onClick={() => incApple()} 
                     color="secondary">Apple</IonButton>
                </div>
            </IonContent>
        </IonPage>
    );
};

export default Page;

摘要

网络工作者确实是一个有趣的概念。Tie Tracker让我进行实验,我肯定会在以后的项目中再次使用它们。它的代码是开源的,可以在GitHub上获得。如果您有任何反馈,甚至更好,有兴趣贡献您的力量,请将最好的请求请求发送给我,这太好了。

待在家里,安全!

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注