mirror of
https://github.com/zhigang1992/DefinitelyTyped.git
synced 2026-05-13 12:37:16 +08:00
Merge pull request #29021 from joaovieira/next/component-types
[@types/next] Add NextComponentType akin to React.ComponentType.
This commit is contained in:
47
types/next/app.d.ts
vendored
47
types/next/app.d.ts
vendored
@@ -1,19 +1,46 @@
|
||||
import * as React from "react";
|
||||
import { NextContext } from ".";
|
||||
import { NextContext, NextComponentType } from ".";
|
||||
import { RouterProps, DefaultQuery } from "./router";
|
||||
|
||||
export interface AppComponentProps<Q = DefaultQuery> {
|
||||
Component: React.ComponentType<any>;
|
||||
router: RouterProps<Q>;
|
||||
pageProps: any;
|
||||
}
|
||||
// Deprecated
|
||||
export type AppComponentProps = AppProps;
|
||||
export type AppComponentContext = NextAppContext;
|
||||
// End Deprecated
|
||||
|
||||
export interface AppComponentContext<Q = DefaultQuery> {
|
||||
Component: React.ComponentType<any>;
|
||||
/**
|
||||
* Context passed to App.getInitialProps.
|
||||
* Component is dynamic - cannot infer type.
|
||||
*
|
||||
* @template Q Query object schema.
|
||||
*/
|
||||
export interface NextAppContext<Q = DefaultQuery> {
|
||||
Component: NextComponentType<any, any, NextContext<Q>>;
|
||||
router: RouterProps<Q>;
|
||||
ctx: NextContext<Q>;
|
||||
}
|
||||
|
||||
export class Container extends React.Component { }
|
||||
/**
|
||||
* Props passed to the App component.
|
||||
* Component and pageProps are dynamic - cannot infer type.
|
||||
*
|
||||
* @template Q Query object schema.
|
||||
*/
|
||||
export interface AppProps<Q = DefaultQuery> {
|
||||
Component: NextComponentType<any, any, NextContext<Q>>;
|
||||
router: RouterProps<Q>;
|
||||
pageProps: any;
|
||||
}
|
||||
|
||||
export default class App<TProps = {}, Q = DefaultQuery> extends React.Component<TProps & AppComponentProps<Q>> { }
|
||||
/**
|
||||
* App component type. Differs from the default type because the context it passes
|
||||
* to getInitialProps and the props is passes to the component are different.
|
||||
*
|
||||
* @template IP Initial props returned from getInitialProps.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
export type AppComponentType<IP = {}, C = NextAppContext> = NextComponentType<IP & AppProps, IP, C>;
|
||||
|
||||
export class Container extends React.Component {}
|
||||
export default class App<IP = {}, C = NextAppContext> extends React.Component<IP & AppProps> {
|
||||
getInitialProps(context: C): Promise<IP> | IP;
|
||||
}
|
||||
|
||||
47
types/next/document.d.ts
vendored
47
types/next/document.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { NextContext } from ".";
|
||||
import { NextContext, NextComponentType } from ".";
|
||||
import { DefaultQuery } from "./router";
|
||||
|
||||
export interface RenderPageResponse {
|
||||
buildManifest: { [key: string]: any };
|
||||
@@ -21,16 +21,25 @@ export interface AnyPageProps extends PageProps {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type Enhancer<E extends PageProps = AnyPageProps, P extends any = E> = (page: React.ComponentType<P>) => React.ComponentType<E>;
|
||||
export type Enhancer<E extends PageProps = AnyPageProps, P extends any = E> = (
|
||||
page: React.ComponentType<P>
|
||||
) => React.ComponentType<E>;
|
||||
|
||||
/**
|
||||
* Context object used inside `Document`
|
||||
* Context passed to Document.getInitialProps.
|
||||
*
|
||||
* @template Q Query object schema.
|
||||
*/
|
||||
export interface NextDocumentContext extends NextContext {
|
||||
export interface NextDocumentContext<Q = DefaultQuery> extends NextContext<Q> {
|
||||
/** A callback that executes the actual React rendering logic (synchronously) */
|
||||
renderPage<E extends PageProps = AnyPageProps, P extends any = E>(enhancer?: Enhancer<E, P>): RenderPageResponse; // tslint:disable-line:no-unnecessary-generics
|
||||
renderPage<E extends PageProps = AnyPageProps, P extends any = E>(
|
||||
enhancer?: Enhancer<E, P> // tslint:disable-line no-unnecessary-generics
|
||||
): RenderPageResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props passed to the Document component.
|
||||
*/
|
||||
export interface DocumentProps {
|
||||
__NEXT_DATA__?: any;
|
||||
dev?: boolean;
|
||||
@@ -45,18 +54,40 @@ export interface DocumentProps {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props supported by the Head component.
|
||||
*/
|
||||
export interface HeadProps {
|
||||
nonce?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props supported by the NextScript component.
|
||||
*/
|
||||
export interface NextScriptProps {
|
||||
nonce?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document component type. Differs from the default type because the context it passes
|
||||
* to getInitialProps and the props is passes to the component are different.
|
||||
*
|
||||
* @template IP Initial props returned from getInitialProps.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
export type DocumentComponentType<IP = {}, C = NextDocumentContext> = NextComponentType<
|
||||
IP & DocumentProps,
|
||||
IP,
|
||||
C
|
||||
>;
|
||||
|
||||
export class Head extends React.Component<HeadProps> {}
|
||||
export class Main extends React.Component {}
|
||||
export class NextScript extends React.Component<NextScriptProps> {}
|
||||
export default class Document<TProps = {}> extends React.Component<TProps & DocumentProps> {
|
||||
static getInitialProps(ctx: NextDocumentContext): Promise<DocumentProps> | DocumentProps;
|
||||
export default class Document<IP = {}, C = NextDocumentContext> extends React.Component<
|
||||
IP & DocumentProps
|
||||
> {
|
||||
getInitialProps(context: C): Promise<IP> | IP;
|
||||
}
|
||||
|
||||
73
types/next/index.d.ts
vendored
73
types/next/index.d.ts
vendored
@@ -16,16 +16,22 @@ import * as url from "url";
|
||||
|
||||
import { Response as NodeResponse } from "node-fetch";
|
||||
|
||||
import { SingletonRouter, DefaultQuery } from './router';
|
||||
import { SingletonRouter, DefaultQuery } from "./router";
|
||||
|
||||
declare namespace next {
|
||||
// Deprecated
|
||||
type QueryStringMapObject = DefaultQuery;
|
||||
type ServerConfig = NextConfig;
|
||||
// End Deprecated
|
||||
|
||||
type UrlLike = url.UrlObject | url.Url;
|
||||
|
||||
/**
|
||||
* Context object used in methods like `getInitialProps()`
|
||||
* https://github.com/zeit/next.js/blob/6.1.1/server/render.js#L77
|
||||
* https://github.com/zeit/next.js/blob/6.1.1/readme.md#fetching-data-and-component-lifecycle
|
||||
*
|
||||
* @template Q Query object schema.
|
||||
*/
|
||||
interface NextContext<Q = DefaultQuery> {
|
||||
/** path section of URL */
|
||||
@@ -44,19 +50,11 @@ declare namespace next {
|
||||
err?: Error;
|
||||
}
|
||||
|
||||
type NextSFC<TProps = {}, Q = DefaultQuery> = NextStatelessComponent<TProps, Q>;
|
||||
interface NextStatelessComponent<TProps = {}, Q = DefaultQuery>
|
||||
extends React.StatelessComponent<TProps> {
|
||||
getInitialProps?: (ctx: NextContext<Q>) => Promise<TProps>;
|
||||
}
|
||||
|
||||
type UrlLike = url.UrlObject | url.Url;
|
||||
|
||||
/**
|
||||
* Next.js config schema.
|
||||
* https://github.com/zeit/next.js/blob/6.1.1/server/config.js#L10
|
||||
*/
|
||||
interface ServerConfig {
|
||||
interface NextConfig {
|
||||
webpack?: any;
|
||||
webpackDevMiddleware?: any;
|
||||
poweredByHeader?: boolean;
|
||||
@@ -83,7 +81,7 @@ declare namespace next {
|
||||
dev?: boolean;
|
||||
staticMarkup?: boolean;
|
||||
quiet?: boolean;
|
||||
conf?: ServerConfig;
|
||||
conf?: NextConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +95,7 @@ declare namespace next {
|
||||
quiet: boolean;
|
||||
router: SingletonRouter;
|
||||
http: null | http.Server;
|
||||
nextConfig: ServerConfig;
|
||||
nextConfig: NextConfig;
|
||||
distDir: string;
|
||||
buildId: string;
|
||||
hotReloader: any;
|
||||
@@ -114,7 +112,7 @@ declare namespace next {
|
||||
|
||||
getHotReloader(
|
||||
dir: string,
|
||||
options: { quiet: boolean; config: ServerConfig; buildId: string }
|
||||
options: { quiet: boolean; config: NextConfig; buildId: string }
|
||||
): any;
|
||||
handleRequest(
|
||||
req: http.IncomingMessage,
|
||||
@@ -132,11 +130,7 @@ declare namespace next {
|
||||
close(): Promise<void>;
|
||||
defineRoutes(): Promise<void>;
|
||||
start(): Promise<void>;
|
||||
run(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
parsedUrl: UrlLike
|
||||
): Promise<void>;
|
||||
run(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: UrlLike): Promise<void>;
|
||||
|
||||
render(
|
||||
req: http.IncomingMessage,
|
||||
@@ -182,6 +176,49 @@ declare namespace next {
|
||||
getCompilationError(): Promise<any>;
|
||||
send404(res: http.ServerResponse): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next.js counterpart of React.ComponentType.
|
||||
* Specially useful in HOCs that receive Next.js components.
|
||||
*
|
||||
* @template P Component props.
|
||||
* @template IP Initial props returned from getInitialProps.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
type NextComponentType<P = {}, IP = P, C = NextContext> =
|
||||
| NextComponentClass<P, IP, C>
|
||||
| NextStatelessComponent<P, IP, C>;
|
||||
|
||||
/**
|
||||
* Next.js counterpart of React.SFC/React.StatelessComponent.
|
||||
*
|
||||
* @template P Component props.
|
||||
* @template IP Initial props returned from getInitialProps.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
type NextSFC<P = {}, IP = P, C = NextContext> = NextStatelessComponent<P, IP, C>;
|
||||
type NextStatelessComponent<P = {}, IP = P, C = NextContext> = React.StatelessComponent<P> &
|
||||
NextStaticLifecycle<IP, C>;
|
||||
|
||||
/**
|
||||
* Next.js counterpart of React.ComponentClass.
|
||||
*
|
||||
* @template P Component props.
|
||||
* @template IP Initial props returned from getInitialProps.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
type NextComponentClass<P = {}, IP = P, C = NextContext> = React.ComponentClass<P> &
|
||||
NextStaticLifecycle<IP, C>;
|
||||
|
||||
/**
|
||||
* Next.js specific lifecycle methods.
|
||||
*
|
||||
* @template IP Initial props returned from getInitialProps and passed to the component.
|
||||
* @template C Context passed to getInitialProps.
|
||||
*/
|
||||
interface NextStaticLifecycle<IP, C> {
|
||||
getInitialProps?: (ctx: C) => Promise<IP> | IP;
|
||||
}
|
||||
}
|
||||
|
||||
declare function next(options?: next.ServerOptions): next.Server;
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import * as React from "react";
|
||||
import App, { Container, AppComponentContext } from "next/app";
|
||||
import { NextStatelessComponent } from "next";
|
||||
import App, { Container, NextAppContext, AppProps, AppComponentType } from "next/app";
|
||||
|
||||
interface NextComponentProps {
|
||||
example: string;
|
||||
}
|
||||
|
||||
class TestApp extends App<NextComponentProps> {
|
||||
static async getInitialProps({ Component, router, ctx }: AppComponentContext) {
|
||||
let pageProps = {};
|
||||
// TODO: fix AppComponentContext to return NextComponentType instead of React.ComponentType
|
||||
const Page = Component as NextStatelessComponent;
|
||||
|
||||
if (Page.getInitialProps) {
|
||||
pageProps = await Page.getInitialProps(ctx);
|
||||
}
|
||||
|
||||
return { pageProps };
|
||||
}
|
||||
interface TypedQuery {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
class TestApp extends App {
|
||||
render() {
|
||||
const { Component, router, pageProps } = this.props;
|
||||
return (
|
||||
@@ -28,3 +19,78 @@ class TestApp extends App<NextComponentProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestAppWithProps extends App<NextComponentProps> {
|
||||
static async getInitialProps({ Component, router, ctx }: NextAppContext) {
|
||||
const pageProps = Component.getInitialProps && (await Component.getInitialProps(ctx));
|
||||
return { pageProps, example: "foobar" };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { Component, router, pageProps, example } = this.props;
|
||||
return <Component {...pageProps} example={example} />;
|
||||
}
|
||||
}
|
||||
|
||||
class TestAppWithTypedQuery extends App<{}, NextAppContext<TypedQuery>> {
|
||||
static async getInitialProps({ ctx }: NextAppContext<TypedQuery>) {
|
||||
const { id } = ctx.query;
|
||||
const processQuery = (id?: string) => id;
|
||||
processQuery(id);
|
||||
}
|
||||
}
|
||||
|
||||
interface WithExampleProps {
|
||||
example: string;
|
||||
}
|
||||
|
||||
interface WithExampleHocProps {
|
||||
test: string;
|
||||
}
|
||||
|
||||
interface TestProps {
|
||||
ownProp: boolean;
|
||||
}
|
||||
|
||||
// Stateful HOC that adds props to wrapped component. Similar to what withRedux does.
|
||||
// tslint:disable-next-line no-unnecessary-generics
|
||||
const withExample = <P extends {}>(App: AppComponentType<P & WithExampleProps>) =>
|
||||
class extends React.Component<P & AppProps & WithExampleHocProps> {
|
||||
test: string;
|
||||
|
||||
static async getInitialProps(context: NextAppContext) {
|
||||
const pageProps = App.getInitialProps && (await App.getInitialProps(context));
|
||||
|
||||
// tslint:disable-next-line prefer-object-spread
|
||||
return Object.assign({}, pageProps, { test: "test" });
|
||||
}
|
||||
|
||||
constructor(props: P & AppProps & WithExampleHocProps) {
|
||||
super(props);
|
||||
this.test = props.test;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <App {...this.props} example={this.test} />;
|
||||
}
|
||||
};
|
||||
|
||||
// Basic stateless HOC. Similar to what withAuth would do.
|
||||
// tslint:disable-next-line no-unnecessary-generics
|
||||
const withBasic = <P extends {}, C extends {}>(App: AppComponentType<P, C>) =>
|
||||
class extends React.Component<P & AppProps> {
|
||||
static async getInitialProps(context: C) {
|
||||
const pageProps = App.getInitialProps && (await App.getInitialProps(context));
|
||||
|
||||
// tslint:disable-next-line prefer-object-spread
|
||||
return Object.assign({}, pageProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <App {...this.props} />;
|
||||
}
|
||||
};
|
||||
|
||||
withExample(TestAppWithProps);
|
||||
|
||||
withBasic(TestAppWithTypedQuery);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { NextStatelessComponent, NextContext } from "next";
|
||||
import { NextStatelessComponent, NextContext, NextComponentType } from "next";
|
||||
|
||||
interface NextComponentProps {
|
||||
example: string;
|
||||
@@ -12,13 +12,11 @@ interface TypedQuery {
|
||||
class ClassNext extends React.Component<NextComponentProps> {
|
||||
static async getInitialProps(ctx: NextContext) {
|
||||
const { example } = ctx.query;
|
||||
return { example };
|
||||
return { example: example as string };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>I'm a class component! {this.props.example}</div>
|
||||
);
|
||||
return <div>I'm a class component! {this.props.example}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,3 +36,87 @@ StatelessNext.getInitialProps = async ({ query }: NextContext) => {
|
||||
const { example } = query;
|
||||
return { example: example as string };
|
||||
};
|
||||
|
||||
interface WithExampleProps {
|
||||
example: string;
|
||||
}
|
||||
|
||||
interface WithExampleHocProps {
|
||||
test: string;
|
||||
}
|
||||
|
||||
interface TestProps {
|
||||
ownProp: boolean;
|
||||
}
|
||||
|
||||
// Stateful HOC that adds props to wrapped component. Similar to what withRedux does.
|
||||
// tslint:disable-next-line use-default-type-parameter
|
||||
const withExample = <P extends {}>(Page: NextComponentType<P & WithExampleProps, P>) =>
|
||||
class extends React.Component<P & WithExampleHocProps> {
|
||||
test: string;
|
||||
|
||||
static async getInitialProps(ctx: NextContext) {
|
||||
const pageProps = Page.getInitialProps && (await Page.getInitialProps(ctx));
|
||||
|
||||
// tslint:disable-next-line prefer-object-spread
|
||||
return Object.assign({}, pageProps, { test: "test" });
|
||||
}
|
||||
|
||||
constructor(props: P & WithExampleHocProps) {
|
||||
super(props);
|
||||
this.test = props.test;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Page {...this.props} example={this.test} />;
|
||||
}
|
||||
};
|
||||
|
||||
// Basic stateless HOC. Similar to what withAuth would do.
|
||||
// tslint:disable-next-line no-unnecessary-generics
|
||||
const withBasic = <P extends {}>(Page: NextComponentType<P>) =>
|
||||
class extends React.Component<P> {
|
||||
static async getInitialProps(ctx: NextContext) {
|
||||
const pageProps = Page.getInitialProps && (await Page.getInitialProps(ctx));
|
||||
|
||||
// tslint:disable-next-line prefer-object-spread
|
||||
const props = Object.assign({}, pageProps);
|
||||
|
||||
if (ctx.query.example === "bar") {
|
||||
// Redirect
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Page {...this.props} />;
|
||||
}
|
||||
};
|
||||
|
||||
class NextWithExample extends React.Component<TestProps & WithExampleProps> {
|
||||
static async getInitialProps(ctx: NextContext<TypedQuery>) {
|
||||
const { id } = ctx.query;
|
||||
const processQuery = (id?: string) => id;
|
||||
processQuery(id);
|
||||
return { ownProp: true };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ownProp, example } = this.props;
|
||||
return (
|
||||
<div>
|
||||
I'm wrapped in a HOC that gives me an example prop! {example} {ownProp}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// P template is inferred as <TestProps>.
|
||||
withExample(NextWithExample);
|
||||
|
||||
// P template inferred as <NextComponentProps>.
|
||||
withBasic(ClassNext);
|
||||
|
||||
// P template inferred as <TestProps & WithExampleProps>
|
||||
withBasic(withExample(NextWithExample));
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import withTypescript = require('@zeit/next-typescript');
|
||||
|
||||
withTypescript({}); // $ExpectType ServerConfig
|
||||
withTypescript({}); // $ExpectType NextConfig
|
||||
|
||||
Reference in New Issue
Block a user