Blazor与typeSrcipt交互

2022/8/27 23:23:49

本文主要是介绍Blazor与typeSrcipt交互,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

创建 Blazor 项目

创建新的 Blazor WebAssembly App

image

将其命名为BlazerWithTSInterop在您选择的目录中。

image

仅使用 .NET 6.0 客户端,无安全性且无 PWA。

image

CTRL+F5 在热重载模式下生成和运行。

image

总结已经创建了一个准备演示 JavaScript 互操作演练的项目。忽略模板附带的“计数器”和“获取数据”页。此演示将仅使用主页。

实现 JavaScript 互操作

在我们开始Typescript之前,让我们看看JavaScript是如何互操作的。

1. 调用 JavaScript 浏览器 API

在 Pages/Index.razor 的所有内容替换为以下代码片段。

@page "/"

@inject IJSRuntime JS

<h1>Hello, Interop!</h1>
<hr />@Message<hr />
<h4>JS Interop</h4>
<button class="btn btn-primary" @onclick="@Prompt">Prompt</button>
<hr>

@code{
    string Message { get; set; } = string.Empty;

    async void Prompt(){

        string answer = await JS.InvokeAsync<string>("propmpt","say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing":answer)}";
    }
}

解释一下:

JS.InvokeAsync:

​ 第一个参数是:执行JavaScript 函数的名称

​ 第二个参数是:执行JavaScript 函数的参数

​ 返回一个字符串

保存以在热重载模式下运行并进行测试。

image

2. 调用嵌入式 JavaScript

创建新的 JavaScript 文件。为JavaScript和Typescript文件创建新的'js'文件夹。
创建新的“wwwroot/js/script.js”文件。

image

添加下面的代码到script.js

function ScriptPrompt(message) {
    return prompt(message);
}

function ScriptAlert(message) {
    alert(message);
}

ScriptPrompt 和 ScriptAlert 将静态加载和全局。
其他 JavaScript 模块(包括隔离模块)可访问。

请注意,脚本方法调用浏览器 API 提示并恭敬地发出警报。

在“wwwroot/index.html”中的“webassemly.js”之后添加“script.js”作为静态资源

<body>
...
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="src/script.js"></script>
...
</body>

请将所有“Pags/Index.razor”内容替换为以下代码片段。
使用操作方法添加脚本提示和脚本警报按钮。

@page "/"

@inject IJSRuntime JS

<h1>Hello, Interop!</h1><br />
<h4 style="background-color:aliceblue; padding:20px">JavaScript Interop</h4>
@Message<hr />

<button class="btn btn-primary" @onclick="@Prompt">Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptPrompt">Script Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptAlert">Script Alert</button>
<hr>

@code{
    string Message { get; set; } = string.Empty;

    async void Prompt()
    {

        string answer = await JS.InvokeAsync<string>("prompt","say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing":answer)}";

        StateHasChanged();
    }


    async void SrciptPrompt()
    {
        // 
        string answer = await JS.InvokeAsync<string>("ScriptPrompt", "say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing" : answer)}";

        StateHasChanged();
    }

    async void ScriptAlert()
    {
        await JS.InvokeAsync("ScriptAlert", "Script Alert");
    }

}

运行并测试。

image

3. 调用隔离的JavaScript

创建新的 'wwwroot/js/script.module.js' JavaScript 文件。

image

将下面的代码添加到'wwwroot/js/script.module.js'

export function ModulePrompt(message) {
    return ScriptPrompt(message);
}

export function ModulAlert(message) {
    return ScriptAlert(message);
}

模块方法演示如何调用全局脚本方法。

请注意“export”方法前缀。这是将代码标记为可导入的 ES 模块语法。全局嵌入式 script.js 不使用“export”。

将所有“Index.razor”内容替换为以下代码片段,以添加模块按钮和方法。

@page "/"

@inject IJSRuntime JS
@implements IAsyncDisposable

<h1>Hello, Interop!</h1><br />
<h4 style="background-color:aliceblue; padding:20px">JavaScript Interop</h4>
@Message<hr />

<button class="btn btn-primary" @onclick="@Prompt">Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptPrompt">Script Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptAlert">Script Alert</button>
<hr>

<button class="btn btn-primary" @onclick="@ModulelPrompt">Module Prompt</button>
<button class="btn btn-primary" @onclick="@ModulelAlert">Module Alert</button>
<hr>

@code{
    private IJSObjectReference module;

    string Message { get; set; } = string.Empty;

    string Version{ get => $"?v={DateTime.Now.Ticks.ToString()}"; }


    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
            await module.DisposeAsync();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if(firstRender){
            module = await JS.InvokeAsync<IJSObjectReference>("import", $"./src/script.module.js{Version}");
        }
    }

    async void ModulelAlert()
    {
        await module.InvokeVoidAsync("ModulAlert", "Modulel Alert");
    }

    async void ModulelPrompt()
    {
        string answer = await module.InvokeAsync<string>("ModulePrompt", "Module Prompt say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing" : answer)}";
        StateHasChanged();
    }

    async void Prompt()
    {

        string answer = await JS.InvokeAsync<string>("prompt","say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing":answer)}";

        StateHasChanged();
    }

    async void ScriptPrompt()
    {
        string answer = await JS.InvokeAsync<string>("ScriptPrompt", "say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing" : answer)}";

        StateHasChanged();
    }

    async void ScriptAlert()
    {
        await JS.InvokeVoidAsync("ScriptAlert", "Script Alert");
    }

}

隔离的模型支持 IAsyncDisposable 与 DisposeAsync 在不再需要时清理模块资源。
模块在首次呈现后由 OnAfterRenderAsync 方法加载。
ModulePrompt 演示如何调用静态方法 ScriptPrompt。
ModuleAlert 演示如何从同一模块调用另一个导出的方法。

通知模块在加载时附加一个唯一参数 Version 标记:

...
string Version { get { return "?v=" + DateTime.Now.Ticks.ToString(); } }
...
module = await JS.InvokeAsync<IJSObjectReference>
                    ("import", "./js/script.module.js" + Version);
...

script.module.js 避免了由唯一参数标记缓存。

image

这是一个绕过浏览器缓存的黑客,该缓存可能会在开发过程中粘附。
要重新获得缓存性能,版本值可以替换为应用程序发行版号。
然后,这将在新版本的第一次客户端运行时强制缓存刷新一次。

构建并运行。

image

**总结 ** 第 2 部分介绍了如何调用独立和嵌入式 JavaScript。从 Blazor 调用 TypeScript 互操作的前身。

调试 JavaScript

现在是回顾从Visual Studio调试JavaScript的好时机

Visual Studio可能会犹豫是否要附加到Chrome调试器。这个问题不是Blazor独有的。

随着JavaScript代码和符号的增长,这一点更加明显。以下是一些可能有帮助的情况和解决方法。

在中设置断点script.js如图所示

image

在调试模式 F5 下运行应用程序。
如果断点红色圆圈为空心,则不会附加调试器。

image

您可以在“脚本文档”文件夹中看到缓存的文件。
单击文件以查看缓存的内容是否来自以前的版本。

image

请尝试删除断点并重新应用。
调试器可能会重新附加。

应用运行时,在浏览器中按 CTR+Shift+I 可查看开发人员工具。
在“source”面板中选择 js/script.js,并在显示处设置断点。
这将触发Visual Studio调试器重新附加到Chrome。如果这不起作用,则在Chrome中调试就足够了。

image

**总结 ** 第 3 部分介绍了调试和调试器附件解决方法。建议执行调试代码演练以查看操作中的互操作

实现 TypeScript 互操作

1. 调用隔离 TypeScript

创建新的 'scripts/hello.ts' TypeScript 文件。

image

将代码添加到“hello.ts”。
注意 类方法从嵌入式“script.js”访问脚本警报

declare function ScriptAlert(message: string);

export class Hello{

    hello(): void {
        ScriptAlert("hello");
    }

    static goodbye(): void {
        ScriptAlert("goodbye");
    }

}


export var HelloInstance = new Hello();

安装 Microsoft.TypeScript.MSBuild

右键单击项目节点,然后选择“添加”>“新项”。 选择“TypeScript JSON 配置文件”,然后单击“添加”。

Visual Studio 会将 tsconfig.json 文件添加到项目根目录中。 可以使用此文件为 TypeScript 编译器配置选项。

打开 tsconfig.json 并更新,以设置所需的编译器选项。

设置版本:ECMAScript, TSX:无, 模块:ES2015 在项目/属性/类型脚本构建

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es2015",
    "outDir": "wwwroot/js"
  },
  "include": [
    "scripts/**/*"
  ]
}

将所有“Index.razor”内容替换为以下代码片段,以添加模块按钮和方法。

@page "/"

@inject IJSRuntime JS
@implements IAsyncDisposable

<h1>Hello, Interop!</h1><br />
<h4 style="background-color:aliceblue; padding:20px">JavaScript Interop</h4>
@Message<hr />

<button class="btn btn-primary" @onclick="@Prompt">Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptPrompt">Script Prompt</button>
<button class="btn btn-primary" @onclick="@ScriptAlert">Script Alert</button>
<hr>

<button class="btn btn-primary" @onclick="@ModulelPrompt">Module Prompt</button>
<button class="btn btn-primary" @onclick="@ModulelAlert">Module Alert</button>
<hr>

<h4 style="background-color:aliceblue; padding:20px">TypeScript Interop</h4><hr>
<button class="btn btn-primary" @onclick="@HelloAlert">Hello Alert</button>

@code{
    private IJSObjectReference module;
    private IJSObjectReference hello;

    string Message { get; set; } = string.Empty;

    string Version{ get => $"?v={DateTime.Now.Ticks.ToString()}"; }


    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
            await module.DisposeAsync();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if(firstRender){
            module = await JS.InvokeAsync<IJSObjectReference>("import", $"./js/script.module.js{Version}");
            hello = await JS.InvokeAsync<IJSObjectReference>("import", $"./js/hello.js{Version}");
        }
    }

    async void ModulelAlert()
    {
        await module.InvokeVoidAsync("ModulAlert", "Modulel Alert");
    }

    async void ModulelPrompt()
    {
        string answer = await module.InvokeAsync<string>("ModulePrompt", "Module Prompt say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing" : answer)}";
        StateHasChanged();
    }

    async void Prompt()
    {

        string answer = await JS.InvokeAsync<string>("prompt","say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing":answer)}";

        StateHasChanged();
    }


    async void ScriptPrompt()
    {
        string answer = await JS.InvokeAsync<string>("ScriptPrompt", "say what?");
        Message = $"Prompt:{(string.IsNullOrEmpty(answer) ? "nothing" : answer)}";

        StateHasChanged();
    }

    async void ScriptAlert()
    {
        await JS.InvokeVoidAsync("ScriptAlert", "Script Alert");
    }


    async void HelloAlert()
    {
        await hello.InvokeVoidAsync("HelloInstance.hello");
        await hello.InvokeVoidAsync("Hello.goodbye");
    }

}

添加了另一个模块“hello”来加载由“hello.ts”生成的JavaScript文件“hello.js”。HelloAlert 方法演示调用 TypeScript 类方法“goodbye”和对象实例方法“hello”。这些方法使用嵌入式脚本“script.js”中的脚本警报函数

构建、运行和测试 Hello 警报。

image

2. 设置 Webpack 构建管道

右键单击“BlazerWithTSInterop”文件夹,然后选择弹出菜单项“在终端中打开”。

这将在编辑器中打开 PowerShell 终端窗口。

image

执行以下命令以创建 package.json

npm init -y

image

执行以下命令以安装 webpack 和 typescript 工具

npm i ts-loader typescript webpack webpack-cli

在 'package.json' 中添加脚本条目 "build": "webpack" 或者将 'package.json' 替换为下面的 json 内容。

{
  "name": "blazerwithtsinterop",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ts-loader": "^9.3.1",
    "typescript": "^4.8.2",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

修改tsconfig.json

{
  "display": "Node 14",
  "compilerOptions": {
    "allowJs": true,
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "lib": [ "es2020", "DOM" ],
    "target": "es6",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "node"
  },
  "include": [
    "scripts/**/*"
  ],
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

这将禁用不再适用的项目 TypeScript 生成属性。Visual Studio现在将使用tsconfig.json进行TypeScript配置。

在“BlazerWithTSInterop”文件夹中创建webpack.config.json,其中包含以下内容。

const path = require("path");

module.exports = {
    mode: 'development',
    devtool: 'eval-source-map',
    module: {
        rules: [
            {
                test: /\.(ts)$/,
                exclude: /node_modules/,
                include: [path.resolve(__dirname, 'scripts')],
                use: 'ts-loader',
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    entry: {
        index: ['./scripts/index']
    },
    output: {
        path: path.resolve(__dirname, './wwwroot/public'),
        filename: '[name]-bundle.js',
        library: "[name]"
    }
};

此脚本告诉 webpack 使用 ts-loader 将 .ts 文件转译为.js。
对于每个条目 [name] 创建一个 JavaScript 库 [name]。
文件 [name]-bundle 在“wwwroot/public”文件夹中进行类型化。
此脚本有一个名为“index”的条目。
将输入文件 './scripts/index.ts' 转译为输出文件 './scripts/index.js'。
第二个传递将 “./scripts/index.js” 与依赖项代码捆绑在一起,并输出到文件 '../wwwroot/public/index-bundle.js'

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
...
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="npm install" WorkingDirectory="wwwroot" />
    <Exec Command="npm run build" WorkingDirectory="wwwroot" />
  </Target>
...
</Project>

不再需要 Microsoft.TypeScript.MSBuild 过程,因为它绕过了 webpack typescript pre-build。

没有做任何伤害,让它留在这个演示中。或者,您可以选择并删除以将其删除。

image

将代码添加到“index.ts”。索引类是 Hello 类包装器。索引模块还导出 Hello 类和 HelloInstance 对象。

import { Hello, HelloInstance } from "./hello";
export { Hello, HelloInstance } from "./hello";

export class Index {

    hello(): void {
        HelloInstance.hello();
    }

    static goodbye(): void{
        Hello.goodbye();
    }
}

export var IndexInstance = new Index()

生成 Ctrl+Shift+B 会在“公共”目录中创建“index.js”和“index_bundle.js”。

image

在新的“index-bundle.js”中搜索“ScriptAlert”。 验证 'index.js' 包包含 'hello.js' 依赖代码。 捆绑包包括相关代码。 然而; 一个包将只有一个模块条目。 Interop 只能访问从模块中导出的项目。

在 'script.js' 之后的 'Index.html' 中添加 'index-bundle.js' 作为静态资源。

<body>
...
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="js/script.js"></script>
    <script src="public/index-bundle.js"></script>
...
</body>

在 'Index.razor' html 部分中,将此行添加为最后一个按钮。

<button class="btn btn-primary" @onclick="@BundleIndexHello">Bundle Index Hello</button>

在“Index.razor”代码部分添加这个作为最后一个方法。

async void BundleIndexHello()
{
    await JS.InvokeVoidAsync("index.IndexInstance.hello");
    await JS.InvokeVoidAsync("index.Index.goodbye");
}

构建并运行。

image

Bundle Index Hello 按钮演示调用从“index”库导出的索引类方法

在 'Index.razor' html 部分中,将此行添加为最后一个按钮。

<button class="btn btn-primary" @onclick="@ReExportHello">ReExport Hello</button>

在“Index.razor”代码部分添加这个作为最后一个方法。

async void ReExportHello()
{
    await JS.InvokeVoidAsync("index.HelloInstance.hello");
    await JS.InvokeVoidAsync("index.Hello.goodbye");
}

构建并运行。

image

ReExport Hello 按钮演示调用从“index”库导出的 Hello 类方法

总结 本节介绍了从 Webpack 包调用 TypeScript。 包可以通过从包入口模块导出来公开依赖代码。 捆绑包是 Blazor 互操作可通过捆绑包库前缀访问的嵌入式资源。

调用 NPM TypeScript

右键单击项目控制台执行下面的命令,将threejs添加到 package.json

npm i three

创建新的“scripts/cube.ts”文件,将代码复制到“cube.js”。

import * as THREE from 'three';

export class Cube {

    camera: THREE.PerspectiveCamera;
    scene: THREE.Scene;
    renderer: THREE.WebGLRenderer;
    cube: any;

    constructor() {
        this.camera = new THREE.PerspectiveCamera(75, 2, .1, 5);
        this.camera.position.z = 2;
        let canvas = document.querySelector('#cube') as HTMLCanvasElement;
        this.renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
        this.scene = new THREE.Scene();
        this.scene.background = null;
        const light = new THREE.DirectionalLight(0xFFFFFF, 1);
        light.position.set(-1, 2, 4);
        this.scene.add(light);

        const geometry = new THREE.BoxGeometry(1, 1, 1);
        const loadManager = new THREE.LoadingManager();
        const loader = new THREE.TextureLoader(loadManager);
        const texBlazor = loader.load('images/blazor.png');
        const texInterop = loader.load('images/interop.png');
        const texCircle = loader.load('images/tscircle.png');

        const matBlazor = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texBlazor, transparent: false, opacity: 1 });
        const matInterop = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texInterop, transparent: false, opacity: 1 });
        const matCircle = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texCircle, transparent: false, opacity: 1 });
        const materials = [matBlazor, matInterop, matCircle, matBlazor, matInterop, matCircle];

        loadManager.onLoad = () => {
            this.cube = new THREE.Mesh(geometry, materials);
            this.scene.add(this.cube);
            this.animate();
        };
    }

    animate(time = 0) {
        time = performance.now() * 0.0005;
        this.cube.rotation.x = time;
        this.cube.rotation.y = time;
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(this.animate.bind(this));
    }

    static Create(): void {
        new Cube();
    }

}

将多维数据集条目添加到 webpack.config.js用下面的代码段替换 entry: 部分。

entry: {
    index: ['./scripts/index'],
    cube:['./scripts.cube.js']
}

在“Index.html”中添加“cube-bundle.js”作为静态资产。

<script src="public/cube-bundle.js"></script>

在“Index.razor”html 部分的末尾添加立方体画布。

<canvas id="cube"/>

将“Index.razor”中的 OnAfterRenderAsync 替换为具有多维数据集互操作调用的以下方法。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if(firstRender){
        module = await JS.InvokeAsync<IJSObjectReference>("import", $"./js/script.module.js{Version}");
        hello = await JS.InvokeAsync<IJSObjectReference>("import", $"./js/hello.js{Version}");
        await JS.InvokeVoidAsync("cube.Cube.Create");
    }
}

构建并运行。

image

互操作软件设计

利用 TypeScript 有利于互操作软件设计。 典型的结构设计模式。
TypeScript 转换为浏览器 JavaScript 准备互操作。
Blazor C# 编译为准备执行的 Wasm 浏览器。

Blazor C# 界面设计与 TypeScript 对应物保持一致。

image

Blazor Wasm 通过可互操作的 JavaScript 与浏览器对话。

原文连接:Trog.NET Blazor Typescript Interop



这篇关于Blazor与typeSrcipt交互的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程