在为第三方库进行模块扩充时,如何在 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——一个没有实现的形状——你必须在哪里手动导入需要。要扩充模块并使其全局可用,声明文件应包含 importexport 语句。

奇怪的是,唯一重要的是声明文件是一个包含至少一个 import/export 语句的模块,这绝对可以是任何东西——如果我要扩充 @mui/material/Button,我不必专门import '@mui/material/Button',我可以写一些无用的任意导出,比如export default true,TypeScript 无论如何都会识别该文件。

我在 CodeSandbox 上做了一个快速测试,看看这是否同样适用于 TypeScript 项目——it seemingly does。它可以在没有 tsconfig.json 中的 typeRoots 属性的 CodeSandbox 上运行,但如果出现问题,请添加它。

一般来说,模块扩充应该放在哪里?我设置jsconfig.json的方式有什么问题吗?

模块扩充可以在您喜欢的任何文件夹中,但该目录(或其祖先) 应该在jsconfiginclude 属性中,并且如果它的祖先已经添加对于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 智能感知?的主要内容,如果未能解决你的问题,请参考以下文章

36《Vue 入门教程》Vue 第三方库的使用

36《Vue 入门教程》Vue 第三方库的使用

在 VScode 和 Android Studio 中使用 Gradle 进行颤振时出错

wpf怎么使用第三方库

angular2怎么使用第三方的库

在vscode中安装python第三方库