硬核!全能 Deno 高手篇 下

本文涉及的产品
.cn 域名,1个 12个月
简介: 硬核!全能 Deno 高手篇

REST API 和 oak、fresh 框架


到这里,关于 Deno 的理论部分基本上已经学完了。

接下来我们将使用 Deno 设计并实现一个具有增删改查操作的 REST API。


使用标准库创建 HTTP 服务器


Deno 在标准库中提供了 http 模块,用于创建 HTTP 客户端和服务器。

这个模块的 API 非常简单,几行代码就可以轻松创建一个 HTTP 服务器。

下面就是利用 Deno 的 HTTP 模块创建服务器的代码,它监听了 5000 端口,并在接收到请求时返回字符串“你好,我是使用 Deno 构建的服务器”。


import { serve } from "https://deno.land/std@0.125.0/http/server.ts";
import { Status } from "https://deno.land/std@0.125.0/http/http_status.ts";
const port = 5000;
function reqHandler(req: Request): Response {
  console.log("\n接收到一个请求");
  const body = JSON.stringify({ message: "你好,我是使用 Deno 构建的服务器" });
  return new Response(body, {
    status: Status.OK, // 200
    headers: {
      "content-type": "application/json; charset=utf-8",
    },
  });
}
// 默认端口是 8000
serve(reqHandler, { port })
console.log("服务器已启动,端口: ", port);

其中 deno.land/std@0.125.0… 模块是 Deno 官方维护的 HTTP Status 枚举。这些枚举值是具有描述性的,比数字代码更容易阅读。

下面是它的部分源码。


export enum Status {
  OK = 200,
  /** RFC 7231, 6.3.2 */
  Created = 201,
  /** RFC 7231, 6.3.3 */
  Accepted = 202,
  /** RFC 7231, 6.3.4 */
  NonAuthoritativeInfo = 203,
  /** RFC 7231, 6.3.5 */
  NoContent = 204,
  /** RFC 7231, 6.5.1 */
  BadRequest = 400,
  /** RFC 7231, 6.6.1 */
  InternalServerError = 500,
  /** RFC 7231, 6.6.2 */
  NotImplemented = 501,
  /** RFC 7231, 6.6.3 */
  BadGateway = 502
// ...
}

Deno 团队从 1.13 版本开始投入了大量精力来改进 HTTP 模块,不断调整它的性能和稳定性,目前已经非常成熟。

但是标准库中的 HTTP Server API 的功能比较有限,它只提供了最基础的 API,我们直接用它很难开发出大型的 HTTP 服务器程序。所以在实际情况中我们通常会使用第三方的 HTTP 模块进行开发。

第三方 HTTP 模块会基于标准库模块进行封装,增加路由、中间件、日志记录等功能。

目前 Deno 生态中最受欢迎的第三方 HTTP 模块有 Oakfresh


使用 Oak 创建 HTTP 服务器


Oak 是受 Node.js 中著名的 HTTP 框架 Koa 启发而创建的框架,对很多 Node.js 的开发者来说,它应该很熟悉。

Oak 提供了 Application 类作为根目录来管理 HTTP 服务器的请求。使用这个类,我们可以使用 use 方法来注册中间件,使用 listen 方法启动我们的服务器。

下面是使用 Oak 的示例。


import { Application } from "https://deno.land/x/oak@v10.2.0/mod.ts";
const app = new Application();
const port = 5000;
app.use((context) => {
  context.response.body = "你好,我是使用 Oak 构建的服务器";
});
await app.listen({ port });
console.log('服务器已启动,端口: ', port)

路由


Oak 还提供了 Router 类,它可以创建出一个中间件,我们使用 use 方法将它注入到 Application 中,就可以把路由功能添加到我们服务器上。


import { Application, Router } from "https://deno.land/x/oak@v10.2.0/mod.ts";
const router = new Router();
router
  .get("/", (context) => {
   // 服务器基础路由: localhost:5400
    context.response.body = "服务根路径";
  })
  .get("/hello", (context) => {
   // 命名路由: localhost:5400/hello
  })
  .get("/hello/:id", (context) => {
   // 动态路由: localhost:5400/hello/abc
  });
const app = new Application();
// 注册中间件
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 5000 });

allowedMethods 返回一个控制注册路由请求的中间件。

如果客户端发送了 DELETE 请求,但是我们不想在路由中实现 DELETE 请求,那这个中间件就会返回状态码:405: Not Allowed。

如果我们实现了删除方法,但是匹配的路由不支持,那么它会返回状态码:501: Not Implemented。

当然这些都属于默认行为,我们可以根据需求自定义返回内容。


CORS


我们要想让不同域名的客户端连接服务器,就需要允许跨域请求,我们可以在服务器上定义 CORS 规则,最简单的方式是使用 CORS 库:deno.land/x/cors

和 Router 类似,CORS 库也返回一个中间件,我们可以在 Application 的实例上注册这个中间件,来开启跨域请求。


import { Application } from "https://deno.land/x/oak@v10.1.0/mod.ts";
import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";
const app = new Application();
//....
app.use(oakCors());
app.listen({ port: 5000 });

如果不给 oakCors 传递参数,那么会有一个默认参数:


{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}


使用 fresh 实现 Todo List


Oak 是一个专注于 REST API 开发的 HTTP 框架。与 Oak 不同的是,fresh 是一个全栈框架,它最重要的两个功能是路由和模板引擎。而 Oak 的功能主要是路由。

整体来看,Oak 更像是 Node.js 中的 Koa,而 fresh 更像 Node.js 中的 Next.js。但 fresh 的前端框架是 Preact。

fresh 虽然是第三方模块,但它是由 Deno 官方团队开发和维护的。fresh 发布的非常晚,但很受欢迎。从 7 月 1 日发布至今短短一个月时间,已经在 github 上面收获了 10k star,超过 oak 4k 左右了。

现在我们要使用 fresh 开发一个完整的 CRUD 应用:Todo List。

所谓的 CRUD,C 是指 Create、R 是指 Read、U 是指 Update、D 是指 Delete。一个具备 CRUD 的项目可以说是麻雀虽小,五脏俱全了。

使用 Fresh 提供的初始化脚本可以创建新项目,唯一的要求是 Deno CLI 的版本要大于等于 1.23.0。


deno run -A -r https://fresh.deno.dev todolist

安装过程它会询问是否使用 twind,这是一种样式框架,我们选择是。

之后它会询问是否使用 VSCode,我们同样选择是。


封装数据层


我们使用数据层来处理我们的数据。

在根目录下创建 data 文件夹,这里面将放置我们的数据层代码。

首先定义数据结构和存储层的 Schema。


export interface ITodo {
  id?: string;
  text?: string;
  completed?: boolean;
}
export type ITodos = ITodo[];
export interface ITodoListStore {
  _get(): ITodos;
  _set(todos: ITodos): void;
  get(): ITodos;
  create(todo: ITodo): ITodo;
  update(todo: ITodo): boolean;
  remove(id: string): boolean;
}

数据层是独立的,它不需要知道底层的存储是采用什么技术,它只是提供了操作数据的接口。这么设计是为了松耦合。

Deno 实现了和 Web API 中类似的存储 API。sessionStorage 和 localStorage。这两个 API 都可以存储字符串格式的数据。存储的上限是 10 MB。

我们可以使用 sessionStore 来实现这个存储接口。

创建 todo-list.session.ts 文件。


import { ITodo, ITodoListStore, ITodos } from "./todo-list.ts";
export class TodoListStore implements ITodoListStore {
  store: Storage;
  key: "todolist";
  constructor() {
    this.store = sessionStorage;
    this.key = "todolist";
  }
  _get() {
    return JSON.parse(this.store.getItem(this.key) || "[]");
  }
  _set(todos: ITodos): void {
    this.store.setItem(this.key, JSON.stringify(todos));
  }
  get() {
    return this._get();
  }
  create(todo: ITodo) {
    todo.id = crypto.randomUUID();
    todo.completed = false;
    const todos = this._get();
    todos.push(todo);
    this._set(todos);
    return todo;
  }
  update(todo: ITodo) {
    const todos = this._get();
    const findTodo = todos.find((t: ITodo) => t.id === todo.id);
    if (!findTodo) {
      return false;
    }
    Object.keys(todo).forEach((key) => {
      findTodo[key] = todo[key as keyof ITodo];
    });
    this._set(todos);
    return true;
  }
  remove(id: string): boolean {
    const todos = this._get();
    const idx = todos.findIndex((t: ITodo) => t.id === id);
    if (idx >= 0) {
      todos.splice(idx, 1);
      this._set(todos);
      return true;
    }
    return false;
  }
}

在全局注入数据层


我们要把数据存储的实例在程序初始化之前注入到全局,方便我们后续使用。

编写 setup.ts,在其中向 Deno 的全局对象 window 注入存储实例。


import { ITodoListStore } from "./data/todo-list.ts";
import { TodoListStore } from "./data/todo-list.memo.ts";
declare global {
  const todoListStore: ITodoListStore;
  interface Window {
    todoListStore: ITodoListStore;
  }
}
window.todoListStore = new TodoListStore();

然后在程序入口,也就是根目录中的 main.ts 的最顶部导入 setup.ts 文件。


import './setup.ts'

编写接口


和 Next.js 类似,routes/api 下面是约定好的接口目录。

我们需要创建 5 个接口:

  • /api/todo-list/ Get 获取所有任务列表
  • /api/todo-list/ Post 创建任务
  • /api/todo-list/:id Delete 删除任务
  • /api/todo-list/complete Put 完成任务
  • /api/todo-list/not-complete Put 未完成任务

创建 /routes/api/todo-list/index.ts 文件,编写获取所有任务列表和创建任务接口。


import { Handlers } from "$fresh/server.ts";
import { ITodo } from "../../../data/todo-list.ts";
import { getData } from "../../../utils/getData.ts";
const store = window.todoListStore
// deno-lint-ignore no-explicit-any
export function JSONtoString(json: any) {
  return JSON.stringify(json);
}
export const handler: Handlers = {
  GET(_) {
    return new Response(JSONtoString(store.get()), {
      headers: { "Content-Type": "application/json" },
    });
  },
  async POST(req) {
    const data = await getData(req.body);
    let response = null;
    if (data) {
      response = store.create(JSON.parse(data) as ITodo);
    }
    return new Response(JSONtoString(response), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

创建 /routes/api/todo-list/[id].ts 文件,中括号命名的文件是动态路由,其中 id 是动态路由参数。这个文件中是删除任务的接口。


import { Handlers } from "$fresh/server.ts";
import { JSONtoString } from "./index.ts";
const store = window.todoListStore
export const handler: Handlers = {
  DELETE(_, ctx) {
    return new Response(
      JSONtoString({ ok: store.remove(ctx.params.id as string) }),
      {
        headers: { "Content-Type": "application/json" },
      }
    );
  },
};

创建 /routes/api/todo-list/complete.ts 文件,编写完成任务接口。


import { Handlers } from "$fresh/server.ts";
import { ITodo } from "../../../data/todo-list.ts";
import { getData } from "../../../utils/getData.ts";
import { JSONtoString } from "./index.ts";
const store = window.todoListStore
export const handler: Handlers = {
  async PUT(req) {
    const data = await getData(req.body);
    let response = null;
    if (data) {
      const todo = JSON.parse(data) as ITodo;
      todo.completed = true;
      response = store.update(todo);
    }
    return new Response(JSONtoString({ ok: response }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

创建 /routes/api/todo-list/not-complete.ts 文件,编写未完成任务接口。


import { Handlers } from "$fresh/server.ts";
import { ITodo } from "../../../data/todo-list.ts";
import { getData } from "../../../utils/getData.ts";
import { JSONtoString } from "./index.ts";
const store = window.todoListStore
export const handler: Handlers = {
  async PUT(req) {
    const data = await getData(req.body);
    let response = null;
    if (data) {
      const todo = JSON.parse(data) as ITodo;
      todo.completed = false;
      response = store.update(todo);
    }
    return new Response(JSONtoString({ ok: response }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

编写前端数据请求函数


在根目录下创建 apis 文件夹,再创建 todolist.ts 文件,编写前端的接口请求函数。


import { ITodo } from "../data/todo-list.ts";
export const get = async () =>
  await fetch("/api/todo-list").then((res) => res.json());
export const add = async (body: ITodo) =>
  await fetch("/api/todo-list", {
    method: "POST",
    body: JSON.stringify(body),
  }).then((res) => res.json());
export const remove = async (id: string) =>
  await fetch(`/api/todo-list/${id}`, {
    method: "DELETE",
  }).then((res) => res.json());
export const complete = async (id: string) =>
  await fetch(`/api/todo-list/complete`, {
    method: "PUT",
    body: JSON.stringify({ id }),
  }).then((res) => res.json());
export const notComplete = async (id: string) =>
  await fetch(`/api/todo-list/not-complete`, {
    method: "PUT",
    body: JSON.stringify({ id }),
  }).then((res) => res.json());

封装按钮组件


首先封装一下按钮组件,在 components 文件夹下修改 Button.tsx 文件。


/** @jsx h */
import { h } from "preact";
export function Button(
props: h.JSX.HTMLAttributes<HTMLButtonElement> & {
 class?: string;
 }
) {
  return (
    <button
      {...props}
      class={`
      whitespace-nowrap
      py-1
      px-2
      my-1/2
      mx-1
      border-2
      rounded
      outline-none
      hover:outline-none
      focus:outline-none
      ${props.class}
      `}
      />
  );
}

封装 TodoList 组件


在 islands 目录下创建 TodoList.tsx,这是我们主要的组件。


/** @jsx h */
import { h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import "twind/shim";
import { get, add, complete, notComplete, remove } from "../apis/todolist.ts";
import { Button } from "../components/Button.tsx";
import { ITodo, ITodos } from "../data/todo-list.ts";
const AddButton = (props: any) => (
  <Button
    {...props}
    class="text-green-600 border-green-600 hover:text-white hover:bg-green-600"
  />
);
const CompleteButton = (props: any) => (
  <Button
    {...props}
    class="text-green-300 border-green-300 hover:text-white hover:bg-green-300"
  />
);
const NotCompleteButton = (props: any) => (
  <Button
    {...props}
    class="hover:text-white text-gray-400 border-gray-400 hover:bg-gray-400"
  />
);
const RemoveButton = (props: any) => (
  <Button
    {...props}
    class="text-red-400 border-red-400 hover:text-white hover:bg-red-400"
  />
);
const TodoItem = (props: ITodo & { refresh: Function }) => (
  <div
    class={`flex  py-2 px-3 items-center border-b-4 border-slate-400
  ${props.completed ? " bg-green-100 " : " bg-red-100 "}`}
  >
    <p class="w-full text-grey-darkest">{props.text}</p>
    {props.completed ? (
      <NotCompleteButton
        onClick={async () => {
          if (props.id) {
            const res = await notComplete(props.id);
            res.ok && props.refresh();
          }
        }}
      >
        未完成
      </NotCompleteButton>
    ) : (
      <CompleteButton
        onClick={async () => {
          if (props.id) {
            const res = await complete(props.id);
            res.ok && props.refresh();
          }
        }}
      >
        完成
      </CompleteButton>
    )}
    <RemoveButton
      onClick={async () => {
        if (props.id) {
          const res = await remove(props.id);
          res.ok && props.refresh();
        }
      }}
    >
      移除
    </RemoveButton>
  </div>
);
export default function TodoList(props: any) {
  const [todoList, setTodoList] = useState<ITodos>([]);
  const inputRef = useRef<HTMLInputElement>(null);
  const refresh = () => {
    get().then((todoList) => {
      setTodoList(todoList);
      console.log(todoList, "todoList");
    });
  };
  useEffect(refresh, []);
  const _add = () => {
    const inputEl = inputRef.current;
    if (inputEl) {
      const body = {
        text: inputEl.value,
      };
      add(body)
        .then(refresh)
        .finally(() => {
          inputEl.value = "";
        });
    }
  };
  return (
    <div class="h-100 w-full flex items-center justify-center bg-teal-lightest font-sans">
      <div class="bg-white rounded shadow p-6 m-4 w-full lg:w-3/4 lg:max-w-lg">
        <div class="mb-4">
          <h1 class="text-grey-darkest">任务列表</h1>
          <div class="flex items-center mt-4">
            <input
              class="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-grey-darker"
              placeholder="添加任务"
              ref={inputRef}
              onKeyPress={(evt) => {
                evt.key === "Enter" && _add();
              }}
            ></input>
            <AddButton onClick={_add}>添加</AddButton>
          </div>
        </div>
        <div>
          {todoList.map((todo) => (
            <TodoItem {...todo} refresh={refresh} />
          ))}
        </div>
      </div>
    </div>
  );
}

最后在 routes/index.ts 中导入 TodoList.tsx 组件即可。


/** @jsx h */
import { h } from "preact";
import TodoList from "../islands/TodoList.tsx";
export default function Page() {
  return (<TodoList />);
}

运行


到这一步,TodoList 应用已经大功告成。

在命令行运行下列命令启动项目:


deno task start

项目默认地址是:http://localhost:8000/


部署


在上面我们已经使用 fresh 开发了一个 TodoList 应用,现在我们来学习如何将它部署到线上。

只要我们将应用部署上线,就可以让全世界任何一个人都去使用它。

但是通常来说,部署是一个比较麻烦的过程。所以涌现了非常多的部署工具来帮助我们简化这一过程。比如 Github Actions、Vercel 等。

想象一下,如果我们只需要在 Github 上面提交代码,应用就可以自动部署,那不是非常令人舒服的事情吗?这种理想的部署方式,正是 Deno Deploy 在做的事。

Deno 的创造者曾经说过(已翻译):

Deno Deploy 是一个分布式系统,在全球范围内运行 JavaScript、TypeScript 和 WebAssembly。 该服务将 V8 JavaScript 运行时和高性能异步 Web 服务器深度集成,来提供最佳性能,而不需要不必要的中间抽象。

Deno Deploy 在全球多个数据中心运行。目前在欧洲、美洲、亚洲和澳大利亚等共 25 个地区和城市都有服务器,并且还在不断增长。

Deno Deploy 使用和 Deno CLI 相同的系统,我们不需要做其他任何事情就可以使用它。它的目标是让部署变得容易。

我们可以在 Deno Deploy 的官网(deno.com/deploy)通过 Github 账号进行注册。

登录成功后就可以创建项目了。

项目是 Deno Deploy 的核心概念,它对应的就是我们的应用程序。

我们点击 new project 就可以创建项目了。

image.png

创建项目时,我们有两种选择。

  • 从 Github 仓库部署项目
  • 创建 playground 项目


从 Github 仓库部署项目


我们只需要将项目提交到 Github,然后选择仓库、分支;输入项目名就可以创建项目了。

我们可以设置特定分支作为生产分支,这样只有提交到生产分支后才会触发自动部署。

部署模式也有两种:

  • Automatic:我们每次推送到仓库后,Deno Deploy 都会进行自动部署。它的缺点是不允许自定义构建步骤。但优点是简单。它适合大多数用户。
  • Github Action:这种模式适合我们需要自定义构建步骤的情况。我们可以将自定义操作加入到构建流中,它比自动模式更加复杂,但是它让我们对整个构建流程有了更多控制能力。

在选择生产分支和部署模式后,我们还要选择部署的文件,最后 Deno Deploy 会生成预览链接和生产链接。


创建 playground 项目


当我们需要快速部署和测试某个想法时,playground 是最快的一种方式。

playground 可以只处理一个文件,我们可以在 Deno Deploy 提供的 Web IDE 中快速修改并部署项目,以此来快速验证自己的想法。

image.png

playground 同样具备完整的自定义域名、环境变量和崩溃报告等功能。

在顶部菜单栏中,我们还可以选择自己使用的语言,比如 JavaScript、TypeScript、JSX 和 TSX。在创建项目时,默认会使用 TypeScript。

为了提高体验,我们甚至在按下保存快捷键时,它就会自动部署项目,我们根本不需要点击 Save&Deploy 按钮。只需要几秒钟,新版本的代码就上线了。

默认情况下,playground 项目是不公开的,没有人可以查看我们的代码。可以在项目设置中将它设置为公开,这样就可以共享给其他人了。

image.png


将 Todo List 应用部署上线


我们首先要将 TodoList 的源代码推送到 Github,然后就可以选择从 Github 创建项目,选择仓库、分支、入口文件(main.ts)、项目名,就可以创建成功。

项目名我使用的是 fresh-todolist,这个名字会作为子域名,子域名 URL 的格式是 project_name.deno.dev。所以项目的访问地址是:fresh-todolist.deno.dev/

但是我们访问时发现了一个 502 Bad Gateway 错误。

image.png

Deno Deploy 提供了一个 Logs 功能,可以排查线上错误。

image.png

通过排查,发现 Deno Deploy 的 Deno 版本不支持 sessionStorage API。

因为我们已经创建了接口,所以只需要换一种技术来实现接口即可。

我们可以使用内存模型来存储数据,创建 /data/todo-list.memo.ts 文件,实现 ITodoListStore 接口。


import { ITodo, ITodoListStore, ITodos } from "./todo-list.ts";
let store: ITodos = [];
export class TodoListStore implements ITodoListStore {
  _get() {
    return store;
  }
  _set(todos: ITodos): void {
    store = todos;
  }
  get() {
    return this._get();
  }
  create(todo: ITodo) {
    todo.id = crypto.randomUUID();
    todo.completed = false;
    const todos = this._get();
    todos.push(todo);
    this._set(todos);
    return todo;
  }
  update(todo: ITodo) {
    const todos = this._get();
    const findTodo = todos.find((t: ITodo) => t.id === todo.id);
    if (!findTodo) {
      return false;
    }
    Object.keys(todo).forEach((key) => {
      if (key in findTodo) {
        (findTodo as any)[key] = todo[key as keyof ITodo];
      }
    });
    this._set(todos);
    return true;
  }
  remove(id: string): boolean {
    const todos = this._get();
    const idx = todos.findIndex((t: ITodo) => t.id === id);
    if (idx >= 0) {
      todos.splice(idx, 1);
      this._set(todos);
      return true;
    }
    return false;
  }
}

然后把 setup.ts 中的数据层改为内存模型的存储。


import { TodoListStore } from "./data/todo-list.memo.ts";
// ...

提交代码,Deno Deploy 会重新部署项目。现在一切正常了。

可以通过 fresh-todolist.deno.dev/ 访问线上项目。


你是否需要 Deno?


通过两篇文章的学习,相比你已经能够熟练使用 Deno 了。

Deno 的学习曲线相对平坦,如果你熟悉 JavaScript 和 Node.js。那么学习 Deno 应该非常轻松。

虽然 Deno 被很多人寄予厚望,但现在断言 Deno 将来可以取代 Node.js 还为之过早。

Deno 的潜力是巨大的,但也不能太过于盲目崇拜。起码在目前来说,Node.js 更加成熟稳定。

如果现在我们已经使用 Node.js 开发了一个应用,我们不应该过于着急的将它迁移到 Deno。Node.js 虽然有很多不完美的地方,但是它的生态是非常完善的,有着非常多的用户和第三方库。毕竟在生态方面,Node.js 比 Deno 有着十几年的优势,这是在短时间内无法改变的。

所以,在未来相当长的一段时间内,Node.js 依然是最好的选择。


什么时候选择 Node.js?


Deno 的开发速度和开发体验是明显优于 Node.js 的。当我们需要开发一些小型项目、或者验证某些概念性项目、MVP 版本的项目,都可以在一开始就使用 Deno。

现在的技术发展迅速,在 JavaScript 运行时中,除了 Node.js 和 Deno 以外,还出现了 Bun。

我打算在未来一段时间来深入研究 Bun,如果你对 Deno 和 Bun 感兴趣的话,欢迎点赞留言与我进行交流。



相关文章
|
6月前
|
存储 Ubuntu Shell
使用 Python 创造你自己的计算机游戏(游戏编程快速上手)第四版:致谢到第四章
使用 Python 创造你自己的计算机游戏(游戏编程快速上手)第四版:致谢到第四章
122 0
|
2月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
187 59
|
2月前
|
Rust 安全 前端开发
30天拿下Rust之图形编程
30天拿下Rust之图形编程
36 0
|
3月前
|
缓存 安全 数据库连接
这个库堪称Python编程的瑞士军刀!
这个库堪称Python编程的瑞士军刀!
|
6月前
|
IDE 算法 开发工具
Scratch编程v3.29.1少儿编程工具
SCRATCH是一款由麻省理工学院(MIT)媒体实验室开发的图形化编程语言和集成开发环境(IDE)。它的目标是让编程变得有趣、直观且易学,尤其是针对儿童和青少年群体。通过SCRATCH,用户可以通过拖放代码块的方式来创建动画、故事、游戏等多媒体项目,无需深入了解复杂的编程语法和结构。
117 2
|
存储 Kubernetes JavaScript
🚀 2023 年你必须贡献的 9 个国外开源库 🔥
🚀 2023 年你必须贡献的 9 个国外开源库 🔥
🚀 2023 年你必须贡献的 9 个国外开源库 🔥
|
算法 编译器 开发工具
强烈推荐的两个神级教学项目: nand2tetris 与 MIT6.828
强烈推荐的两个神级教学项目: nand2tetris 与 MIT6.828
97 0
|
Web App开发 编解码 JavaScript
硬核!全能 Deno 高手篇 上
硬核!全能 Deno 高手篇
295 0
|
机器学习/深度学习 人工智能 安全
这款编译器能让Python和C++一样快:最高提速百倍,MIT出品
这款编译器能让Python和C++一样快:最高提速百倍,MIT出品
120 0
|
数据采集 Linux 开发工具
猿创征文 | 三款Python学习开发任选工具
猿创征文 | 三款Python学习开发任选工具
116 0
猿创征文 | 三款Python学习开发任选工具
下一篇
无影云桌面