Mở rộng đối tượng Yêu cầu Nhanh bằng cách sử dụng Bản in


125

Tôi đang cố gắng thêm một thuộc tính để thể hiện đối tượng yêu cầu từ phần mềm trung gian bằng cách sử dụng bản in. Tuy nhiên tôi không thể tìm ra cách thêm thuộc tính cho đối tượng. Tôi không muốn sử dụng ký hiệu ngoặc nếu có thể.

Tôi đang tìm kiếm một giải pháp cho phép tôi viết một cái gì đó tương tự như thế này (nếu có thể):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});

bạn sẽ có thể mở rộng giao diện yêu cầu mà tệp express.d.ts cung cấp với các trường bạn muốn.
toskv

Câu trả lời:


142

Bạn muốn tạo một định nghĩa tùy chỉnh và sử dụng một tính năng trong Bản mô tả có tên là Khai báo hợp nhất . Điều này thường được sử dụng, ví dụ như trong method-override.

Tạo một tập tin custom.d.tsvà chắc chắn bao gồm nó trong của bạn tsconfig.json's files-section nếu có. Các nội dung có thể trông như sau:

declare namespace Express {
   export interface Request {
      tenant?: string
   }
}

Điều này sẽ cho phép bạn, tại bất kỳ điểm nào trong mã của bạn, sử dụng một cái gì đó như thế này:

router.use((req, res, next) => {
    req.tenant = 'tenant-X'
    next()
})

router.get('/whichTenant', (req, res) => {
    res.status(200).send('This is your tenant: '+req.tenant)
})

2
Tôi mới làm điều này, nhưng tôi đã làm cho nó hoạt động mà không cần thêm tệp custom.d.ts của mình vào phần tệp trong tsconfig.json, nhưng nó vẫn hoạt động. Đây có phải là hành vi dự kiến?
Chaim Friedman

1
@ChaimFriedman Có. Phần filesnày giới hạn tập hợp các tệp được bao gồm bởi TypeScript. Nếu bạn không chỉ định fileshoặc include, thì tất cả *.d.tsđược bao gồm theo mặc định, do đó không cần thêm các kiểu tùy chỉnh của bạn ở đó.
interphx

9
Không hoạt động đối với tôi: Tôi nhận được Property 'tenantkhông tồn tại trên loại 'Yêu cầu' `Không tạo ra sự khác biệt nếu tôi đưa nó vào tsconfig.jsonhoặc không rõ ràng . CẬP NHẬT Với việc declare global@basarat chỉ ra trong phần trả lời của anh ta hoạt động, nhưng tôi phải làm import {Request} from 'express'trước.
Sư tử

5
FWIW, câu trả lời này đã lỗi thời . Câu trả lời của JCM là cách chính xác để tăng Requestđối tượng trong expressjs (ít nhất là 4.x)
Eric Liprandi

3
Đối với các tìm kiếm trong tương lai - một ví dụ hay mà tôi thấy đã hoạt động tốt: github.com/3mard/ts-node-example
jd291

79

Theo đề xuất của các bình luận trongindex.d.ts , bạn chỉ cần khai báo với Expresskhông gian tên toàn cầu bất kỳ thành viên mới nào. Thí dụ:

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

Ví dụ đầy đủ:

import * as express from 'express';

export class Context {
  constructor(public someContextVariable) {
  }

  log(message: string) {
    console.log(this.someContextVariable, { message });
  }
}

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

const app = express();

app.use((req, res, next) => {
  req.context = new Context(req.url);
  next();
});

app.use((req, res, next) => {
  req.context.log('about to return')
  res.send('hello world world');
});

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Mở rộng không gian tên toàn cầu được đề cập nhiều hơn tại GitBook của tôi .


Tại sao toàn cầu là cần thiết trong tuyên bố? Điều gì xảy ra nếu nó không ở đó?
Jason Kuhrt

Điều này hoạt động với các giao diện, nhưng trong trường hợp bất kỳ ai cần hợp nhất các loại, lưu ý rằng các loại là "đóng" và không thể được hợp nhất: github.com/Microsoft/TypeScript/issues/ Kẻ
Peter W

Ông @basarat Tôi nợ bạn một số loại bia.
marcellsimon

Tôi cũng đã phải thêm vào tsconfig.json: {"Trình biên dịch": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon

Không có giải pháp nào ở trên có hiệu quả .. nhưng cái này đã thực hiện công việc trong lần chạy đầu tiên .. cảm ơn rất nhiều .. !!
Ritesh

55

Đối với các phiên bản mới hơn của express, bạn cần tăng express-serve-static-coremô-đun.

Điều này là cần thiết bởi vì bây giờ đối tượng Express xuất phát từ đó: https://github.com/DefiniteTyped/DefiniteTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts

Về cơ bản, sử dụng như sau:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}

1
Điều này làm việc cho tôi, trong khi mở rộng 'express'mô-đun cũ đơn giản thì không. Cảm ơn bạn!
Ben Kreeger

4
Đã phải vật lộn với điều này, để làm cho nó hoạt động, tôi cũng phải nhập mô-đun:import {Express} from "express-serve-static-core";
andre_b

1
@andre_b Cảm ơn bạn đã gợi ý. Tôi nghĩ rằng câu lệnh nhập biến tệp thành một mô-đun và đây là phần cần thiết. Tôi đã chuyển sang sử dụng export {}cũng hoạt động.
Danyal Aytekin

2
Hãy chắc chắn rằng tệp mà mã này đi vào không được gọi express.d.ts, nếu không trình biên dịch sẽ cố gắng hợp nhất mã này vào các kiểu chữ nhanh, dẫn đến lỗi.
Tom Spencer

3
Hãy chắc chắn rằng các kiểu của bạn phải là đầu tiên trong typeRoots! loại / express / index.d.ts và tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya

30

Câu trả lời được chấp nhận (như những người khác) không hiệu quả với tôi nhưng

declare module 'express' {
    interface Request {
        myProperty: string;
    }
}

đã làm. Hy vọng rằng sẽ giúp được ai đó.


2
Phương pháp tương tự được mô tả trong tài liệu ts trong phần "Tăng cường mô-đun". Thật tuyệt nếu bạn không muốn sử dụng *.d.tscác tệp và chỉ lưu trữ các loại của mình trong các *.tstệp thông thường .
im.pankratov

3
đây là điều duy nhất làm việc cho tôi, tất cả các câu trả lời khác dường như cần phải có trong các tệp .d.ts
nghị viện

Điều này cũng tốt cho tôi, với điều kiện tôi đặt custom-declarations.d.tstệp của mình vào thư mục gốc của dự án TypeScript.
trung vào

Tôi mở rộng loại ban đầu để bảo tồn nó: import { Request as IRequest } from 'express/index';interface Request extends IRequest. Cũng phải thêm loạiRoot
Ben Creasy

17

Sau khi thử 8 câu trả lời hoặc không thành công. Cuối cùng tôi quản lý để làm cho nó làm việc với jd291 's comment trỏ đến 3mards repo .

Tạo một tập tin trong cơ sở được gọi là types/express/index.d.ts. Và trong đó viết:

declare namespace Express {
    interface Request {
        yourProperty: <YourType>;
    }
}

và bao gồm nó trong tsconfig.json:

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}

Sau đó yourPropertynên có thể truy cập theo mọi yêu cầu:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});

14

Không có giải pháp được cung cấp làm việc cho tôi. Tôi đã kết thúc đơn giản là mở rộng giao diện Yêu cầu:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Sau đó, để sử dụng nó:

import {NextFunction, Response} from 'express';
import {RequestCustom} from 'RequestCustom';

someMiddleware(req: RequestCustom, res: Response, next: NextFunction): void
{
    req.property = '';
}

Chỉnh sửa : Các phiên bản gần đây của TypeScript phàn nàn về điều này. Thay vào đó, tôi phải làm:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}

1
điều đó sẽ hoạt động, nhưng khá dài dòng nếu bạn có 100 chức năng phần mềm trung gian, amirite
Alexander Mills

1
@ user2473015 Vâng, các phiên bản gần đây của Typecript đã phá vỡ điều này. Xem câu trả lời cập nhật của tôi.
Tom Mettam

8

Trong TypeScript, giao diện được mở kết thúc. Điều đó có nghĩa là bạn có thể thêm thuộc tính cho chúng từ bất cứ đâu chỉ bằng cách xác định lại chúng.

Xem xét rằng bạn đang sử dụng tệp express.d.ts này , bạn sẽ có thể xác định lại giao diện Yêu cầu để thêm trường bổ sung.

interface Request {
  property: string;
}

Sau đó, trong chức năng phần mềm trung gian của bạn, tham số req cũng nên có thuộc tính này. Bạn sẽ có thể sử dụng nó mà không có bất kỳ thay đổi nào đối với mã của bạn.


1
Làm thế nào để bạn "chia sẻ" thông tin đó trong suốt mã của bạn? Nếu tôi xác định một thuộc tính trong Yêu cầu, hãy nói Request.user = {};bằng app.tscách nào userController.tsbiết về nó?
Nepoxx

2
@Nepoxx nếu bạn xác định lại giao diện, trình biên dịch sẽ hợp nhất các thuộc tính và làm cho chúng hiển thị ở mọi nơi, đó là lý do. Lý tưởng nhất là bạn thực hiện xác định lại trong tệp .d.ts. :)
toskv

Điều đó dường như hoạt động, tuy nhiên nếu tôi sử dụng loại express.Handler(thay vì chỉ định thủ công (req: express.Request, res: express.Response, next: express.NextFunction) => any)), nó dường như không đề cập đến giống Requestnhư nó phàn nàn rằng tài sản của tôi không tồn tại.
Nepoxx

Tôi sẽ không mong đợi điều đó, trừ khi express.Handler mở rộng giao diện Yêu cầu. Phải không?
toskv

2
Tôi có thể làm cho công việc đó nếu tôi sử dụng declare module "express"nhưng không sử dụng declare namespace Express. Tôi muốn sử dụng cú pháp không gian tên nhưng nó không hoạt động với tôi.
WillyC

5

Mặc dù đây là một câu hỏi rất cũ, tôi đã vấp phải vấn đề này gần đây. Câu trả lời được chấp nhận hoạt động tốt nhưng tôi cần thêm giao diện tùy chỉnh vào Request- một giao diện tôi đã sử dụng trong mã của mình và nó không hoạt động tốt với chấp nhận câu trả lời. Theo logic, tôi đã thử điều này:

import ITenant from "../interfaces/ITenant";

declare namespace Express {
    export interface Request {
        tenant?: ITenant;
    }
}

Nhưng điều đó không hiệu quả vì Typecript coi .d.tscác tệp là nhập toàn cầu và khi chúng có nhập trong đó, chúng được coi là các mô-đun bình thường. Đó là lý do tại sao đoạn mã trên không hoạt động trên cài đặt bản thảo tiêu chuẩn.

Đây là những gì tôi đã làm

// typings/common.d.ts

declare namespace Express {
    export interface Request {
        tenant?: import("../interfaces/ITenant").default;
    }
}
// interfaces/ITenant.ts

export interface ITenant {
    ...
}

Điều này hoạt động cho tệp chính của tôi, nhưng không phải trong các tệp định tuyến hoặc bộ điều khiển của tôi, tôi không nhận được thông tin nào, nhưng khi tôi cố gắng biên dịch thì thông báo "Thuộc tính 'người dùng' không tồn tại trên loại 'Yêu cầu'." (Tôi đang sử dụng người dùng thay vì người thuê nhà), nhưng nếu tôi thêm // @ ts-bỏ qua phía trên họ, thì nó vẫn hoạt động (mặc dù đó là cách ngớ ngẩn để sửa nó tất nhiên. Bạn có suy nghĩ gì về lý do tại sao nó không thể làm việc cho các tập tin khác của tôi?
Logan

Đó là một điều rất lạ @Logan. Bạn có thể chia sẻ của bạn .d.ts, tsconfig.jsonvà ví dụ sử dụng? Ngoài ra, phiên bản bản thảo nào bạn đang sử dụng vì việc nhập này trong các mô-đun toàn cầu chỉ được hỗ trợ bắt đầu từ TS 2.9? Điều đó có thể giúp tốt hơn.
16kb

Tôi đã tải lên dữ liệu ở đây, pastebin.com/0npmR1Zr Tôi không chắc chắn lý do tại sao làm nổi bật được tất cả các điều sai lầm dù Đây là từ các tập tin chính prnt.sc/n6xsyl Đây là từ một tập tin prnt.sc/n6xtp0 Rõ ràng một số phần của nó hiểu những gì đang xảy ra, nhưng trình biên dịch thì không. Tôi đang sử dụng phiên bản 3.2.2 của bản thảo
Logan

1
Đáng ngạc nhiên, ... "include": [ "src/**/*" ] ...làm việc cho tôi nhưng "include": ["./src/", "./src/Types/*.d.ts"],không. Tôi đã không cố gắng để hiểu điều này chưa
16kb

Giao diện nhập bằng cách sử dụng nhập động làm việc cho tôi. Cảm ơn
Roman Mahotskyi

3

Có thể vấn đề này đã được trả lời, nhưng tôi muốn chia sẻ một chút, bây giờ đôi khi một giao diện như các câu trả lời khác có thể hơi quá hạn chế, nhưng chúng tôi thực sự có thể duy trì các thuộc tính cần thiết và sau đó thêm bất kỳ thuộc tính bổ sung nào bằng cách tạo khóa với loại stringcó giá trị loạiany

import { Request, Response, NextFunction } from 'express'

interface IRequest extends Request {
  [key: string]: any
}

app.use( (req: IRequest, res: Response, next: NextFunction) => {
  req.property = setProperty();

  next();
});

Vì vậy, bây giờ, chúng ta cũng có thể thêm bất kỳ thuộc tính bổ sung nào mà chúng ta muốn vào đối tượng này.


2

Nếu bạn đang tìm kiếm giải pháp hoạt động với express4, thì đây là:

@ type / express / index.d.ts: -------- phải là /index.d.ts

declare namespace Express { // must be namespace, and not declare module "Express" { 
  export interface Request {
    user: any;
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2016",
    "typeRoots" : [
      "@types", // custom merged types must be first in a list
      "node_modules/@types",
    ]
  }
}

Tham chiếu từ https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308


2

Tất cả những phản hồi này dường như là sai hoặc lỗi thời theo cách này hay cách khác.

Điều này làm việc cho tôi vào tháng 5 năm 2020:

trong ${PROJECT_ROOT}/@types/express/index.d.ts:

import * as express from "express"

declare global {
    namespace Express {
        interface Request {
            my_custom_property: TheCustomType
        }
    }
}

trong tsconfig.json, thêm / hợp nhất các thuộc tính sao cho:

"typeRoots": [ "@types" ]

Chúc mừng.


Hoạt động với Webpack + Docker, nhập * có thể được thay thế bằng export {};
Dooomel

1

Một giải pháp khả thi là sử dụng "đúc kép cho bất kỳ"

1- xác định giao diện với tài sản của bạn

export interface MyRequest extends http.IncomingMessage {
     myProperty: string
}

2- diễn viên đôi

app.use((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: Error) => void) => {
    const myReq: MyRequest = req as any as MyRequest
    myReq.myProperty = setProperty()
    next()
})

Ưu điểm của đúc đôi là:

  • đánh máy có sẵn
  • nó không gây ô nhiễm các định nghĩa hiện có nhưng mở rộng chúng, tránh nhầm lẫn
  • vì việc truyền là rõ ràng, nó biên dịch tiền phạt với -noImplicitanycờ

Ngoài ra, có tuyến đường nhanh (chưa được tháo lắp):

 req['myProperty'] = setProperty()

(không chỉnh sửa các tệp định nghĩa hiện có với các thuộc tính của riêng bạn - điều này không thể nhầm lẫn. Nếu các định nghĩa sai, hãy mở một yêu cầu kéo)

BIÊN TẬP

Xem bình luận dưới đây, công việc đúc đơn giản trong trường hợp này req as MyRequest


@akshay Trong trường hợp này, vâng, vì MyRequestmở rộng http.IncomingMessage. Không phải như vậy, việc nhân đôi thông qua anysẽ là sự thay thế duy nhất
Bruno Grieder

Chúng tôi khuyên bạn nên chọn không biết thay vì bất kỳ.
dev

0

Câu trả lời này sẽ có lợi cho những người dựa vào gói npm ts-node.

Tôi cũng đang vật lộn với mối quan tâm tương tự về việc mở rộng đối tượng yêu cầu , tôi đã theo dõi rất nhiều câu trả lời trong stack-overflow & kết thúc bằng chiến lược được đề cập dưới đây.

Tôi đã khai báo gõ mở rộng cho express trong thư mục sau.${PROJECT_ROOT}/api/@types/express/index.d.ts

declare namespace Express {
  interface Request {
    decoded?: any;
  }
}

sau đó cập nhật của tôi tsconfig.jsonđể một cái gì đó như thế này.

{
  "compilerOptions": {
     "typeRoots": ["api/@types", "node_modules/@types"]
      ...
  }
}

ngay cả sau khi thực hiện các bước trên, studio hình ảnh đã ngừng phàn nàn, nhưng thật không may, ts-nodetrình biên dịch vẫn được sử dụng để ném.

 Property 'decoded' does not exist on type 'Request'.

Rõ ràng, ts-nodekhông thể định vị các định nghĩa kiểu mở rộng cho đối tượng yêu cầu .

Cuối cùng, sau khi dành hàng giờ, vì tôi biết Mã VS không phàn nàn & có thể xác định được các định nghĩa đánh máy, ngụ ý có gì đó không ổn với ts-nodetrình biên dịch.

Cập nhật bắt đầu scripttrong package.jsoncố định nó cho tôi.

"start": "ts-node --files api/index.ts",

các --filesđối số đóng vai trò chính ở đây tìm cách xác định các định nghĩa loại tùy chỉnh.

Để biết thêm thông tin, vui lòng truy cập: https://github.com/TypeStrong/ts-node#help-my-types-are-missing


0

Để giúp bất cứ ai đang tìm kiếm thứ gì khác để thử ở đây là những gì hiệu quả với tôi vào cuối tháng 5 năm 2020 khi cố gắng gia hạn Yêu cầu của ExpressJS. Tôi đã phải thử hơn một tá thứ trước khi làm việc này:

  • Lật thứ tự những gì mọi người đang đề xuất trong "typeRoots" của tsconfig.json của bạn (và đừng quên bỏ đường dẫn src nếu bạn có cài đặt rootDir trong tsconfig, chẳng hạn như "./src"). Thí dụ:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Ví dụ về tiện ích mở rộng tùy chỉnh ('./your-custom-types-dir/express/index.d.ts "). Tôi đã phải sử dụng nhập nội tuyến và xuất mặc định để sử dụng các lớp làm kinh nghiệm của mình để nó cũng được hiển thị:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Cập nhật tệp gật đầu của bạn để thêm lệnh "--files" vào nút ts, ví dụ:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}

0

Có thể đã khá muộn cho câu trả lời này, nhưng dù sao đây là cách tôi giải quyết:

  1. Đảm bảo rằng bạn có nguồn loại của bạn trong tsconfigtệp của bạn (đây có thể là một chủ đề hoàn toàn mới)
  2. Trong thư mục loại của bạn thêm một thư mục mới và đặt tên là gói bạn muốn mở rộng hoặc tạo loại cho. Trong trường hợp cụ thể này, bạn sẽ tạo một thư mục có tênexpress
  3. Trong expressthư mục tạo một tập tin và đặt tên cho nó index.d.ts(PHẢI ĐƯỢC CHÍNH XÁC NHƯ VẬY)
  4. Cuối cùng, để tạo phần mở rộng của các loại bạn chỉ cần đặt một mã như sau:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.