JavaScript [JSImport]/[JSExport] interop trong .NET WebAssembly
Nguồn: [JavaScript [JSImport]/[JSExport] interop in .NET WebAssembly](https://learn.microsoft.com/en-us/aspnet/core/client-side/dotnet-interop/?view=aspnetcore-8.0)
Tác giả: Aaron Shumaker
Bài viết này giải thích cách tương tác với JavaScript (JS) trong client-side WebAssembly sử dụng JS [JSImport]/[JSExport] interop (API System.Runtime.InteropServices.JavaScript).
[JSImport]/[JSExport] interop áp dụng khi chạy module .NET WebAssembly trong một JS host trong các tình huống sau:
- JavaScript
[JSImport]/[JSExport]interop với dự án WebAssembly Browser App. - JavaScript JSImport/JSExport interop với ASP.NET Core Blazor.
- Các nền tảng .NET WebAssembly khác hỗ trợ
[JSImport]/[JSExport]interop.
Điều kiện tiên quyết
Bất kỳ loại dự án nào sau đây:
- Dự án WebAssembly Browser App được tạo theo hướng dẫn JavaScript
[JSImport]/[JSExport]interop với dự án WebAssembly Browser App. - Dự án Blazor client-side được tạo theo hướng dẫn JavaScript JSImport/JSExport interop với ASP.NET Core Blazor.
- Dự án được tạo cho nền tảng thương mại hoặc mã nguồn mở hỗ trợ
[JSImport]/[JSExport]interop.
Ứng dụng mẫu
Xem hoặc tải xuống code mẫu: Chọn thư mục phiên bản 8.0 hoặc mới hơn phù hợp với phiên bản .NET bạn đang sử dụng. Trong thư mục phiên bản, truy cập mẫu có tên WASMBrowserAppImportExportInterop.
JS interop sử dụng thuộc tính [JSImport]/[JSExport]
Thuộc tính [JSImport] (nhập) được áp dụng cho một phương thức .NET để chỉ ra rằng phương thức JS tương ứng phải được gọi khi phương thức .NET được gọi. Điều này cho phép các developer .NET định nghĩa các "import" (nhập khẩu) giúp code .NET có thể gọi vào JS. Ngoài ra, một Action có thể được truyền dưới dạng tham số, và JS có thể gọi action để hỗ trợ mô hình callback (hàm gọi lại) hoặc đăng ký sự kiện.
Thuộc tính [JSExport] (xuất) được áp dụng cho một phương thức .NET để hiển thị nó cho code JS. Điều này cho phép code JS khởi tạo các lời gọi đến phương thức .NET.
Nhập phương thức JS
Ví dụ sau nhập một phương thức JS tiêu chuẩn tích hợp sẵn (console.log) vào C#. [JSImport] bị giới hạn chỉ nhập các phương thức của đối tượng có thể truy cập toàn cục. Ví dụ, log là phương thức được định nghĩa trên đối tượng console, được định nghĩa trên đối tượng có thể truy cập toàn cục globalThis. Phương thức console.log được ánh xạ đến phương thức proxy C# ConsoleLog, nhận một chuỗi cho thông điệp log:
public partial class GlobalInterop
{
[JSImport("globalThis.console.log")]
public static partial void ConsoleLog(string text);
}Trong Program.Main, ConsoleLog được gọi với thông điệp cần log:
GlobalInterop.ConsoleLog("Hello World!");Kết quả xuất hiện trong console của trình duyệt.
Ví dụ sau minh họa việc nhập một phương thức được khai báo trong JS.
Phương thức JS tùy chỉnh sau (globalThis.callAlert) mở một hộp thoại alert (window.alert) với thông điệp được truyền vào text:
globalThis.callAlert = function (text) {
globalThis.window.alert(text);
}Phương thức globalThis.callAlert được ánh xạ đến phương thức proxy C# (CallAlert), nhận một chuỗi cho thông điệp:
using System.Runtime.InteropServices.JavaScript;
public partial class GlobalInterop
{
[JSImport("globalThis.callAlert")]
public static partial void CallAlert(string text);
}Trong Program.Main, CallAlert được gọi, truyền văn bản cho thông điệp hộp thoại alert:
GlobalInterop.CallAlert("Hello World");Lớp C# khai báo phương thức [JSImport] không có phần triển khai. Tại thời điểm biên dịch, một partial class (lớp bộ phận) được tạo bởi source generator chứa code .NET triển khai việc marshalling của lời gọi và các kiểu để gọi phương thức JS tương ứng.
Trong ví dụ trước, khai báo JS trung gian globalThis.callAlert được dùng để bọc code JS hiện có. Bài viết này gọi không chính thức khai báo JS trung gian là JS shim (lớp đệm JS). JS shim lấp đầy khoảng cách giữa triển khai .NET và các khả năng/thư viện JS hiện có. Trong nhiều trường hợp như ví dụ đơn giản trên, JS shim là không cần thiết và các phương thức có thể được nhập trực tiếp. JS shim có thể:
- Đóng gói logic bổ sung.
- Ánh xạ thủ công các kiểu.
- Giảm số lượng đối tượng hoặc lời gọi vượt qua ranh giới interop.
- Ánh xạ thủ công các lời gọi tĩnh đến các phương thức thực thể (instance methods).
Tải khai báo JavaScript
Các khai báo JS dự định được nhập với [JSImport] thường được tải trong ngữ cảnh của cùng trang hoặc JS host đã tải .NET WebAssembly. Điều này có thể được thực hiện với:
- Một khối
<script>...</script>khai báo JS nội tuyến. - Khai báo script source (
src) (<script src="./some.js"></script>) tải file JS bên ngoài. - Một module ES6 JS (
<script type='module' src="./moduleName.js"></script>). - Một module ES6 JS được tải sử dụng JSHost.ImportAsync từ .NET WebAssembly.
Các ví dụ trong bài viết này sử dụng JSHost.ImportAsync. Khi gọi ImportAsync, .NET WebAssembly phía client yêu cầu file sử dụng tham số moduleUrl, do đó nó kỳ vọng file có thể truy cập như một static web asset (tài nguyên web tĩnh). Ví dụ, code C# sau trong dự án WebAssembly Browser App duy trì file JS tại đường dẫn /wwwroot/scripts/ExampleShim.js:
await JSHost.ImportAsync("ExampleShim", "/scripts/ExampleShim.js");Quan trọng: Nếu JS được tải từ một JavaScript module, thì các thuộc tính [JSImport] phải bao gồm tên module làm tham số thứ hai. Ví dụ: [JSImport("globalThis.callAlert", "ExampleShim")] chỉ ra phương thức được nhập được khai báo trong module JavaScript có tên "ExampleShim."
Ánh xạ kiểu (Type mappings)
Các tham số và kiểu trả về trong chữ ký phương thức .NET tự động được chuyển đổi sang hoặc từ các kiểu JS phù hợp tại thời điểm runtime (chạy) nếu có ánh xạ duy nhất được hỗ trợ. Quá trình này được gọi là type marshalling (chuyển đổi kiểu). Sử dụng JSMarshalAsAttribute\<T\> để kiểm soát cách các tham số và kiểu trả về của phương thức được nhập được marshalled.
Một số kiểu không có ánh xạ mặc định. Ví dụ, long có thể được marshalled như System.Runtime.InteropServices.JavaScript.JSType.Number hoặc System.Runtime.InteropServices.JavaScript.JSType.BigInt, vì vậy JSMarshalAsAttribute<T> là bắt buộc.
Các tình huống ánh xạ kiểu sau được hỗ trợ:
- Truyền
ActionhoặcFunc<TResult>dưới dạng tham số, được marshalled như các phương thức JS có thể gọi. Điều này cho phép code .NET gọi các listener (bộ lắng nghe) phản hồi các callback hoặc sự kiện JS. - Truyền các tham chiếu JS và tham chiếu đối tượng managed .NET theo cả hai hướng, được marshalled như các đối tượng proxy và giữ sống qua ranh giới interop cho đến khi proxy được garbage collected (thu gom rác).
- Marshalling các phương thức JS bất đồng bộ hoặc một JS
Promisevới kết quảTask, và ngược lại.
Hầu hết các kiểu được marshalled hoạt động theo cả hai hướng, dưới dạng tham số và giá trị trả về, trên cả phương thức nhập và xuất.
Bảng sau chỉ ra các ánh xạ kiểu được hỗ trợ:
| .NET | JavaScript | Nullable | Task→Promise | JSMarshalAs tùy chọn | Array of |
|---|---|---|---|---|---|
Boolean | Boolean | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
Byte | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ |
Char | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
Int16 | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
Int32 | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ |
Int64 | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Int64 | BigInt | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Single | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
Double | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ |
IntPtr | Number | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
DateTime | Date | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ | Không hỗ trợ |
DateTimeOffset | Date | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Exception | Error | Không hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
JSObject | Object | Không hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ |
String | String | Không hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ | ✅ Hỗ trợ |
Object | Any | Không hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ | ✅ Hỗ trợ |
Span<Byte> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Span<Int32> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Span<Double> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
ArraySegment<Byte> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
ArraySegment<Int32> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
ArraySegment<Double> | MemoryView | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Task | Promise | Không hỗ trợ | Không hỗ trợ | ✅ Hỗ trợ | Không hỗ trợ |
Action | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Action<T1> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Action<T1, T2> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Action<T1, T2, T3> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Func<TResult> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Func<T1, TResult> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Func<T1, T2, TResult> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Func<T1, T2, T3, TResult> | Function | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ | Không hỗ trợ |
Các điều kiện sau áp dụng cho ánh xạ kiểu và các giá trị được marshalled:
- Cột
Array ofchỉ ra liệu kiểu .NET có thể được marshalled như một JSArraykhông. Ví dụ: C#int[](Int32) ánh xạ đến JSArraycủaNumber. - Khi truyền một giá trị JS cho C# với giá trị sai kiểu, framework ném ra một ngoại lệ trong hầu hết các trường hợp. Framework không thực hiện kiểm tra kiểu tại thời điểm biên dịch trong JS.
JSObject,Exception,TaskvàArraySegmenttạoGCHandlevà một proxy. Bạn có thể kích hoạt disposal (giải phóng bộ nhớ) trong code developer hoặc cho phép .NET garbage collection (GC) xử lý các đối tượng sau này. Các kiểu này có overhead (chi phí) hiệu suất đáng kể.Array: Marshalling một array tạo ra một bản sao của array trong JS hoặc .NET.MemoryView:MemoryViewlà một lớp JS dành cho runtime .NET WebAssembly để marshallSpanvàArraySegment.- Không giống marshalling một array, marshalling một
SpanhoặcArraySegmentkhông tạo ra bản sao của bộ nhớ bên dưới. MemoryViewchỉ có thể được khởi tạo đúng cách bởi runtime .NET WebAssembly.MemoryViewđược tạo choSpanchỉ có hiệu lực trong thời gian của lời gọi interop.MemoryViewđược tạo choArraySegmenttồn tại sau lời gọi interop và hữu ích cho việc chia sẻ buffer. Gọidispose()trênMemoryViewđược tạo choArraySegmentsẽ xử lý proxy và bỏ ghim mảng .NET bên dưới.
Kiểu nguyên thủy JS
Ví dụ sau minh họa [JSImport] tận dụng ánh xạ kiểu của nhiều kiểu nguyên thủy JS và sử dụng JSMarshalAs, nơi cần ánh xạ tường minh tại thời điểm biên dịch.
PrimitivesShim.js:
globalThis.counter = 0;
// Không có tham số và không trả về gì.
export function incrementCounter() {
globalThis.counter += 1;
};
// Trả về một int.
export function getCounter() { return globalThis.counter; };
// Nhận một tham số và không trả về gì. JS không hạn chế kiểu tham số,
// nhưng chúng ta có thể hạn chế nó trong proxy .NET nếu muốn.
export function logValue(value) { console.log(value); };
// Được gọi cho các kiểu .NET khác nhau để minh họa ánh xạ đến kiểu nguyên thủy JS.
export function logValueAndType(value) { console.log(typeof value, value); };PrimitivesInterop.cs:
using System;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
public partial class PrimitivesInterop
{
// Nhập một phương thức JS hiện có.
[JSImport("globalThis.console.log")]
public static partial void ConsoleLog([JSMarshalAs<JSType.Any>] object value);
// Nhập các phương thức tĩnh từ một module JS.
[JSImport("incrementCounter", "PrimitivesShim")]
public static partial void IncrementCounter();
[JSImport("getCounter", "PrimitivesShim")]
public static partial int GetCounter();
// Tên phương thức JS shim không cần trùng với tên phương thức C#.
[JSImport("logValue", "PrimitivesShim")]
public static partial void LogInt(int value);
// Một ánh xạ thứ hai đến cùng phương thức JS với kiểu tương thích.
[JSImport("logValue", "PrimitivesShim")]
public static partial void LogString(string value);
[JSImport("logValueAndType", "PrimitivesShim")]
public static partial void LogValueAndType(
[JSMarshalAs<JSType.Any>] object value);
// Một số kiểu có nhiều ánh xạ và yêu cầu marshalling tường minh.
// long/Int64 có thể được ánh xạ như Number hoặc BigInt.
[JSImport("logValueAndType", "PrimitivesShim")]
public static partial void LogValueAndTypeForNumber(
[JSMarshalAs<JSType.Number>] long value);
[JSImport("logValueAndType", "PrimitivesShim")]
public static partial void LogValueAndTypeForBigInt(
[JSMarshalAs<JSType.BigInt>] long value);
}
public static class PrimitivesUsage
{
public static async Task Run()
{
await JSHost.ImportAsync("PrimitivesShim", "/PrimitivesShim.js");
PrimitivesInterop.ConsoleLog("Printed from JSImport of console.log()");
PrimitivesInterop.IncrementCounter();
int counterValue = PrimitivesInterop.GetCounter();
PrimitivesInterop.LogInt(counterValue);
PrimitivesInterop.LogString("I'm a string from .NET in your browser!");
PrimitivesInterop.LogValueAndType(true);
PrimitivesInterop.LogValueAndType(0x3A); // Byte literal
PrimitivesInterop.LogValueAndType('C');
PrimitivesInterop.LogValueAndType((Int16)12);
PrimitivesInterop.LogValueAndTypeForNumber(9007199254740990L);
PrimitivesInterop.LogValueAndTypeForBigInt(1234567890123456789L);
PrimitivesInterop.LogValueAndType(3.14f);
PrimitivesInterop.LogValueAndType(3.14d);
PrimitivesInterop.LogValueAndType("A string");
}
}Đối tượng JS Date
Ví dụ trong phần này minh họa việc nhập các phương thức có đối tượng JS Date làm kết quả trả về hoặc tham số. Ngày tháng được marshalled qua interop theo giá trị, nghĩa là chúng được sao chép tương tự như kiểu nguyên thủy JS.
DateShim.js:
export function incrementDay(date) {
date.setDate(date.getDate() + 1);
return date;
}
export function logValueAndType(value) {
console.log("Date:", value)
}DateInterop.cs:
using System;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
public partial class DateInterop
{
[JSImport("incrementDay", "DateShim")]
[return: JSMarshalAs<JSType.Date>]
public static partial DateTime IncrementDay(
[JSMarshalAs<JSType.Date>] DateTime date);
[JSImport("logValueAndType", "DateShim")]
public static partial void LogValueAndType(
[JSMarshalAs<JSType.Date>] DateTime value);
}Tham chiếu đối tượng JS
Bất cứ khi nào một phương thức JS trả về một tham chiếu đối tượng, nó được biểu diễn trong .NET như một JSObject. Đối tượng JS gốc tiếp tục tồn tại trong ranh giới JS, trong khi code .NET có thể truy cập và sửa đổi nó bởi tham chiếu thông qua JSObject. JSObject cung cấp các phương thức để truy cập thuộc tính, nhưng không cung cấp quyền truy cập trực tiếp vào các phương thức thực thể.
JSObjectShim.js:
export function createObject() {
return {
name: "Example JS Object",
answer: 41,
question: null,
summarize: function () {
return `Question: "${this.question}" Answer: ${this.answer}`;
}
};
}
export function incrementAnswer(object) {
object.answer += 1;
}
export function summarize(object) {
return object.summarize();
}JSObjectInterop.cs:
using System;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
public partial class JSObjectInterop
{
[JSImport("createObject", "JSObjectShim")]
public static partial JSObject CreateObject();
[JSImport("incrementAnswer", "JSObjectShim")]
public static partial void IncrementAnswer(JSObject jsObject);
[JSImport("summarize", "JSObjectShim")]
public static partial string Summarize(JSObject jsObject);
[JSImport("globalThis.console.log")]
public static partial void ConsoleLog([JSMarshalAs<JSType.Any>] object value);
}Interop bất đồng bộ
Nhiều API JS là bất đồng bộ và báo hiệu hoàn thành thông qua callback, Promise, hoặc phương thức async. Các phương thức JS sử dụng từ khóa async hoặc trả về một Promise có thể được await trong C# bởi một phương thức trả về Task.
PromisesShim.js:
export function wait2Seconds() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 2000);
});
}
export function waitGetString() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("String From Resolve");
}, 500);
});
}
export function waitGetDate() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new Date('1988-11-24'));
}, 500);
});
}
export function fetchCurrentUrl() {
return fetch(globalThis.window.location, { method: 'GET' })
.then(response => response.text());
}
export async function asyncFunction() {
await wait2Seconds();
}
export function conditionalSuccess(shouldSucceed) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed)
resolve();
else
reject("Reject: ShouldSucceed == false");
}, 500);
});
}PromisesInterop.cs:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
public partial class PromisesInterop
{
[JSImport("wait2Seconds", "PromisesShim")]
public static partial Task Wait2Seconds();
[JSImport("waitGetString", "PromisesShim")]
public static partial Task<string> WaitGetString();
[JSImport("waitGetDate", "PromisesShim")]
[return: JSMarshalAs<JSType.Promise<JSType.Date>>()]
public static partial Task<DateTime> WaitGetDate();
[JSImport("fetchCurrentUrl", "PromisesShim")]
public static partial Task<string> FetchCurrentUrl();
[JSImport("asyncFunction", "PromisesShim")]
public static partial Task AsyncFunction();
[JSImport("conditionalSuccess", "PromisesShim")]
public static partial Task ConditionalSuccess(bool shouldSucceed);
}Giới hạn ánh xạ kiểu
Một số ánh xạ kiểu yêu cầu các kiểu generic (kiểu tổng quát) lồng nhau trong định nghĩa JSMarshalAs hiện không được hỗ trợ. Ví dụ, trả về một Promise cho một array như [return: JSMarshalAs<JSType.Promise<JSType.Array<JSType.Number>>>()] tạo ra lỗi biên dịch. Một cách giải quyết phù hợp tùy theo tình huống, nhưng một tùy chọn là biểu diễn array như một tham chiếu JSObject.
Cân nhắc về hiệu suất
Marshalling của lời gọi và overhead của việc theo dõi đối tượng qua ranh giới interop đắt hơn các thao tác .NET thuần túy nhưng vẫn nên cho thấy hiệu suất chấp nhận được cho ứng dụng web điển hình với nhu cầu vừa phải.
Proxy đối tượng như JSObject duy trì tham chiếu qua ranh giới interop có overhead bộ nhớ bổ sung và ảnh hưởng đến cách garbage collection ảnh hưởng đến các đối tượng này. Trong các trường hợp như vậy, chúng tôi khuyến nghị theo dõi các mô hình disposal xác định với các phạm vi using tận dụng interface IDisposable trên các đối tượng JS.
Đăng ký sự kiện JS
Code .NET có thể đăng ký các sự kiện JS và xử lý chúng bằng cách truyền một C# Action đến một hàm JS để hoạt động như một handler (bộ xử lý). Code JS shim xử lý việc đăng ký sự kiện.
Cảnh báo: Tương tác với các thuộc tính riêng lẻ của DOM qua JS interop tương đối chậm và có thể dẫn đến việc tạo ra nhiều proxy gây áp lực garbage collection cao. Mô hình sau không thường được khuyến nghị. Chỉ sử dụng mô hình sau cho không quá một vài phần tử.
EventsShim.js:
export function subscribeEventById(elementId, eventName, listenerFunc) {
const elementObj = document.getElementById(elementId);
let handler = function (event) {
listenerFunc(event.type, event.target.id);
}.bind(elementObj);
elementObj.addEventListener(eventName, handler, false);
return handler;
}
export function unsubscribeEventById(elementId, eventName, listenerHandler) {
const elementObj = document.getElementById(elementId);
elementObj.removeEventListener(eventName, listenerHandler, false);
}
export function triggerClick(elementId) {
const elementObj = document.getElementById(elementId);
elementObj.click();
}EventsInterop.cs:
using System;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
public partial class EventsInterop
{
[JSImport("subscribeEventById", "EventsShim")]
public static partial JSObject SubscribeEventById(string elementId,
string eventName,
[JSMarshalAs<JSType.Function<JSType.String, JSType.String>>]
Action<string, string> listenerFunc);
[JSImport("unsubscribeEventById", "EventsShim")]
public static partial void UnsubscribeEventById(string elementId,
string eventName, JSObject listenerHandler);
[JSImport("triggerClick", "EventsShim")]
public static partial void TriggerClick(string elementId);
}Các tình huống JS [JSImport]/[JSExport] interop
Các bài viết sau tập trung vào việc chạy module .NET WebAssembly trong JS host như trình duyệt: