未验证 提交 bf294ee4 编写于 作者: L Lukáš Huvar 提交者: GitHub

Types description (#7217)

* App, Document, Page, Error types description

* Router description

* Added head changed router

* Additions

* Fix examples

* Push and replace details

* Update packages/next-server/lib/head.tsx
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next-server/lib/utils.ts
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next/types/index.d.ts
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next/pages/_document.tsx
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next/pages/_document.tsx
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next/pages/_app.tsx
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>

* Update packages/next/pages/_document.tsx
Co-Authored-By: NLuis Fernando Alvarez D. <luis@zeit.co>
上级 ed0e1750
...@@ -9,7 +9,7 @@ export default class MyDocument extends Document { ...@@ -9,7 +9,7 @@ export default class MyDocument extends Document {
try { try {
ctx.renderPage = () => ctx.renderPage = () =>
originalRenderPage({ originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />) enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
}); });
const initialProps = await Document.getInitialProps(ctx); const initialProps = await Document.getInitialProps(ctx);
...@@ -20,7 +20,7 @@ export default class MyDocument extends Document { ...@@ -20,7 +20,7 @@ export default class MyDocument extends Document {
{initialProps.styles} {initialProps.styles}
{sheet.getStyleElement()} {sheet.getStyleElement()}
</> </>
) ),
}; };
} finally { } finally {
sheet.seal(); sheet.seal();
......
...@@ -131,6 +131,10 @@ function reduceComponents(headElements: Array<React.ReactElement<any>>, props: W ...@@ -131,6 +131,10 @@ function reduceComponents(headElements: Array<React.ReactElement<any>>, props: W
const Effect = withSideEffect(); const Effect = withSideEffect();
/**
* This component injects elements to `<head>` of your page.
* To avoid duplicated `tags` in `<head>` you can use the `key` property, which will make sure every tag is only rendered once.
*/
function Head({ children }: { children: React.ReactNode }) { function Head({ children }: { children: React.ReactNode }) {
return ( return (
<AmpModeContext.Consumer> <AmpModeContext.Consumer>
......
...@@ -34,6 +34,9 @@ export default class Router implements BaseRouter { ...@@ -34,6 +34,9 @@ export default class Router implements BaseRouter {
pathname: string pathname: string
query: ParsedUrlQuery query: ParsedUrlQuery
asPath: string asPath: string
/**
* Map of all components loaded in `Router`
*/
components: {[pathname: string]: RouteInfo} components: {[pathname: string]: RouteInfo}
subscriptions: Set<Subscription> subscriptions: Set<Subscription>
componentLoadCancel: (() => void) | null componentLoadCancel: (() => void) | null
...@@ -151,14 +154,29 @@ export default class Router implements BaseRouter { ...@@ -151,14 +154,29 @@ export default class Router implements BaseRouter {
window.location.reload() window.location.reload()
} }
/**
* Go back in history
*/
back() { back() {
window.history.back() window.history.back()
} }
/**
* Performs a `pushState` with arguments
* @param url of the route
* @param as masks `url` for the browser
* @param options object you can define `shallow` and other options
*/
push(url: string, as: string = url, options = {}) { push(url: string, as: string = url, options = {}) {
return this.change('pushState', url, as, options) return this.change('pushState', url, as, options)
} }
/**
* Performs a `replaceState` with arguments
* @param url of the route
* @param as masks `url` for the browser
* @param options object you can define `shallow` and other options
*/
replace(url: string, as: string = url, options = {}) { replace(url: string, as: string = url, options = {}) {
return this.change('replaceState', url, as, options) return this.change('replaceState', url, as, options)
} }
...@@ -339,6 +357,10 @@ export default class Router implements BaseRouter { ...@@ -339,6 +357,10 @@ export default class Router implements BaseRouter {
this.notify(data) this.notify(data)
} }
/**
* Callback to execute before replacing router state
* @param cb callback to be executed
*/
beforePopState(cb: BeforePopStateCallback) { beforePopState(cb: BeforePopStateCallback) {
this._bps = cb this._bps = cb
} }
...@@ -391,6 +413,11 @@ export default class Router implements BaseRouter { ...@@ -391,6 +413,11 @@ export default class Router implements BaseRouter {
return this.asPath !== asPath return this.asPath !== asPath
} }
/**
* Prefetch `page` code, you may wait for the data during `page` rendering.
* This feature only works in production!
* @param url of prefetched `page`
*/
prefetch(url: string): Promise<void> { prefetch(url: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Prefetch is not supported in development mode because it would trigger on-demand-entries // Prefetch is not supported in development mode because it would trigger on-demand-entries
......
import { format, UrlObject, URLFormatOptions } from 'url' import { format, UrlObject, URLFormatOptions } from 'url'
import { ServerResponse, IncomingMessage } from 'http'; import { ServerResponse, IncomingMessage } from 'http'
import { ComponentType } from 'react' import { ComponentType } from 'react'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
import { ManifestItem } from '../server/get-dynamic-import-bundles' import { ManifestItem } from '../server/get-dynamic-import-bundles'
...@@ -8,23 +8,44 @@ import { BaseRouter } from './router/router' ...@@ -8,23 +8,44 @@ import { BaseRouter } from './router/router'
/** /**
* Types used by both next and next-server * Types used by both next and next-server
*/ */
export type NextComponentType<C extends BaseContext = NextPageContext, IP = {}, P = {}> = ComponentType<P> & { export type NextComponentType<
C extends BaseContext = NextPageContext,
IP = {},
P = {}
> = ComponentType<P> & {
getInitialProps?(context: C): Promise<IP>, getInitialProps?(context: C): Promise<IP>,
} }
export type DocumentType = NextComponentType<DocumentContext, DocumentInitialProps, DocumentProps> export type DocumentType = NextComponentType<
DocumentContext,
DocumentInitialProps,
DocumentProps
>
export type AppType = NextComponentType<AppContextType, AppInitialProps, AppPropsType> export type AppType = NextComponentType<
AppContextType,
AppInitialProps,
AppPropsType
>
export type Enhancer<C> = (Component: C) => C export type Enhancer<C> = (Component: C) => C
export type ComponentsEnhancer = export type ComponentsEnhancer =
| { enhanceApp?: Enhancer<AppType>; enhanceComponent?: Enhancer<NextComponentType> } | {
enhanceApp?: Enhancer<AppType>
enhanceComponent?: Enhancer<NextComponentType>,
}
| Enhancer<NextComponentType> | Enhancer<NextComponentType>
export type RenderPageResult = { html: string, head?: Array<JSX.Element | null>, dataOnly?: true } export type RenderPageResult = {
html: string
head?: Array<JSX.Element | null>
dataOnly?: true,
}
export type RenderPage = (options?: ComponentsEnhancer) => RenderPageResult | Promise<RenderPageResult> export type RenderPage = (
options?: ComponentsEnhancer,
) => RenderPageResult | Promise<RenderPageResult>
export type BaseContext = { export type BaseContext = {
res?: ServerResponse res?: ServerResponse
...@@ -45,13 +66,34 @@ export type NEXT_DATA = { ...@@ -45,13 +66,34 @@ export type NEXT_DATA = {
err?: Error & { statusCode?: number }, err?: Error & { statusCode?: number },
} }
/**
* `Next` context
*/
// tslint:disable-next-line interface-name // tslint:disable-next-line interface-name
export interface NextPageContext { export interface NextPageContext {
/**
* Error object if encountered during rendering
*/
err?: Error & { statusCode?: number } | null err?: Error & { statusCode?: number } | null
/**
* `HTTP` request object.
*/
req?: IncomingMessage req?: IncomingMessage
/**
* `HTTP` response object.
*/
res?: ServerResponse res?: ServerResponse
/**
* Path section of `URL`.
*/
pathname: string pathname: string
/**
* Query string section of `URL` parsed as an object.
*/
query: ParsedUrlQuery query: ParsedUrlQuery
/**
* `String` of the actual path including query.
*/
asPath?: string asPath?: string
} }
...@@ -65,7 +107,10 @@ export type AppInitialProps = { ...@@ -65,7 +107,10 @@ export type AppInitialProps = {
pageProps: any, pageProps: any,
} }
export type AppPropsType<R extends BaseRouter = BaseRouter, P = {}> = AppInitialProps & { export type AppPropsType<
R extends BaseRouter = BaseRouter,
P = {}
> = AppInitialProps & {
Component: NextComponentType<NextPageContext, any, P> Component: NextComponentType<NextPageContext, any, P>
router: R, router: R,
} }
...@@ -117,17 +162,25 @@ export function getURL() { ...@@ -117,17 +162,25 @@ export function getURL() {
} }
export function getDisplayName(Component: ComponentType<any>) { export function getDisplayName(Component: ComponentType<any>) {
return typeof Component === 'string' ? Component : (Component.displayName || Component.name || 'Unknown') return typeof Component === 'string'
? Component
: Component.displayName || Component.name || 'Unknown'
} }
export function isResSent(res: ServerResponse) { export function isResSent(res: ServerResponse) {
return res.finished || res.headersSent return res.finished || res.headersSent
} }
export async function loadGetInitialProps<C extends BaseContext, IP = {}, P = {}>(Component: NextComponentType<C, IP, P>, ctx: C): Promise<IP | null> { export async function loadGetInitialProps<
C extends BaseContext,
IP = {},
P = {}
>(Component: NextComponentType<C, IP, P>, ctx: C): Promise<IP | null> {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
if (Component.prototype && Component.prototype.getInitialProps) { if (Component.prototype && Component.prototype.getInitialProps) {
const message = `"${getDisplayName(Component)}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.` const message = `"${getDisplayName(
Component,
)}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.`
throw new Error(message) throw new Error(message)
} }
} }
...@@ -148,28 +201,46 @@ export async function loadGetInitialProps<C extends BaseContext, IP = {}, P = {} ...@@ -148,28 +201,46 @@ export async function loadGetInitialProps<C extends BaseContext, IP = {}, P = {}
// set cache-control header to stale-while-revalidate // set cache-control header to stale-while-revalidate
if (ctx.Component && !ctx.Component.getInitialProps) { if (ctx.Component && !ctx.Component.getInitialProps) {
if (res && res.setHeader) { if (res && res.setHeader) {
res.setHeader( res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate')
'Cache-Control', 's-maxage=86400, stale-while-revalidate',
)
} }
} }
if (!props) { if (!props) {
const message = `"${getDisplayName(Component)}.getInitialProps()" should resolve to an object. But found "${props}" instead.` const message = `"${getDisplayName(
Component,
)}.getInitialProps()" should resolve to an object. But found "${props}" instead.`
throw new Error(message) throw new Error(message)
} }
return props return props
} }
export const urlObjectKeys = ['auth', 'hash', 'host', 'hostname', 'href', 'path', 'pathname', 'port', 'protocol', 'query', 'search', 'slashes'] export const urlObjectKeys = [
'auth',
export function formatWithValidation(url: UrlObject, options?: URLFormatOptions) { 'hash',
'host',
'hostname',
'href',
'path',
'pathname',
'port',
'protocol',
'query',
'search',
'slashes',
]
export function formatWithValidation(
url: UrlObject,
options?: URLFormatOptions,
) {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
if (url !== null && typeof url === 'object') { if (url !== null && typeof url === 'object') {
Object.keys(url).forEach((key) => { Object.keys(url).forEach((key) => {
if (urlObjectKeys.indexOf(key) === -1) { if (urlObjectKeys.indexOf(key) === -1) {
console.warn(`Unknown key passed via urlObject into url.format: ${key}`) console.warn(
`Unknown key passed via urlObject into url.format: ${key}`,
)
} }
}) })
} }
......
import React, {ErrorInfo} from 'react' import React, { ErrorInfo } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { execOnce, loadGetInitialProps, AppContextType, AppInitialProps, AppPropsType } from 'next-server/dist/lib/utils' import { execOnce, loadGetInitialProps, AppContextType, AppInitialProps, AppPropsType } from 'next-server/dist/lib/utils'
import { Router, makePublicRouterInstance } from '../client/router' import { Router, makePublicRouterInstance } from '../client/router'
...@@ -9,6 +9,10 @@ export type AppContext = AppContextType<Router> ...@@ -9,6 +9,10 @@ export type AppContext = AppContextType<Router>
export type AppProps<P = {}> = AppPropsType<Router, P> export type AppProps<P = {}> = AppPropsType<Router, P>
/**
* `App` component is used for initialize of pages. It allows for overwriting and full control of the `page` initialization.
* This allows for keeping state between navigation, custom error handling, injecting additional data.
*/
async function appGetInitialProps({ Component, ctx }: AppContext): Promise<AppInitialProps> { async function appGetInitialProps({ Component, ctx }: AppContext): Promise<AppInitialProps> {
const pageProps = await loadGetInitialProps(Component, ctx) const pageProps = await loadGetInitialProps(Component, ctx)
return { pageProps } return { pageProps }
...@@ -74,7 +78,9 @@ export class Container extends React.Component { ...@@ -74,7 +78,9 @@ export class Container extends React.Component {
const warnUrl = execOnce(() => { const warnUrl = execOnce(() => {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
console.error(`Warning: the 'url' property is deprecated. https://err.sh/zeit/next.js/url-deprecated`) console.error(
`Warning: the 'url' property is deprecated. https://err.sh/zeit/next.js/url-deprecated`,
)
} }
}) })
......
...@@ -22,12 +22,20 @@ export type DocumentComponentContext = { ...@@ -22,12 +22,20 @@ export type DocumentComponentContext = {
readonly _devOnlyInvalidateCacheQueryString: string, readonly _devOnlyInvalidateCacheQueryString: string,
} }
/**
* `Document` component handles the initial `document` markup and renders only on the server side.
* Commonly used for implementing server side rendering for `css-in-js` libraries.
*/
export default class Document<P = {}> extends Component<DocumentProps & P> { export default class Document<P = {}> extends Component<DocumentProps & P> {
static childContextTypes = { static childContextTypes = {
_documentProps: PropTypes.any, _documentProps: PropTypes.any,
_devOnlyInvalidateCacheQueryString: PropTypes.string, _devOnlyInvalidateCacheQueryString: PropTypes.string,
} }
/**
* `getInitialProps` hook returns the context object with the addition of `renderPage`. `
* `renderPage` callback executes `React` rendering logic synchronously to support server-rendering wrappers
*/
static async getInitialProps({ renderPage }: DocumentContext): Promise<DocumentInitialProps> { static async getInitialProps({ renderPage }: DocumentContext): Promise<DocumentInitialProps> {
const { html, head, dataOnly } = await renderPage() const { html, head, dataOnly } = await renderPage()
const styles = flush() const styles = flush()
......
...@@ -13,6 +13,9 @@ export type ErrorProps = { ...@@ -13,6 +13,9 @@ export type ErrorProps = {
statusCode: number, statusCode: number,
} }
/**
* `Error` component used for handling errors.
*/
export default class Error<P = {}> extends React.Component<P & ErrorProps> { export default class Error<P = {}> extends React.Component<P & ErrorProps> {
static displayName = 'ErrorPage' static displayName = 'ErrorPage'
......
...@@ -9,7 +9,7 @@ import { NextPageContext, NextComponentType } from 'next-server/dist/lib/utils'; ...@@ -9,7 +9,7 @@ import { NextPageContext, NextComponentType } from 'next-server/dist/lib/utils';
declare module 'react' { declare module 'react' {
// <html amp=""> support // <html amp=""> support
interface HtmlHTMLAttributes<T> extends React.HTMLAttributes<T> { interface HtmlHTMLAttributes<T> extends React.HTMLAttributes<T> {
amp?: string; amp?: string
} }
// <link nonce=""> support // <link nonce=""> support
...@@ -19,14 +19,23 @@ declare module 'react' { ...@@ -19,14 +19,23 @@ declare module 'react' {
// <style jsx> and <style jsx global> support for styled-jsx // <style jsx> and <style jsx global> support for styled-jsx
interface StyleHTMLAttributes<T> extends HTMLAttributes<T> { interface StyleHTMLAttributes<T> extends HTMLAttributes<T> {
jsx?: boolean; jsx?: boolean
global?: boolean; global?: boolean
} }
} }
/**
* `Page` type, use it as a guide to create `pages`.
*/
export type NextPage<P = {}> = { export type NextPage<P = {}> = {
(props: P): JSX.Element; (props: P): JSX.Element
getInitialProps?(ctx: NextPageContext): Promise<P>; /**
* Used for initial page load data population. Data returned from `getInitialProps` is serialized when server rendered.
* Make sure to return plain `Object` without using `Date`, `Map`, `Set`.
* @param ctx Context of `page`
*/
getInitialProps?(ctx: NextPageContext): Promise<P>
} }
export { NextPageContext, NextComponentType } export { NextPageContext, NextComponentType }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册