在开发vscode插件过程中,有一个典型场景是webview与extension.ts进行通信,例如,webview上的某些信息发送改变时,需要发送消息传递给extension.ts. 如果使用react框架构建vscode插件的webview,如何实现webview与extension.ts之间通信呢?如果要实现信息通信,需要三个步骤:
步骤一:在react的src目录下,定义一个全局变量vscode,代码如下所示:这里定义了Message数据结构,定义了VSCode别名。
interface Message {
command: string;
content: string;
[key: string]: any;
}
type VSCode = {
Uri: any;
env: any;
postMessage(message: Message): void;
getState(): any;
setState(state: any): void;
};
declare const vscode: VSCode;
步骤二:在react的source code下面,在需要发送共享消息的地方,调用vscode.postMessage()发送消息。示例代码如下所示,当input中的信息或者dropdown中的信息发生改变时,就会通过postMessage发送消息出去。
const App = () => {
const [sex, setSex] = useState('');
const handleChange = (event: any) => {
setSex(event.target.value);
vscode.postMessage({ command: "sexChange", content: event.target.value });
};
const handleNameChange = (event: any) => {
const value = event.target.value;
vscode.postMessage({ command: "nameChange", content: value });
}
const handelAgeChange = (event: any) => {
const value = event.target.value;
vscode.postMessage({ command: "ageChange", content: value });
}
return (
<div>
<div>
<input type="text" name="name" onChange={handleNameChange}></input>
</div>
<div>
<input type="text" name="age" onChange={handelAgeChange}></input>
</div>
<div>
<select value={sex} onChange={handleChange} >
<option>girl</option>
<option>boy</option>
</select>
</div>
<div>
<button>ShowIt</button>
</div>
</div>
);
};
const rootElement = document.getElementById('root');
if (rootElement) {
const root = (ReactDOM as any).createRoot(rootElement);
root.render(<App />);
} else {
console.error('Root element not found!');
}
步骤三:在extension.ts代码中,增加监听消息的代码,如果是多个信息需要汇聚,那么可以定义个公共的数据对象,直接更新这个对象即可。调用webview.onDidReceiveMessage监听消息,并将接受到的内容,更新到info这个变量上。
webviewView.webview.onDidReceiveMessage((message: any) => {
if (message.command === "nameChange") {
info.name = message.content
} else if (message.command === "ageChange") {
info.age = message.content
} else if (message.command === "sexChange") {
info.sex = message.content
}
})
export interface Info {
name: string,
age: number,
sex: string
}
export let info: Info = {
name: "",
age: 0,
sex: "boy"
}
修改完上述代码后,就可以进行验证,是否能在extension.ts上接受不了webview上的输入信息了,这里当trigger demo.showOne command的时候,在message上,打印了info这个对象。
context.subscriptions.push(
vscode.commands.registerCommand('demo.showOne', () => {
vscode.window.showInformationMessage('I am showOne.');
vscode.window.showInformationMessage(JSON.stringify(info))
})
);
重新编译后,在extension的webview上输入一些信息,在点击右键菜单的showOne,在右下角的message上成功打印出了消息,说明消息传递成功。
那么,为什么在webview的source code上定义了Message和VSCode对象后,就能调用postMessage发送消息呢?实际上如果要成功发送消息,在extension.ts的webview.html代码中还需要添加上“const vscode = acquireVsCodeApi()”才行。
private getWebviewContent(webviewUri: vscode.Uri): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Taoli Webview</title>
</head>
<body>
<div id="root"></div>
<script>
const vscode = acquireVsCodeApi();
</script>
<script src="${webviewUri}"></script>
</body>
</html>`;
}
}
消息通信工作原理
在 VS Code 扩展中,webview 是一个内嵌的 HTML 环境,类似于一个 iframe。acquireVsCodeApi 函数是 VS Code 提供的一个全局函数,允许 webview 内部的 JavaScript 代码获取一个 VS Code API 对象,从而与扩展主进程进行通信。调用该函数会返回一个包含一组方法的对象,这些方法用于与扩展主进程进行双向通信。主要的方法包括:
postMessage(message: any): void
getState(): any
setState(state: any): void
在步骤一中定义的VScode和Message对象,这些代码是告诉 TypeScript 编译器,在运行时将会有一个全局的 vscode 对象,该对象符合 VSCode 类型。这是为了让 TypeScript 知道 vscode 对象的存在,并进行类型检查和自动补全。实际当webview运行的时候,使用的是acquireVsCodeApi 函数返回的VScode常量。这个常量提供的方法和之前定义的一致。通过这种方式实现了消息通信。
1.发送消息:webview 内部的代码使用 vscode.postMessage 方法发送消息到主进程。
2.接收消息:主进程使用 webview.onDidReceiveMessage 方法接收并处理这些消息。
3.双向通信:如果需要,主进程还可以使用 webview.postMessage 方法向 webview 发送消息,从而实现双向通信。