创建 Blazor 项目
创建新的 Blazor WebAssembly App
将其命名为BlazerWithTSInterop在您选择的目录中。
仅使用 .NET 6.0 客户端,无安全性且无 PWA。
CTRL+F5 在热重载模式下生成和运行。
总结已经创建了一个准备演示 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 函数的参数
返回一个字符串
保存以在热重载模式下运行并进行测试。
2. 调用嵌入式 JavaScript
创建新的 JavaScript 文件。为JavaScript和Typescript文件创建新的'js'文件夹。
创建新的“wwwroot/js/script.js”文件。
添加下面的代码到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");
}
}
运行并测试。
3. 调用隔离的JavaScript
创建新的 'wwwroot/js/script.module.js' JavaScript 文件。
将下面的代码添加到'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 避免了由唯一参数标记缓存。
这是一个绕过浏览器缓存的黑客,该缓存可能会在开发过程中粘附。
要重新获得缓存性能,版本值可以替换为应用程序发行版号。
然后,这将在新版本的第一次客户端运行时强制缓存刷新一次。
构建并运行。
**总结 ** 第 2 部分介绍了如何调用独立和嵌入式 JavaScript。从 Blazor 调用 TypeScript 互操作的前身。
调试 JavaScript
现在是回顾从Visual Studio调试JavaScript的好时机
Visual Studio可能会犹豫是否要附加到Chrome调试器。这个问题不是Blazor独有的。
随着JavaScript代码和符号的增长,这一点更加明显。以下是一些可能有帮助的情况和解决方法。
在中设置断点script.js如图所示
在调试模式 F5 下运行应用程序。
如果断点红色圆圈为空心,则不会附加调试器。
您可以在“脚本文档”文件夹中看到缓存的文件。
单击文件以查看缓存的内容是否来自以前的版本。
请尝试删除断点并重新应用。
调试器可能会重新附加。
应用运行时,在浏览器中按 CTR+Shift+I 可查看开发人员工具。
在“source”面板中选择 js/script.js,并在显示处设置断点。
这将触发Visual Studio调试器重新附加到Chrome。如果这不起作用,则在Chrome中调试就足够了。
**总结 ** 第 3 部分介绍了调试和调试器附件解决方法。建议执行调试代码演练以查看操作中的互操作
实现 TypeScript 互操作
1. 调用隔离 TypeScript
创建新的 'scripts/hello.ts' TypeScript 文件。
将代码添加到“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 警报。
2. 设置 Webpack 构建管道
右键单击“BlazerWithTSInterop”文件夹,然后选择弹出菜单项“在终端中打开”。
这将在编辑器中打开 PowerShell 终端窗口。
执行以下命令以创建 package.json
npm init -y
执行以下命令以安装 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。
没有做任何伤害,让它留在这个演示中。或者,您可以选择并删除以将其删除。
将代码添加到“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”。
在新的“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");
}
构建并运行。
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");
}
构建并运行。
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");
}
}
构建并运行。
互操作软件设计
利用 TypeScript 有利于互操作软件设计。 典型的结构设计模式。
TypeScript 转换为浏览器 JavaScript 准备互操作。
Blazor C# 编译为准备执行的 Wasm 浏览器。
Blazor C# 界面设计与 TypeScript 对应物保持一致。
Blazor Wasm 通过可互操作的 JavaScript 与浏览器对话。
原文连接:Trog.NET Blazor Typescript Interop
标签:typeSrcipt,Prompt,string,await,js,answer,Blazor,JS,交互 From: https://www.cnblogs.com/King2019Blog/p/16630359.html