使用React和Vite用Tauri建立一个Pomodoro计时器

1,137 阅读7分钟

Tauri是一套工具,让你使用前端框架构建跨平台的桌面应用。当与React和Vite结合时,它可以用来为所有桌面平台构建极快的二进制文件。

开发人员可以使用Tauri用网络技术编写安全、精简、快速的跨平台桌面应用。对许多开发者来说,Electron通常是这些应用程序的首选框架,但Tauri现在已经做好了竞争的准备。

在这篇文章中,我们将建立一个简单的Pomodoro计时器,我邀请你按照自己的节奏来学习这个教程。

Tauri是什么?

它 "快得惊人"(这是他们说的,不是我说的;但请点击链接查看基准,自己决定),因为它使用Rust作为后端(相对于Node),产生的二进制文件更小,而且比Node更安全。

它使用底层操作系统提供的WebView来渲染应用程序的用户界面--这也是应用程序的二进制文件较小的原因之一(与电子相比)。来自Tauri工具包的WRY库提供了一个统一的接口来与不同操作系统提供的WebView交互。WRY库使用Tao crate进行跨平台的窗口管理。

Tauri把这一切结合在一起,使开发者能够编写强大的、性能良好的桌面应用程序。

先决条件

在我们开始之前,我们需要安装一些东西。

要安装Rust,请打开你的终端并运行:
curl --proto '=https' --tlsv1.2 -sSf [https://sh.rustup.rs](https://sh.rustup.rs) | sh

如果你是在Windows上,请按照以下说明进行安装。

npm

npm是Node的一个软件包管理器。它与Node捆绑在一起,所以如果你的系统上安装了Node,你很可能也会有npm。

如果你没有Node,可以用Homebrew安装它。

brew install node

在安装了npm和Rust之后,我们就可以开始使用Tauri开发应用程序了。

用create-tauri-app搭建一个Tauri应用的支架

由于先决条件已经安装,我们可以开始开发Pomodoro计时器的桌面应用程序。Tauri的人们用 create-tauri-appnpm包,使应用程序的脚手架搭建变得非常容易。

要开始工作,请运行:

npx create-tauri-app

运行这个命令后,你将被要求输入一些信息:

  • 项目的名称
  • 窗口的标题,应用程序将在其中加载
  • UI配方
  • 要使用的模板

对于这个教程应用程序,我使用了以下内容,见下文👇

  • 项目名称为 "pomodoro"
  • 窗口的标题为 "Pomodoro Timer App"。
  • UI配方为 "create-vite"
  • 模板使用 "react-ts"

Tauri Project Dependencies Installation

点击回车键将安装所有必要的软件包,并在与项目名称相同的文件夹下输出一个脚手架项目。

现在我们都准备好了,可以第一次运行这个项目了

要运行该项目,请运行:

cd pomodoro // cding into the project folder
npm run tauri dev //running the app

这将首先启动前端服务器,然后将下载crates(如果需要)并编译Rust后端。一旦编译完成,没有任何错误,应用程序就会启动,你会看到像这样的东西。

Vite And React App Example

项目文件夹包含很多重要的文件和文件夹。在本教程中,我们要处理的两个文件是。

  • src 文件夹和。
  • src-tauri/tauri.config.json 文件

(注意:第一次运行该应用程序将需要一些时间,因为该应用程序需要下载和编译必要的Rust crates)

(注意:对于使用macOS进行开发的人来说--如果你运行命令来启动应用,并且应用窗口出现在全屏应用上,你将无法移动应用窗口。这是一个已知的错误,可以在这里进行追踪)

构建前台

我们将使用ChakraUI来实现UI组件和风格设计。要设置ChakraUI,请遵循他们的入门指南

我们将在下面构建UI:

Timer App UI Example

以下是我们的应用程序的要求:

  • 应该有一个开始暂停按钮,启动和停止定时器。
  • 一旦定时器达到0,就触发一个通知给用户。
  • 可以选择间隔长度,即:15分钟、30分钟和60分钟。
  • 一个复位按钮,将计时器重置为初始值。

要开始,请到src/App.tsx

// src/App.tsx
import { Button, Flex, Text } from "@chakra-ui/react";
import { useEffect, useState } from "react";

function App() {
  const [time, setTime] = useState(0);
  const [timerStart, setTimerStart] = useState(false);
  const buttons = [
    {
      value: 900,
      display: "15 minutes",
    },
    {
      value: 1800,
      display: "30 minutes",
    },
    {
      value: 3600,
      display: "60 minutes",
    },
  ];
  const toggleTimer = () => {
    setTimerStart(!timerStart);
  };
  useEffect(() => {
    const interval = setInterval(() => {
      if (timerStart) {
        if (time > 0) {
          setTime(time - 1);
        } else if (time === 0) {
          // TODO: Send notification to user.
          clearInterval(interval);
        }
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [timerStart, time]);
  return (
    <div className="App" style={{ height: "100%" }}>
      <Flex
        background="gray.700"
        height="100%"
        alignItems="center"
        flexDirection="column"
      >
        <Text color="white" fontWeight="bold" marginTop="20" fontSize="35">
          Pomodoro Timer
        </Text>
        <Text fontWeight="bold" fontSize="7xl" color="white">
          {`${
            Math.floor(time / 60) < 10
              ? `0${Math.floor(time / 60)}`
              : `${Math.floor(time / 60)}`
          }:${time % 60 < 10 ? `0${time % 60}` : time % 60}`}
        </Text>
        <Flex>
          <Button
            width="7rem"
            background="tomato"
            color="white"
            onClick={toggleTimer}
          >
            {!timerStart ? "Start" : "Pause"}
          </Button>
          {/* TODO: Add Button to reset timer */}
        </Flex>
        <Flex marginTop={10}>
          {buttons.map(({ value, display }) => (
            <Button
              marginX={4}
              background="green.300"
              color="white"
              onClick={() => {
                setTimerStart(false);
                setTime(value);
              }}
            >
              {display}
            </Button>
          ))}
        </Flex>
      </Flex>
    </div>
  );
}
export default App;

在这里,我们使用ChakraUI的内置组件来设置按钮和文本的布局。
useEffect 钩子中的setTimeout ,通过每过一秒设置状态来驱动用户界面。

该效果重新运行:

  • 当用户点击持续时间按钮时(这设置了timerStarttime 的状态值)
  • 当用户点击 "开始/暂停"按钮时。(这设置了timerStart 状态值)
  • 每过一秒。(setTimeout 触发对time 值的更新,见第29行)

为了以一致的格式(mm:ss)显示时间,我们需要做一些数学体操。

这当然不完美,但它能完成工作。

这里有两个待办事项:

  • 添加一个复位按钮
    • 在点击这个按钮时,会触发一个本地对话框,要求用户确认该操作。
  • 添加一个通知触发器
    • 当定时器达到0时,需要向用户发送通知

但是,在我们开始处理这些项目之前,我们需要在src-tauri/tauri.config.json 文件中添加我们将要调用的本地API。如果我们不做这个步骤,我们将无法触发本地元素。

因此,前往src-tauri/tauri.config.json ,并将此添加到tauri.allowlist

  "tauri": {
    "allowlist": {
      // other allowed items
      "notification": {
        "all": true
      },
      "dialog": {
        "all": true
      }
    }    
  }

(注意:为了简单起见,dialognotification 的一切都被允许。我们可以更具体一些,以避免不必要的访问)

触发通知并添加一个重置计时器的按钮

// src/App.tsx
import { Button, Flex, Text } from "@chakra-ui/react";
import { useEffect, useState } from "react";
+ import { sendNotification } from "@tauri-apps/api/notification";
+ import { ask } from "@tauri-apps/api/dialog";
function App() {
  const [time, setTime] = useState(0);
  const [timerStart, setTimerStart] = useState(false);
  const buttons = [
    {
      value: 900,
      display: "15 minutes",
    },
    {
      value: 1800,
      display: "30 minutes",
    },
    {
      value: 3600,
      display: "60 minutes",
    },
  ];
  const toggleTimer = () => {
    setTimerStart(!timerStart);
  };
+ const triggerResetDialog = async () => {
+   let shouldReset = await ask("Do you want to reset timer?", {
+     title: "Pomodoro Timer App",
+     type: "warning",
+   });
+   if (shouldReset) {
+     setTime(900);
+     setTimerStart(false);
+   }
+ };
  useEffect(() => {
    const interval = setInterval(() => {
      if (timerStart) {
        if (time > 0) {
          setTime(time - 1);
        } else if (time === 0) {
+         sendNotification({
+           title: `Time's up!`,
+           body: `Congrats on completing a session!

Tauri提供了一个JS/TS API包,用于从Rust后端调用函数。必要的包已经被create-tauri-app ,所以我们可以直接导入npm包并在我们的前端代码中使用它。

用于触发一个通知

  • 从通知模块中导入sendNotification 函数
  • 在前台代码中调用该函数(见第42行)。更多关于API的信息请点击这里

对于触发一个对话框

  • 从对话框模块中导入ask 函数
  • 在前端代码中调用该函数。第一个参数是将出现在对话框中的文本(见第27行)。这里有更多关于API的信息
  • 由于它需要用户输入,该函数返回一个带有布尔值的承诺,因此我们可以await ,以获得用户点击的值
  • 如果用户接受,我们将时间值重置为15分钟,如果定时器已经在运行,则停止它

最后运行该应用程序,运行

npm run tauri dev

Pomodoro Timer Final

构建应用程序

构建用于分发的应用程序可以通过运行来实现。

cargo tauri build

这将会构建应用程序并创建一个二进制文件。src-tauri/target/release

现在,该应用程序已经准备好发布了!

总结

就这样吧!感谢你阅读我关于使用Vite和React的Tauri构建应用程序的演练教程。