在为第三方库进行模块扩充时,如何在 VSCode 中为 JavaScript 中的代码库启用 TypeScript 智能感知?
Posted
技术标签:
【中文标题】在为第三方库进行模块扩充时,如何在 VSCode 中为 JavaScript 中的代码库启用 TypeScript 智能感知?【英文标题】:How to enable TypeScript intellisense in VSCode for a codebase in JavaScript when doing module augmentation for a third-party library? 【发布时间】:2021-11-12 11:54:54 【问题描述】:在标准的create-react-app
配置中,有一个第三方组件可以在需要时导入:
import Button from '@mui/material'
// […]
<Button variant="|"></Button>
该库是完全类型化的,因此当光标位于 |
时,在 VSCode 中点击 Ctrl
+Space
将显示我可以使用的可能字符串变体的列表。我可以通过创建一个包装 @mui/material/Button
的本地组件来添加另一个变体:
// LocalComponent.js
import React from 'react'
import Button from '@mui/material'
// Do something to actually add a variant called `new`.
export const LocalComponent = React.forwardRef((props, ref) =>
return (
<Button ref=ref ...props>
props.children
</Button>
)
)
// LocalComponent.d.ts
import type ButtonProps from '@mui/material'
interface Props extends ButtonProps
/** @default 'text' */
variant?: 'text' | 'outlined' | 'contained' | 'new'
export declare function LocalComponent(props: Props): React.FunctionComponent
如果我导入LocalComponent
,我可以获得新变体的智能感知,以及原始Button
的道具。该库还有一个功能,允许我创建新的变体而不必包装库的组件,但是当我使用它时我遇到了智能感知问题——即使实现了new
变体,我只看到原始变体.该库公开了一个专门用于模块扩充的接口,所以我应该这样做:
declare module '@mui/material/Button'
interface ButtonPropsVariantOverrides
new: true;
ButtonPropsVariantOverrides
应该与定义Button
的所有道具的接口的variant
属性合并,本质上将其变成variant?: 'text' | 'outlined' | 'contained' | 'new'
,但似乎TypeScript 没有看到这个模块扩充.我尝试了以下设置:
// jsconfig.json
"compilerOptions":
"baseUrl": "src",
"typeRoots": [
"./src/types", "node_modules/@types"
]
,
"include": ["src"]
不确定typeRoots
是否在jsconfig
中执行任何操作——它没有列为compilerOptions
的可用选项,但由于jsconfig
几乎是tsconfig
,我还是尝试了。
.
├── src/
│ └── types/
│ └── @mui/
│ └── material/
│ └── Button/
│ └── index.d.ts // Contains the module augmentation from above.
└── jsconfig.json
还有这个:
.
├── src/
│ └── types/
│ └── index.d.ts // Contains the module augmentation from above.
└── jsonfig.json
我还确保每次更改某些内容时都重新启动 TS 服务器。一般在哪里放置模块扩充?我如何设置jsconfig.json
有什么问题吗? Here’s a CodeSandbox 我刚刚描述的内容。
感谢您的宝贵时间。
【问题讨论】:
相关github.com/microsoft/TypeScript/issues/30656 我并不精通打字稿,但将其放入LocalComponent.d.ts
似乎可以解决问题。 codesandbox.io/s/xenodochial-dan-sk4wf?file=/src/App.js
【参考方案1】:
似乎当一个带有模块扩充的文件没有import
/export
语句时,你定义了一个ambient module——一个没有实现的形状——你必须在哪里手动导入需要。要扩充模块并使其全局可用,声明文件应包含 import
或 export
语句。
奇怪的是,唯一重要的是声明文件是一个包含至少一个 import
/export
语句的模块,这绝对可以是任何东西——如果我要扩充 @mui/material/Button
,我不必专门import '@mui/material/Button'
,我可以写一些无用的任意导出,比如export default true
,TypeScript 无论如何都会识别该文件。
我在 CodeSandbox 上做了一个快速测试,看看这是否同样适用于 TypeScript 项目——it seemingly does。它可以在没有 tsconfig.json
中的 typeRoots
属性的 CodeSandbox 上运行,但如果出现问题,请添加它。
一般来说,模块扩充应该放在哪里?我设置
jsconfig.json
的方式有什么问题吗?
模块扩充可以在您喜欢的任何文件夹中,但该目录(或其祖先) 应该在jsconfig
的include
属性中,并且如果它的祖先已经添加对于include
,无需单独列出目录类型。尽管没有必要,但您可能希望将扩展包分隔到包含 index.d.ts
的文件夹中——不要以不同的方式命名声明文件,否则 TypeScript 将忽略它们。以我的问题为例,一种选择是将模块扩充放入./src/types/@mui/Button/index.d.ts
。最后,typeRoots
属性在添加到 jsconfig.json
时确实没有任何作用,因此没有必要使用它。
【讨论】:
在顶部添加导入有助于:从“@mui/material/Button”导入按钮。谢谢【参考方案2】:如果你使用tsconfig.json
会更好
如果你使用 tsconfig 和 JSDoc example
你的代码可能是这样的
我们需要关注 tsconfig.json 的一些属性。并且必须重要的是baseUrl
它用于
解析非绝对模块名称的基本目录。
所以以快递为例
import express from "express";
使用 baseUrl 我们告诉 typescript 在哪里寻找它们 express types tsc 将像这样在导入路径前添加前缀
import express from "./node_modules/express";
每个人说...我选择 express 是有原因的,因为经常有一些包不包含它们的类型,但是有一个庞大的社区可以为我们完成它们,要使用它们,我们需要单独导入它们
$ npm i -D @types/express
有时您需要覆盖它们的类型,因此您需要让 tsc 在哪里查找它们,并使用 paths
覆盖以正确执行此操作,您需要在根目录下创建一个目录您的项目名称类型
project
--node_modules
--src
--types
--tsconfig.json
需要提到的重要一点是,在您的类型目录中,您需要创建一个与 import 语句中使用的标识符相同的目录,以保持我们的示例看起来像这样
project
--node_modules
--src
--types
----express
------index.d.ts
--tsconfig.json
其正确的文件需要调用index.d.ts
让我们在 express userIp
的请求方法中添加一个新属性,新属性将引用客户端 IP 地址,因此它需要是一个字符串,并且它需要在 req 属性中
所以让我们覆盖它
/**
* Creates an Express application. The express() function is a top-level function exported by the express module.
*/
declare function e (): core.Express;
declare namespace e
interface Request<P = EnetoParams, ResBody = any, ReqBody = any, ReqQuery = core.Query> extends core.Request<P, ResBody, ReqBody, ReqQuery>
/** ip del usuario que solicito la peticion */
userIp: string;
export = e;
现在告诉 tsc 在哪里可以找到它。 paths
用于它
"compilerOptions":
"baseUrl":"./node_modules",
"paths":
"express":["./@types/express/index.d.ts","../types/express/index.d.ts"]
格式是
"paths":
"name used in the import statement":[
"path to the original definitions",
"path to the override",
"if any other exists"
]
如果你不想为所有这些变化和东西做这件事,你可以使用通配符
"paths":
"*": ["./*/index.d.ts", "./*/types.d.ts", "./*/types", "./@types/index.d.ts","./@types/types.d.ts"]
我们以./
开始它们的路径,因为我们的baseUrl 是./node_modules
现在告诉tsc在哪里可以找到我们自己的类型或定义
// baseUrl is ./node_modues thats why we need to get out of that dir and get inside types dir
"typeRoots": ["../types/index.d.ts"]
index.d.ts
declare module "my-package-name"
interface SomeObj
prop1: string;
prop2: string;
在你的代码中使用它的方式
import SomeObj from "my-package-name";
const myVar: SomeObj = ;
【讨论】:
以上是关于在为第三方库进行模块扩充时,如何在 VSCode 中为 JavaScript 中的代码库启用 TypeScript 智能感知?的主要内容,如果未能解决你的问题,请参考以下文章