From 677cd21d8674716d050b077a29d5b9659de969be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=A1=E9=91=AB-King?= Date: Mon, 27 Jul 2020 11:22:46 +0800 Subject: [PATCH] feat: renderClient with callback (#4979) --- .../src/renderClient/renderClient.test.tsx | 32 ++++++++- .../src/renderClient/renderClient.tsx | 3 + .../src/renderClient/renderClient.test.tsx | 66 ++++++++++++++++++- .../src/renderClient/renderClient.tsx | 11 ++-- 4 files changed, 105 insertions(+), 7 deletions(-) diff --git a/packages/renderer-mpa/src/renderClient/renderClient.test.tsx b/packages/renderer-mpa/src/renderClient/renderClient.test.tsx index 539c2f0fe9fe..1cb9f1096480 100644 --- a/packages/renderer-mpa/src/renderClient/renderClient.test.tsx +++ b/packages/renderer-mpa/src/renderClient/renderClient.test.tsx @@ -1,7 +1,22 @@ -import React from 'react'; +import { cleanup, render } from '@testing-library/react'; import { Plugin } from '@umijs/runtime'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; import { renderClient } from './renderClient'; -import { render } from '@testing-library/react'; + +let container: HTMLDivElement; +beforeEach(() => { + container = document.createElement('div'); + container.id = 'app'; + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + // @ts-ignore + container = null; + cleanup(); +}); test('normal', () => { const plugin = new Plugin({ @@ -60,6 +75,19 @@ test('normal', () => { path: '/haha', }); }).toThrow(/Render failed, route of path \/haha not found\./); + + let loading = true; + act(() => { + renderClient({ + plugin, + routes, + rootElement: 'app', + callback: () => { + loading = false; + }, + }); + }); + expect(loading).toBeFalsy(); }); test('do not support child routes', () => { diff --git a/packages/renderer-mpa/src/renderClient/renderClient.tsx b/packages/renderer-mpa/src/renderClient/renderClient.tsx index 45a0eaef9aaf..c0a807c09ab5 100644 --- a/packages/renderer-mpa/src/renderClient/renderClient.tsx +++ b/packages/renderer-mpa/src/renderClient/renderClient.tsx @@ -9,6 +9,7 @@ interface IOpts { defaultTitle?: string; rootElement?: string | HTMLElement; path?: string; + callback?: () => void; } function getRootContainer(opts: { routes: any[]; path: string }) { @@ -61,10 +62,12 @@ export function renderClient(opts: IOpts): any { typeof opts.rootElement === 'string' ? document.getElementById(opts.rootElement) : opts.rootElement; + const callback = opts.callback || (() => {}); // @ts-ignore ReactDOM[window.g_useSSR ? 'hydrate' : 'render']( rootContainer, rootElement, + callback, ); } else { return rootContainer; diff --git a/packages/renderer-react/src/renderClient/renderClient.test.tsx b/packages/renderer-react/src/renderClient/renderClient.test.tsx index 03ac4723478f..56de704d5966 100644 --- a/packages/renderer-react/src/renderClient/renderClient.test.tsx +++ b/packages/renderer-react/src/renderClient/renderClient.test.tsx @@ -1,9 +1,21 @@ import React from 'react'; import { render, cleanup, waitFor, getByText } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; import { createMemoryHistory, Plugin, dynamic } from '@umijs/runtime'; import renderClient, { preloadComponent } from './renderClient'; -afterEach(cleanup); +let container; +beforeEach(() => { + container = document.createElement('div'); + container.id = 'app'; + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; + cleanup(); +}); test('normal', async () => { const history = createMemoryHistory({ @@ -49,6 +61,58 @@ test('normal', async () => { expect(routeChanges).toEqual(['POP /foo', 'PUSH /bar']); }); +test('normal with mount', async () => { + const history = createMemoryHistory({ + initialEntries: ['/foo'], + }); + const routeChanges: string[] = []; + const plugin = new Plugin({ + validKeys: ['onRouteChange', 'rootContainer'], + }); + plugin.register({ + apply: { + onRouteChange({ location, action }: any) { + routeChanges.push(`${action} ${location.pathname}`); + }, + rootContainer(container: any, args: any) { + if (!(args.history && args.plugin && args.routes)) { + throw new Error( + 'history, plugin or routes not exists in the args of rootContainer', + ); + } + return
{container}
; + }, + }, + path: '/foo', + }); + let loading = true; + act(() => { + renderClient({ + history, + plugin, + // #app + rootElement: 'app', + routes: [ + { path: '/foo', component: () =>

foo

}, + { path: '/bar', component: () =>

bar

}, + ], + callback: () => { + loading = false; + }, + }); + }); + expect(container.outerHTML).toEqual( + '

foo

', + ); + expect(loading).toBeFalsy(); + + history.push({ + pathname: '/bar', + }); + expect(container.innerHTML).toEqual('

bar

'); + expect(routeChanges).toEqual(['POP /foo', 'PUSH /bar']); +}); + const Common = ({ title }) => { return

{title}

; }; diff --git a/packages/renderer-react/src/renderClient/renderClient.tsx b/packages/renderer-react/src/renderClient/renderClient.tsx index f082d4f0ee45..86ff86a32ea9 100644 --- a/packages/renderer-react/src/renderClient/renderClient.tsx +++ b/packages/renderer-react/src/renderClient/renderClient.tsx @@ -17,6 +17,7 @@ interface IRouterComponentProps { interface IOpts extends IRouterComponentProps { rootElement?: string | HTMLElement; + callback?: () => void; } function RouterComponent(props: IRouterComponentProps) { @@ -111,19 +112,21 @@ export default function renderClient(opts: IOpts) { typeof opts.rootElement === 'string' ? document.getElementById(opts.rootElement) : opts.rootElement; - // flag showing SSR successed + const callback = opts.callback || (() => {}); + + // flag showing SSR successed if (window.g_useSSR) { if (opts.dynamicImport) { // dynamicImport should preload current route component // first loades); preloadComponent(opts.routes).then(function () { - ReactDOM.hydrate(rootContainer, rootElement); + ReactDOM.hydrate(rootContainer, rootElement, callback); }); } else { - ReactDOM.hydrate(rootContainer, rootElement); + ReactDOM.hydrate(rootContainer, rootElement, callback); } } else { - ReactDOM.render(rootContainer, rootElement); + ReactDOM.render(rootContainer, rootElement, callback); } } else { return rootContainer;