@qlover/create-app 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/index.cjs +9 -9
- package/dist/index.js +9 -9
- package/package.json +4 -3
- package/dist/configs/_common/.editorconfig +0 -23
- package/dist/configs/_common/.env.template +0 -13
- package/dist/configs/_common/.gitattributes +0 -2
- package/dist/configs/_common/.github/workflows/general-check.yml +0 -41
- package/dist/configs/_common/.github/workflows/release.yml +0 -81
- package/dist/configs/_common/.gitignore.template +0 -64
- package/dist/configs/_common/.husky/commit-msg +0 -3
- package/dist/configs/_common/.husky/pre-commit +0 -3
- package/dist/configs/_common/.prettierignore +0 -17
- package/dist/configs/_common/.prettierrc.js +0 -7
- package/dist/configs/_common/.vscode/extensions.json +0 -9
- package/dist/configs/_common/.vscode/react.code-snippets +0 -19
- package/dist/configs/_common/.vscode/settings.json +0 -16
- package/dist/configs/_common/commitlint.config.js +0 -10
- package/dist/configs/_common/package.json.template +0 -75
- package/dist/configs/node-lib/eslint.config.js +0 -50
- package/dist/templates/next-app/.env.template +0 -25
- package/dist/templates/next-app/.prettierignore +0 -58
- package/dist/templates/next-app/README.en.md +0 -130
- package/dist/templates/next-app/README.md +0 -130
- package/dist/templates/next-app/config/IOCIdentifier.ts +0 -74
- package/dist/templates/next-app/config/Identifier/api.ts +0 -41
- package/dist/templates/next-app/config/Identifier/common/admint.table.ts +0 -69
- package/dist/templates/next-app/config/Identifier/common/common.ts +0 -90
- package/dist/templates/next-app/config/Identifier/common/index.ts +0 -3
- package/dist/templates/next-app/config/Identifier/common/validators.ts +0 -34
- package/dist/templates/next-app/config/Identifier/index.ts +0 -3
- package/dist/templates/next-app/config/Identifier/pages/index.ts +0 -7
- package/dist/templates/next-app/config/Identifier/pages/page.about.ts +0 -20
- package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +0 -27
- package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +0 -266
- package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +0 -293
- package/dist/templates/next-app/config/Identifier/pages/page.home.ts +0 -56
- package/dist/templates/next-app/config/Identifier/pages/page.login.ts +0 -159
- package/dist/templates/next-app/config/Identifier/pages/page.register.ts +0 -177
- package/dist/templates/next-app/config/adminNavs.ts +0 -19
- package/dist/templates/next-app/config/common.ts +0 -43
- package/dist/templates/next-app/config/cookies.ts +0 -23
- package/dist/templates/next-app/config/i18n/AboutI18n.ts +0 -14
- package/dist/templates/next-app/config/i18n/HomeI18n.ts +0 -24
- package/dist/templates/next-app/config/i18n/PageI18nInterface.ts +0 -51
- package/dist/templates/next-app/config/i18n/admin18n.ts +0 -75
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +0 -16
- package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +0 -36
- package/dist/templates/next-app/config/i18n/index.ts +0 -7
- package/dist/templates/next-app/config/i18n/loginI18n.ts +0 -50
- package/dist/templates/next-app/config/i18n/register18n.ts +0 -44
- package/dist/templates/next-app/config/route.ts +0 -9
- package/dist/templates/next-app/config/theme.ts +0 -28
- package/dist/templates/next-app/docs/en/api.md +0 -387
- package/dist/templates/next-app/docs/en/component.md +0 -544
- package/dist/templates/next-app/docs/en/database.md +0 -496
- package/dist/templates/next-app/docs/en/development-guide.md +0 -727
- package/dist/templates/next-app/docs/en/env.md +0 -563
- package/dist/templates/next-app/docs/en/i18n.md +0 -287
- package/dist/templates/next-app/docs/en/index.md +0 -165
- package/dist/templates/next-app/docs/en/page.md +0 -457
- package/dist/templates/next-app/docs/en/project-structure.md +0 -176
- package/dist/templates/next-app/docs/en/router.md +0 -427
- package/dist/templates/next-app/docs/en/theme.md +0 -532
- package/dist/templates/next-app/docs/en/validator.md +0 -478
- package/dist/templates/next-app/docs/zh/api.md +0 -387
- package/dist/templates/next-app/docs/zh/component.md +0 -544
- package/dist/templates/next-app/docs/zh/database.md +0 -496
- package/dist/templates/next-app/docs/zh/development-guide.md +0 -727
- package/dist/templates/next-app/docs/zh/env.md +0 -563
- package/dist/templates/next-app/docs/zh/i18n.md +0 -287
- package/dist/templates/next-app/docs/zh/index.md +0 -165
- package/dist/templates/next-app/docs/zh/page.md +0 -457
- package/dist/templates/next-app/docs/zh/project-structure.md +0 -176
- package/dist/templates/next-app/docs/zh/router.md +0 -427
- package/dist/templates/next-app/docs/zh/theme.md +0 -532
- package/dist/templates/next-app/docs/zh/validator.md +0 -476
- package/dist/templates/next-app/eslint.config.mjs +0 -285
- package/dist/templates/next-app/make/generateLocales.ts +0 -32
- package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +0 -15
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +0 -38
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +0 -21
- package/dist/templates/next-app/next.config.ts +0 -25
- package/dist/templates/next-app/package.json +0 -87
- package/dist/templates/next-app/postcss.config.mjs +0 -5
- package/dist/templates/next-app/public/favicon.ico +0 -0
- package/dist/templates/next-app/public/file.svg +0 -1
- package/dist/templates/next-app/public/globe.svg +0 -1
- package/dist/templates/next-app/public/locales/en.json +0 -182
- package/dist/templates/next-app/public/locales/zh.json +0 -182
- package/dist/templates/next-app/public/next.svg +0 -1
- package/dist/templates/next-app/public/vercel.svg +0 -1
- package/dist/templates/next-app/public/window.svg +0 -1
- package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +0 -37
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +0 -42
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +0 -153
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +0 -20
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +0 -67
- package/dist/templates/next-app/src/app/[locale]/auth/layout.tsx +0 -18
- package/dist/templates/next-app/src/app/[locale]/auth/login/LoginForm.tsx +0 -126
- package/dist/templates/next-app/src/app/[locale]/auth/login/page.tsx +0 -90
- package/dist/templates/next-app/src/app/[locale]/auth/page.tsx +0 -8
- package/dist/templates/next-app/src/app/[locale]/auth/register/RegisterForm.tsx +0 -197
- package/dist/templates/next-app/src/app/[locale]/auth/register/page.tsx +0 -90
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +0 -63
- package/dist/templates/next-app/src/app/[locale]/not-found.tsx +0 -24
- package/dist/templates/next-app/src/app/[locale]/page.tsx +0 -98
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +0 -13
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +0 -13
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +0 -20
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +0 -13
- package/dist/templates/next-app/src/app/api/admin/users/route.ts +0 -20
- package/dist/templates/next-app/src/app/api/ai/completions/route.ts +0 -32
- package/dist/templates/next-app/src/app/api/auth/callback/route.ts +0 -11
- package/dist/templates/next-app/src/app/api/callback/route.ts +0 -49
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +0 -33
- package/dist/templates/next-app/src/app/api/user/login/route.ts +0 -10
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +0 -8
- package/dist/templates/next-app/src/app/api/user/register/route.ts +0 -11
- package/dist/templates/next-app/src/app/manifest.ts +0 -16
- package/dist/templates/next-app/src/app/robots.txt +0 -2
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +0 -28
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +0 -40
- package/dist/templates/next-app/src/base/cases/Datetime.ts +0 -18
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +0 -57
- package/dist/templates/next-app/src/base/cases/DialogHandler.ts +0 -116
- package/dist/templates/next-app/src/base/cases/InversifyContainer.ts +0 -45
- package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +0 -32
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +0 -77
- package/dist/templates/next-app/src/base/cases/ResourceState.ts +0 -17
- package/dist/templates/next-app/src/base/cases/RouterService.ts +0 -52
- package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +0 -73
- package/dist/templates/next-app/src/base/cases/TranslateI18nUtil.ts +0 -53
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +0 -212
- package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +0 -26
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +0 -36
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +0 -27
- package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +0 -25
- package/dist/templates/next-app/src/base/port/IOCInterface.ts +0 -33
- package/dist/templates/next-app/src/base/port/RouterInterface.ts +0 -11
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +0 -25
- package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +0 -8
- package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +0 -26
- package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +0 -42
- package/dist/templates/next-app/src/base/services/AppApiRequester.ts +0 -67
- package/dist/templates/next-app/src/base/services/AppUserGateway.ts +0 -110
- package/dist/templates/next-app/src/base/services/I18nService.ts +0 -87
- package/dist/templates/next-app/src/base/services/ResourceService.ts +0 -139
- package/dist/templates/next-app/src/base/services/UserService.ts +0 -68
- package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +0 -106
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +0 -87
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +0 -110
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +0 -52
- package/dist/templates/next-app/src/base/types/AppPageRouter.ts +0 -12
- package/dist/templates/next-app/src/base/types/PagesRouter.ts +0 -9
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +0 -76
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +0 -125
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +0 -50
- package/dist/templates/next-app/src/core/bootstraps/IocIdentifierTest.ts +0 -26
- package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +0 -18
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +0 -68
- package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +0 -100
- package/dist/templates/next-app/src/core/globals.ts +0 -28
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +0 -80
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +0 -66
- package/dist/templates/next-app/src/i18n/loadMessages.ts +0 -103
- package/dist/templates/next-app/src/i18n/request.ts +0 -31
- package/dist/templates/next-app/src/i18n/routing.ts +0 -35
- package/dist/templates/next-app/src/lib/supabase/client.ts +0 -8
- package/dist/templates/next-app/src/lib/supabase/conts.ts +0 -2
- package/dist/templates/next-app/src/lib/supabase/proxy.ts +0 -84
- package/dist/templates/next-app/src/lib/supabase/server.ts +0 -38
- package/dist/templates/next-app/src/pages/[locale]/about.tsx +0 -61
- package/dist/templates/next-app/src/pages/_app.tsx +0 -50
- package/dist/templates/next-app/src/pages/_document.tsx +0 -13
- package/dist/templates/next-app/src/proxy.ts +0 -33
- package/dist/templates/next-app/src/server/AppErrorApi.ts +0 -10
- package/dist/templates/next-app/src/server/AppPageRouteParams.ts +0 -110
- package/dist/templates/next-app/src/server/AppSuccessApi.ts +0 -7
- package/dist/templates/next-app/src/server/NextApiServer.ts +0 -61
- package/dist/templates/next-app/src/server/PagesRouteParams.ts +0 -145
- package/dist/templates/next-app/src/server/PasswordEncrypt.ts +0 -18
- package/dist/templates/next-app/src/server/ServerAuth.ts +0 -81
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +0 -262
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +0 -53
- package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +0 -86
- package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +0 -42
- package/dist/templates/next-app/src/server/controllers/LocalesController.ts +0 -36
- package/dist/templates/next-app/src/server/controllers/UserController.ts +0 -91
- package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +0 -8
- package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +0 -21
- package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +0 -11
- package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +0 -5
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +0 -36
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +0 -12
- package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +0 -10
- package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +0 -43
- package/dist/templates/next-app/src/server/port/PaginationInterface.ts +0 -6
- package/dist/templates/next-app/src/server/port/RouteParamsnHandlerInterface.ts +0 -18
- package/dist/templates/next-app/src/server/port/ServerAuthInterface.ts +0 -15
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +0 -23
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +0 -15
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +0 -14
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +0 -23
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +0 -216
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +0 -102
- package/dist/templates/next-app/src/server/services/AIService.ts +0 -45
- package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +0 -21
- package/dist/templates/next-app/src/server/services/AdminLocalesService.ts +0 -20
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +0 -137
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +0 -29
- package/dist/templates/next-app/src/server/services/UserService.ts +0 -134
- package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +0 -6
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +0 -145
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +0 -82
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +0 -70
- package/dist/templates/next-app/src/server/validators/SignupVerifyValidator.ts +0 -68
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -280
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -233
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/index.css +0 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -246
- package/dist/templates/next-app/src/styles/css/antd-themes/index.css +0 -4
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/_default.css +0 -108
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/dark.css +0 -67
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/index.css +0 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/pink.css +0 -67
- package/dist/templates/next-app/src/styles/css/antd-themes/no-context.css +0 -34
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +0 -34
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +0 -31
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/index.css +0 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +0 -36
- package/dist/templates/next-app/src/styles/css/antd-themes/table/_default.css +0 -44
- package/dist/templates/next-app/src/styles/css/antd-themes/table/dark.css +0 -43
- package/dist/templates/next-app/src/styles/css/antd-themes/table/index.css +0 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/table/pink.css +0 -43
- package/dist/templates/next-app/src/styles/css/index.css +0 -6
- package/dist/templates/next-app/src/styles/css/page.css +0 -20
- package/dist/templates/next-app/src/styles/css/scrollbar.css +0 -34
- package/dist/templates/next-app/src/styles/css/tailwind.css +0 -5
- package/dist/templates/next-app/src/styles/css/themes/_default.css +0 -30
- package/dist/templates/next-app/src/styles/css/themes/dark.css +0 -30
- package/dist/templates/next-app/src/styles/css/themes/index.css +0 -3
- package/dist/templates/next-app/src/styles/css/themes/pink.css +0 -30
- package/dist/templates/next-app/src/styles/css/zIndex.css +0 -9
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +0 -150
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +0 -39
- package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +0 -64
- package/dist/templates/next-app/src/uikit/components/ClientSeo.tsx +0 -36
- package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +0 -42
- package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +0 -118
- package/dist/templates/next-app/src/uikit/components/FeatureItem.tsx +0 -13
- package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +0 -34
- package/dist/templates/next-app/src/uikit/components/LocaleLink.tsx +0 -50
- package/dist/templates/next-app/src/uikit/components/With.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +0 -62
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +0 -28
- package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +0 -6
- package/dist/templates/next-app/src/uikit/components-app/AdminButton.tsx +0 -29
- package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +0 -105
- package/dist/templates/next-app/src/uikit/components-app/AuthButton.tsx +0 -20
- package/dist/templates/next-app/src/uikit/components-app/LanguageSwitcher.tsx +0 -75
- package/dist/templates/next-app/src/uikit/components-app/LogoutButton.tsx +0 -45
- package/dist/templates/next-app/src/uikit/components-app/ThemeSwitcher.tsx +0 -144
- package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +0 -98
- package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +0 -93
- package/dist/templates/next-app/src/uikit/context/IOCContext.ts +0 -25
- package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +0 -22
- package/dist/templates/next-app/src/uikit/hook/useIOC.ts +0 -37
- package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +0 -32
- package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +0 -11
- package/dist/templates/next-app/src/uikit/utils/getHashParams.ts +0 -8
- package/dist/templates/next-app/src/uikit/utils/getHashVerifyEmailParams.ts +0 -42
- package/dist/templates/next-app/tailwind.config.ts +0 -8
- package/dist/templates/next-app/tsconfig.json +0 -39
- package/dist/templates/node-lib/__tests__/readJson.test.ts +0 -26
- package/dist/templates/node-lib/bin/test.js +0 -30
- package/dist/templates/node-lib/package.json +0 -66
- package/dist/templates/node-lib/rollup.config.js +0 -79
- package/dist/templates/node-lib/src/index.ts +0 -7
- package/dist/templates/node-lib/src/readJson.ts +0 -12
- package/dist/templates/node-lib/tsconfig.json +0 -23
- package/dist/templates/pack-app/README.md +0 -108
- package/dist/templates/pack-app/eslint.config.js +0 -97
- package/dist/templates/pack-app/fe-config.json +0 -35
- package/dist/templates/pack-app/package.json +0 -86
- package/dist/templates/pack-app/pnpm-workspace.yaml +0 -2
- package/dist/templates/pack-app/tsconfig.json +0 -9
- package/dist/templates/pack-app/tsconfig.test.json +0 -10
- package/dist/templates/pack-app/vite.config.ts +0 -14
- package/dist/templates/react-app/.env.template +0 -22
- package/dist/templates/react-app/.prettierignore +0 -17
- package/dist/templates/react-app/README.en.md +0 -274
- package/dist/templates/react-app/README.md +0 -273
- package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +0 -16
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfig.ts +0 -48
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +0 -17
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +0 -14
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +0 -38
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +0 -53
- package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +0 -46
- package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +0 -12
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +0 -96
- package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +0 -110
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +0 -55
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +0 -74
- package/dist/templates/react-app/__tests__/setup/index.ts +0 -1
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +0 -64
- package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +0 -115
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +0 -288
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +0 -226
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +0 -178
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +0 -181
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +0 -61
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +0 -177
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +0 -191
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +0 -245
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +0 -240
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +0 -242
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +0 -135
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +0 -74
- package/dist/templates/react-app/__tests__/src/main.test.tsx +0 -46
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +0 -97
- package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +0 -274
- package/dist/templates/react-app/config/IOCIdentifier.ts +0 -91
- package/dist/templates/react-app/config/Identifier/common/common.error.ts +0 -34
- package/dist/templates/react-app/config/Identifier/common/common.ts +0 -62
- package/dist/templates/react-app/config/Identifier/common/index.ts +0 -2
- package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +0 -56
- package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +0 -103
- package/dist/templates/react-app/config/Identifier/index.ts +0 -2
- package/dist/templates/react-app/config/Identifier/pages/index.ts +0 -9
- package/dist/templates/react-app/config/Identifier/pages/page.about.ts +0 -189
- package/dist/templates/react-app/config/Identifier/pages/page.executor.ts +0 -275
- package/dist/templates/react-app/config/Identifier/pages/page.home.ts +0 -71
- package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +0 -102
- package/dist/templates/react-app/config/Identifier/pages/page.jsonStorage.ts +0 -77
- package/dist/templates/react-app/config/Identifier/pages/page.login.ts +0 -162
- package/dist/templates/react-app/config/Identifier/pages/page.message.ts +0 -20
- package/dist/templates/react-app/config/Identifier/pages/page.register.ts +0 -159
- package/dist/templates/react-app/config/Identifier/pages/page.request.ts +0 -169
- package/dist/templates/react-app/config/app.router.ts +0 -338
- package/dist/templates/react-app/config/common.ts +0 -85
- package/dist/templates/react-app/config/feapi.mock.json +0 -34
- package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +0 -51
- package/dist/templates/react-app/config/i18n/aboutI18n.ts +0 -42
- package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +0 -17
- package/dist/templates/react-app/config/i18n/executorI18n.ts +0 -51
- package/dist/templates/react-app/config/i18n/homeI18n.ts +0 -24
- package/dist/templates/react-app/config/i18n/i18nConfig.ts +0 -30
- package/dist/templates/react-app/config/i18n/identifiter18n.ts +0 -30
- package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +0 -27
- package/dist/templates/react-app/config/i18n/login18n.ts +0 -42
- package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +0 -22
- package/dist/templates/react-app/config/i18n/messageI18n.ts +0 -14
- package/dist/templates/react-app/config/i18n/notFoundI18n.ts +0 -34
- package/dist/templates/react-app/config/i18n/register18n.ts +0 -40
- package/dist/templates/react-app/config/i18n/request18n.ts +0 -41
- package/dist/templates/react-app/config/theme.ts +0 -21
- package/dist/templates/react-app/docs/en/bootstrap.md +0 -1891
- package/dist/templates/react-app/docs/en/components/chat-message-component.md +0 -320
- package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +0 -283
- package/dist/templates/react-app/docs/en/components/message-base-list-component.md +0 -171
- package/dist/templates/react-app/docs/en/development-guide.md +0 -1199
- package/dist/templates/react-app/docs/en/env.md +0 -1336
- package/dist/templates/react-app/docs/en/global.md +0 -509
- package/dist/templates/react-app/docs/en/i18n.md +0 -979
- package/dist/templates/react-app/docs/en/index.md +0 -802
- package/dist/templates/react-app/docs/en/ioc.md +0 -1365
- package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +0 -321
- package/dist/templates/react-app/docs/en/playwright/index.md +0 -19
- package/dist/templates/react-app/docs/en/playwright/installation-summary.md +0 -332
- package/dist/templates/react-app/docs/en/playwright/overview.md +0 -222
- package/dist/templates/react-app/docs/en/playwright/quickstart.md +0 -325
- package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +0 -340
- package/dist/templates/react-app/docs/en/playwright/setup-complete.md +0 -290
- package/dist/templates/react-app/docs/en/playwright/testing-guide.md +0 -565
- package/dist/templates/react-app/docs/en/request.md +0 -423
- package/dist/templates/react-app/docs/en/router.md +0 -404
- package/dist/templates/react-app/docs/en/store.md +0 -1331
- package/dist/templates/react-app/docs/en/test-guide.md +0 -976
- package/dist/templates/react-app/docs/en/theme.md +0 -424
- package/dist/templates/react-app/docs/en/typescript-guide.md +0 -473
- package/dist/templates/react-app/docs/en/why-no-globals.md +0 -797
- package/dist/templates/react-app/docs/zh/bootstrap.md +0 -1891
- package/dist/templates/react-app/docs/zh/components/chat-message-component.md +0 -320
- package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +0 -283
- package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +0 -171
- package/dist/templates/react-app/docs/zh/development-guide.md +0 -1199
- package/dist/templates/react-app/docs/zh/env.md +0 -1336
- package/dist/templates/react-app/docs/zh/global.md +0 -511
- package/dist/templates/react-app/docs/zh/i18n.md +0 -979
- package/dist/templates/react-app/docs/zh/index.md +0 -786
- package/dist/templates/react-app/docs/zh/ioc.md +0 -1364
- package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +0 -321
- package/dist/templates/react-app/docs/zh/playwright/index.md +0 -19
- package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +0 -332
- package/dist/templates/react-app/docs/zh/playwright/overview.md +0 -222
- package/dist/templates/react-app/docs/zh/playwright/quickstart.md +0 -325
- package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +0 -340
- package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +0 -290
- package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +0 -565
- package/dist/templates/react-app/docs/zh/request.md +0 -427
- package/dist/templates/react-app/docs/zh/router.md +0 -408
- package/dist/templates/react-app/docs/zh/store.md +0 -1329
- package/dist/templates/react-app/docs/zh/test-guide.md +0 -976
- package/dist/templates/react-app/docs/zh/theme.md +0 -424
- package/dist/templates/react-app/docs/zh/typescript-guide.md +0 -473
- package/dist/templates/react-app/docs/zh/why-no-globals.md +0 -797
- package/dist/templates/react-app/e2e/App.spec.ts +0 -319
- package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +0 -40
- package/dist/templates/react-app/e2e/main.spec.ts +0 -20
- package/dist/templates/react-app/e2e/utils/test-helpers.ts +0 -19
- package/dist/templates/react-app/eslint.config.mjs +0 -325
- package/dist/templates/react-app/index.html +0 -13
- package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +0 -26
- package/dist/templates/react-app/package.json +0 -125
- package/dist/templates/react-app/playwright.config.ts +0 -79
- package/dist/templates/react-app/postcss.config.js +0 -5
- package/dist/templates/react-app/public/locales/en/common.json +0 -235
- package/dist/templates/react-app/public/locales/zh/common.json +0 -235
- package/dist/templates/react-app/public/logo.svg +0 -1
- package/dist/templates/react-app/public/router-root/logo.svg +0 -1
- package/dist/templates/react-app/src/App.tsx +0 -35
- package/dist/templates/react-app/src/assets/react.svg +0 -1
- package/dist/templates/react-app/src/base/apis/AiApi.ts +0 -68
- package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +0 -29
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +0 -14
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +0 -86
- package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +0 -21
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +0 -142
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +0 -14
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +0 -86
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +0 -70
- package/dist/templates/react-app/src/base/cases/AppConfig.ts +0 -123
- package/dist/templates/react-app/src/base/cases/DialogHandler.ts +0 -115
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +0 -64
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +0 -45
- package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +0 -23
- package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +0 -55
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +0 -80
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +0 -40
- package/dist/templates/react-app/src/base/cases/ResourceState.ts +0 -23
- package/dist/templates/react-app/src/base/cases/RouterLoader.ts +0 -173
- package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +0 -26
- package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +0 -23
- package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +0 -10
- package/dist/templates/react-app/src/base/port/IOCInterface.ts +0 -38
- package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +0 -21
- package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +0 -23
- package/dist/templates/react-app/src/base/port/RequestStatusInterface.ts +0 -5
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +0 -29
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +0 -20
- package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +0 -61
- package/dist/templates/react-app/src/base/services/I18nService.ts +0 -146
- package/dist/templates/react-app/src/base/services/IdentifierService.ts +0 -162
- package/dist/templates/react-app/src/base/services/RouteService.ts +0 -115
- package/dist/templates/react-app/src/base/services/UserBootstrap.ts +0 -45
- package/dist/templates/react-app/src/base/services/UserService.ts +0 -88
- package/dist/templates/react-app/src/base/types/Page.ts +0 -47
- package/dist/templates/react-app/src/base/types/deprecated-antd.d.ts +0 -60
- package/dist/templates/react-app/src/base/types/global.d.ts +0 -8
- package/dist/templates/react-app/src/core/IOC.ts +0 -28
- package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +0 -108
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +0 -54
- package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +0 -26
- package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +0 -14
- package/dist/templates/react-app/src/core/bootstraps/SaveAppInfo.ts +0 -28
- package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +0 -47
- package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +0 -142
- package/dist/templates/react-app/src/core/globals.ts +0 -47
- package/dist/templates/react-app/src/main.tsx +0 -19
- package/dist/templates/react-app/src/pages/404.tsx +0 -19
- package/dist/templates/react-app/src/pages/500.tsx +0 -18
- package/dist/templates/react-app/src/pages/NoRouteFound.tsx +0 -5
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +0 -27
- package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +0 -166
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +0 -226
- package/dist/templates/react-app/src/pages/base/AboutPage.tsx +0 -123
- package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +0 -467
- package/dist/templates/react-app/src/pages/base/HomePage.tsx +0 -81
- package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +0 -117
- package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +0 -132
- package/dist/templates/react-app/src/pages/base/Layout.tsx +0 -20
- package/dist/templates/react-app/src/pages/base/MessagePage.tsx +0 -71
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +0 -18
- package/dist/templates/react-app/src/pages/base/RequestPage.tsx +0 -193
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/_default.css +0 -280
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/dark.css +0 -233
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +0 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/pink.css +0 -246
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +0 -4
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +0 -108
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +0 -67
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +0 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +0 -67
- package/dist/templates/react-app/src/styles/css/antd-themes/no-context.css +0 -34
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +0 -34
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +0 -31
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +0 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +0 -36
- package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +0 -44
- package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +0 -43
- package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +0 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +0 -43
- package/dist/templates/react-app/src/styles/css/index.css +0 -6
- package/dist/templates/react-app/src/styles/css/page.css +0 -20
- package/dist/templates/react-app/src/styles/css/scrollbar.css +0 -34
- package/dist/templates/react-app/src/styles/css/tailwind.css +0 -5
- package/dist/templates/react-app/src/styles/css/themes/_default.css +0 -30
- package/dist/templates/react-app/src/styles/css/themes/dark.css +0 -30
- package/dist/templates/react-app/src/styles/css/themes/index.css +0 -3
- package/dist/templates/react-app/src/styles/css/themes/pink.css +0 -30
- package/dist/templates/react-app/src/styles/css/zIndex.css +0 -9
- package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +0 -72
- package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +0 -41
- package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +0 -22
- package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +0 -136
- package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +0 -35
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +0 -51
- package/dist/templates/react-app/src/uikit/components/BaseLayoutProvider.tsx +0 -44
- package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +0 -21
- package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +0 -18
- package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +0 -11
- package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +0 -62
- package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +0 -38
- package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +0 -78
- package/dist/templates/react-app/src/uikit/components/Loading.tsx +0 -49
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +0 -43
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +0 -58
- package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +0 -258
- package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +0 -19
- package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +0 -137
- package/dist/templates/react-app/src/uikit/components/With.tsx +0 -17
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +0 -205
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +0 -21
- package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +0 -108
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +0 -282
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +0 -102
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +0 -86
- package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +0 -42
- package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +0 -13
- package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +0 -26
- package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +0 -25
- package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +0 -35
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +0 -21
- package/dist/templates/react-app/src/uikit/hooks/useRouterI18nGuard.ts +0 -25
- package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -31
- package/dist/templates/react-app/src/vite-env.d.ts +0 -1
- package/dist/templates/react-app/tailwind.config.js +0 -4
- package/dist/templates/react-app/tsconfig.app.json +0 -36
- package/dist/templates/react-app/tsconfig.e2e.json +0 -24
- package/dist/templates/react-app/tsconfig.json +0 -22
- package/dist/templates/react-app/tsconfig.node.json +0 -27
- package/dist/templates/react-app/tsconfig.test.json +0 -18
- package/dist/templates/react-app/vite.config.ts +0 -144
|
@@ -1,1365 +0,0 @@
|
|
|
1
|
-
# IOC Container (Dependency Injection)
|
|
2
|
-
|
|
3
|
-
## 📋 Table of Contents
|
|
4
|
-
|
|
5
|
-
- [Core Philosophy](#-core-philosophy) - UI separation, logic independence
|
|
6
|
-
- [What is IOC](#-what-is-ioc) - Inversion of Control
|
|
7
|
-
- [Why Need IOC](#-why-need-ioc) - Core problems it solves
|
|
8
|
-
- [Two Key Questions](#-two-key-questions) - Why need interfaces? Why separate even simple components?
|
|
9
|
-
- [Implementation in the Project](#-implementation-in-the-project) - Bootstrap integration
|
|
10
|
-
- [How to Use](#-how-to-use) - Practical guide
|
|
11
|
-
- [Testing](#-testing) - Independent testing and combination testing
|
|
12
|
-
- [Best Practices](#-best-practices) - 8 core practices
|
|
13
|
-
- [FAQ](#-faq) - Common questions
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 🎯 Core Philosophy
|
|
18
|
-
|
|
19
|
-
> **🚨 Important Principle: UI is UI, logic is logic, they must be separated!**
|
|
20
|
-
|
|
21
|
-
> **⭐ Core Advantage: UI and logic can be tested independently, and also in combination!**
|
|
22
|
-
|
|
23
|
-
### Core Concept
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
┌─────────────────────────────────────────┐
|
|
27
|
-
│ Traditional Approach: UI and Logic Mixed│
|
|
28
|
-
│ │
|
|
29
|
-
│ Component │
|
|
30
|
-
│ ├── UI rendering │
|
|
31
|
-
│ ├── Business logic │
|
|
32
|
-
│ ├── API calls │
|
|
33
|
-
│ ├── State management │
|
|
34
|
-
│ └── Data processing │
|
|
35
|
-
│ │
|
|
36
|
-
│ ❌ Problems: │
|
|
37
|
-
│ - Hard to test (need to render) │
|
|
38
|
-
│ - Logic can't be reused │
|
|
39
|
-
│ - Unclear responsibilities │
|
|
40
|
-
└─────────────────────────────────────────┘
|
|
41
|
-
|
|
42
|
-
┌─────────────────────────────────────────┐
|
|
43
|
-
│ IOC Approach: UI and Logic Completely │
|
|
44
|
-
│ Separated │
|
|
45
|
-
│ │
|
|
46
|
-
│ Component (UI Layer) │
|
|
47
|
-
│ └── Only responsible for rendering │
|
|
48
|
-
│ ↓ Get through IOC │
|
|
49
|
-
│ Service (Logic Layer) │
|
|
50
|
-
│ ├── Business logic │
|
|
51
|
-
│ ├── API calls │
|
|
52
|
-
│ ├── State management │
|
|
53
|
-
│ └── Data processing │
|
|
54
|
-
│ │
|
|
55
|
-
│ ✅ Advantages: │
|
|
56
|
-
│ - UI and logic can be tested separately │
|
|
57
|
-
│ - Logic can be reused │
|
|
58
|
-
│ - Clear responsibilities │
|
|
59
|
-
└─────────────────────────────────────────┘
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 🔄 What is IOC
|
|
65
|
-
|
|
66
|
-
IOC (Inversion of Control) = **Don't create objects yourself, let the container create and manage them**
|
|
67
|
-
|
|
68
|
-
### Traditional Approach vs IOC
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
// ❌ Traditional approach: Create dependencies yourself (tight coupling)
|
|
72
|
-
class UserComponent {
|
|
73
|
-
private userService = new UserService(); // Create yourself
|
|
74
|
-
private storage = new LocalStorage(); // Create yourself
|
|
75
|
-
private api = new UserApi(); // Create yourself
|
|
76
|
-
|
|
77
|
-
async loadUser() {
|
|
78
|
-
return await this.userService.getUser();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Problems:
|
|
83
|
-
// 1. UserComponent depends on concrete implementations
|
|
84
|
-
// 2. Cannot replace UserService implementation
|
|
85
|
-
// 3. Cannot mock UserService in tests
|
|
86
|
-
// 4. Need to manually create UserService dependencies
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// ✅ IOC approach: Container injects dependencies (loose coupling)
|
|
90
|
-
function UserComponent() {
|
|
91
|
-
// Get service from IOC container
|
|
92
|
-
const userService = useIOC('UserServiceInterface'); // Container provides
|
|
93
|
-
|
|
94
|
-
async function loadUser() {
|
|
95
|
-
return await userService.getUser();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// UI only responsible for rendering
|
|
99
|
-
return <div>...</div>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Advantages:
|
|
103
|
-
// 1. UserComponent depends on interface, not implementation
|
|
104
|
-
// 2. Can easily replace UserService implementation
|
|
105
|
-
// 3. Can mock UserService in tests
|
|
106
|
-
// 4. UserService dependencies managed by container
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Analogy
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
Traditional Approach = Cook at home
|
|
113
|
-
- Need to buy groceries (create dependencies)
|
|
114
|
-
- Need to cook (manage lifecycle)
|
|
115
|
-
- Need to wash dishes (clean up resources)
|
|
116
|
-
|
|
117
|
-
IOC Approach = Go to restaurant
|
|
118
|
-
- Order food (tell container what you need)
|
|
119
|
-
- Wait for food (container provides service)
|
|
120
|
-
- Don't need to worry about kitchen (dependency management handled by container)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## 🤔 Why Need IOC
|
|
126
|
-
|
|
127
|
-
### Core Problem: UI and Logic Mixed Together
|
|
128
|
-
|
|
129
|
-
#### ❌ Problem Example: No UI Separation
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// ❌ Traditional component: UI and logic mixed
|
|
133
|
-
function UserProfile() {
|
|
134
|
-
const [user, setUser] = useState(null);
|
|
135
|
-
const [loading, setLoading] = useState(false);
|
|
136
|
-
const [error, setError] = useState(null);
|
|
137
|
-
|
|
138
|
-
// 😰 Business logic mixed in component
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
setLoading(true);
|
|
141
|
-
|
|
142
|
-
// 😰 API call in component
|
|
143
|
-
fetch('/api/user')
|
|
144
|
-
.then(res => res.json())
|
|
145
|
-
.then(data => {
|
|
146
|
-
// 😰 Data processing in component
|
|
147
|
-
const processedData = {
|
|
148
|
-
...data,
|
|
149
|
-
fullName: `${data.firstName} ${data.lastName}`
|
|
150
|
-
};
|
|
151
|
-
setUser(processedData);
|
|
152
|
-
})
|
|
153
|
-
.catch(err => setError(err))
|
|
154
|
-
.finally(() => setLoading(false));
|
|
155
|
-
}, []);
|
|
156
|
-
|
|
157
|
-
// 😰 More business logic
|
|
158
|
-
const handleLogout = () => {
|
|
159
|
-
localStorage.removeItem('token');
|
|
160
|
-
localStorage.removeItem('user');
|
|
161
|
-
window.location.href = '/login';
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// UI rendering
|
|
165
|
-
if (loading) return <div>Loading...</div>;
|
|
166
|
-
if (error) return <div>Error: {error.message}</div>;
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<div>
|
|
170
|
-
<h1>{user?.fullName}</h1>
|
|
171
|
-
<button onClick={handleLogout}>Logout</button>
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 😰😰😰 Problem Summary:
|
|
177
|
-
// 1. UI and logic mixed, hard to maintain
|
|
178
|
-
// 2. Logic can't be reused (what if another component needs user info?)
|
|
179
|
-
// 3. Hard to test (need to render component to test business logic)
|
|
180
|
-
// 4. Unclear responsibilities (component does too much)
|
|
181
|
-
// 5. Can't test logic separately (must test through UI)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
#### ✅ Solution: IOC + UI Separation
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// ✅ Step 1: Define interface (Port)
|
|
188
|
-
export interface UserServiceInterface {
|
|
189
|
-
getUser(): Promise<UserInfo>;
|
|
190
|
-
logout(): Promise<void>;
|
|
191
|
-
isAuthenticated(): boolean;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ✅ Step 2: Implement service (Logic layer)
|
|
195
|
-
@injectable()
|
|
196
|
-
export class UserService implements UserServiceInterface {
|
|
197
|
-
constructor(
|
|
198
|
-
@inject(UserApi) private api: UserApi,
|
|
199
|
-
@inject(IOCIdentifier.AppConfig) private config: AppConfig,
|
|
200
|
-
@inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage,
|
|
201
|
-
@inject(IOCIdentifier.RouteServiceInterface) private router: RouteService
|
|
202
|
-
) {}
|
|
203
|
-
|
|
204
|
-
// Pure logic: Get user info
|
|
205
|
-
async getUser(): Promise<UserInfo> {
|
|
206
|
-
const data = await this.api.getUserInfo();
|
|
207
|
-
|
|
208
|
-
// Data processing
|
|
209
|
-
return {
|
|
210
|
-
...data,
|
|
211
|
-
fullName: `${data.firstName} ${data.lastName}`
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Pure logic: Logout
|
|
216
|
-
async logout(): Promise<void> {
|
|
217
|
-
this.storage.removeItem(this.config.userTokenStorageKey);
|
|
218
|
-
this.storage.removeItem(this.config.userInfoStorageKey);
|
|
219
|
-
await this.router.push('/login');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
isAuthenticated(): boolean {
|
|
223
|
-
return !!this.storage.getItem(this.config.userTokenStorageKey);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ✅ Step 3: UI component (UI layer)
|
|
228
|
-
function UserProfile() {
|
|
229
|
-
// Get service from IOC container
|
|
230
|
-
const userService = useIOC('UserServiceInterface');
|
|
231
|
-
const [user, setUser] = useState(null);
|
|
232
|
-
const [loading, setLoading] = useState(false);
|
|
233
|
-
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
setLoading(true);
|
|
236
|
-
// ✅ UI only calls service, contains no business logic
|
|
237
|
-
userService.getUser()
|
|
238
|
-
.then(setUser)
|
|
239
|
-
.finally(() => setLoading(false));
|
|
240
|
-
}, []);
|
|
241
|
-
|
|
242
|
-
// ✅ UI only responsible for rendering and event binding
|
|
243
|
-
if (loading) return <div>Loading...</div>;
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<div>
|
|
247
|
-
<h1>{user?.fullName}</h1>
|
|
248
|
-
<button onClick={() => userService.logout()}>Logout</button>
|
|
249
|
-
</div>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ✅✅✅ Advantages Summary:
|
|
254
|
-
// 1. UI and logic completely separated, clear responsibilities
|
|
255
|
-
// 2. Logic can be reused (other components can use UserService)
|
|
256
|
-
// 3. Easy to test (can test UserService independently, no need to render UI)
|
|
257
|
-
// 4. Easy to maintain (changing logic doesn't affect UI, changing UI doesn't affect logic)
|
|
258
|
-
// 5. Can test logic separately (no dependency on UI)
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Comparison Summary
|
|
262
|
-
|
|
263
|
-
| Feature | No UI Separation | IOC + UI Separation |
|
|
264
|
-
| ------------------------------- | ---------------------------------- | --------------------------------------------- |
|
|
265
|
-
| **Clarity of Responsibilities** | ❌ UI and logic mixed | ✅ UI only renders, logic independent |
|
|
266
|
-
| **Testability** | ❌ Must render component to test | ✅ Logic can be tested independently |
|
|
267
|
-
| **Reusability** | ❌ Logic can't be reused | ✅ Logic can be reused in multiple components |
|
|
268
|
-
| **Maintainability** | ❌ Changing logic affects UI | ✅ UI and logic modified independently |
|
|
269
|
-
| **Test Speed** | ❌ Slow (need to render UI) | ✅ Fast (pure logic tests) |
|
|
270
|
-
| **Test Complexity** | ❌ High (need to mock many things) | ✅ Low (only need to mock interfaces) |
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## ❓ Two Key Questions
|
|
275
|
-
|
|
276
|
-
### Question 1: Why does an implementation class also need an interface?
|
|
277
|
-
|
|
278
|
-
Many developers ask: "If `UserService` only has one implementation class, why define a `UserServiceInterface` interface?"
|
|
279
|
-
|
|
280
|
-
#### Answer: For testability and flexibility
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
// ❌ No interface: Hard to test
|
|
284
|
-
class UserComponent {
|
|
285
|
-
constructor(
|
|
286
|
-
@inject(UserService) private userService: UserService // Depend on concrete implementation
|
|
287
|
-
) {}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// In tests:
|
|
291
|
-
describe('UserComponent', () => {
|
|
292
|
-
it('should load user', () => {
|
|
293
|
-
// ❌ Problem: Cannot mock UserService
|
|
294
|
-
// UserService has many dependencies (API, Storage, Router, etc.)
|
|
295
|
-
// Need to create all these dependencies to create UserService
|
|
296
|
-
|
|
297
|
-
const userApi = new UserApi(); // Need to create
|
|
298
|
-
const storage = new Storage(); // Need to create
|
|
299
|
-
const router = new Router(); // Need to create
|
|
300
|
-
const config = new AppConfig(); // Need to create
|
|
301
|
-
|
|
302
|
-
const userService = new UserService(userApi, config, storage, router);
|
|
303
|
-
const component = new UserComponent(userService);
|
|
304
|
-
|
|
305
|
-
// 😰 Too complex!
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// ✅ With interface: Easy to test
|
|
310
|
-
class UserComponent {
|
|
311
|
-
constructor(
|
|
312
|
-
@inject('UserServiceInterface') // Depend on interface
|
|
313
|
-
private userService: UserServiceInterface
|
|
314
|
-
) {}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// In tests:
|
|
318
|
-
describe('UserComponent', () => {
|
|
319
|
-
it('should load user', () => {
|
|
320
|
-
// ✅ Only need to mock interface
|
|
321
|
-
const mockUserService: UserServiceInterface = {
|
|
322
|
-
getUser: jest.fn().mockResolvedValue({ name: 'John' }),
|
|
323
|
-
logout: jest.fn(),
|
|
324
|
-
isAuthenticated: jest.fn().mockReturnValue(true)
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const component = new UserComponent(mockUserService);
|
|
328
|
-
|
|
329
|
-
// ✅ Simple and clear!
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Key Advantages:**
|
|
335
|
-
|
|
336
|
-
1. **Simple testing** - Only need to mock interface methods, no need to create real dependencies
|
|
337
|
-
2. **Isolation** - When testing UserComponent, don't need to care about UserService implementation details
|
|
338
|
-
3. **Flexibility** - Can easily replace implementation in future (like adding MockUserService, CacheUserService, etc.)
|
|
339
|
-
4. **Decoupling** - Component only depends on interface, not concrete implementation
|
|
340
|
-
|
|
341
|
-
**Even with only one implementation class, interface is necessary because:**
|
|
342
|
-
|
|
343
|
-
- ✅ Need to mock in tests
|
|
344
|
-
- ✅ May have new implementations in future
|
|
345
|
-
- ✅ Component shouldn't depend on concrete implementation
|
|
346
|
-
- ✅ Interface is contract, implementation is detail
|
|
347
|
-
|
|
348
|
-
### Question 2: Why does a simple UI component also need UI separation?
|
|
349
|
-
|
|
350
|
-
Many developers ask: "My component is simple, just displays a username, why separate?"
|
|
351
|
-
|
|
352
|
-
#### Answer: For testability and future extensibility
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
// ❌ Simple component, not separated
|
|
356
|
-
function UserName() {
|
|
357
|
-
const [name, setName] = useState('');
|
|
358
|
-
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
// 😰 Even if simple, logic is mixed in UI
|
|
361
|
-
fetch('/api/user')
|
|
362
|
-
.then(res => res.json())
|
|
363
|
-
.then(data => setName(data.name));
|
|
364
|
-
}, []);
|
|
365
|
-
|
|
366
|
-
return <span>{name}</span>;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Problems:
|
|
370
|
-
// 1. Cannot test logic (must render component)
|
|
371
|
-
// 2. What if logic becomes complex? (add cache, error handling, etc.)
|
|
372
|
-
// 3. What if other components need username? (copy-paste?)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// ✅ Simple component, but separated
|
|
376
|
-
// 1. Service (Logic layer)
|
|
377
|
-
@injectable()
|
|
378
|
-
export class UserService implements UserServiceInterface {
|
|
379
|
-
constructor(@inject(UserApi) private api: UserApi) {}
|
|
380
|
-
|
|
381
|
-
async getUserName(): Promise<string> {
|
|
382
|
-
const user = await this.api.getUserInfo();
|
|
383
|
-
return user.name;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// 2. UI component (UI layer)
|
|
388
|
-
function UserName() {
|
|
389
|
-
const userService = useIOC('UserServiceInterface');
|
|
390
|
-
const [name, setName] = useState('');
|
|
391
|
-
|
|
392
|
-
useEffect(() => {
|
|
393
|
-
userService.getUserName().then(setName);
|
|
394
|
-
}, []);
|
|
395
|
-
|
|
396
|
-
return <span>{name}</span>;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Advantages:
|
|
400
|
-
// 1. ✅ Can test getUserName logic independently
|
|
401
|
-
// 2. ✅ When logic becomes complex in future, only need to modify UserService
|
|
402
|
-
// 3. ✅ Other components can reuse UserService
|
|
403
|
-
// 4. ✅ UI component stays simple, only responsible for rendering
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
**Key Scenario: Logic Gradually Becomes Complex**
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
// ❌ Not separated: Component becomes bloated when logic gets complex
|
|
410
|
-
function UserName() {
|
|
411
|
-
const [name, setName] = useState('');
|
|
412
|
-
const [loading, setLoading] = useState(false);
|
|
413
|
-
const [error, setError] = useState(null);
|
|
414
|
-
|
|
415
|
-
useEffect(() => {
|
|
416
|
-
setLoading(true);
|
|
417
|
-
|
|
418
|
-
// 😰 Add cache
|
|
419
|
-
const cached = localStorage.getItem('userName');
|
|
420
|
-
if (cached) {
|
|
421
|
-
setName(cached);
|
|
422
|
-
setLoading(false);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// 😰 Add error handling
|
|
427
|
-
fetch('/api/user')
|
|
428
|
-
.then(res => {
|
|
429
|
-
if (!res.ok) throw new Error('Failed');
|
|
430
|
-
return res.json();
|
|
431
|
-
})
|
|
432
|
-
.then(data => {
|
|
433
|
-
setName(data.name);
|
|
434
|
-
localStorage.setItem('userName', data.name);
|
|
435
|
-
})
|
|
436
|
-
.catch(err => setError(err))
|
|
437
|
-
.finally(() => setLoading(false));
|
|
438
|
-
}, []);
|
|
439
|
-
|
|
440
|
-
// 😰 Component becomes complex
|
|
441
|
-
if (loading) return <span>Loading...</span>;
|
|
442
|
-
if (error) return <span>Error</span>;
|
|
443
|
-
return <span>{name}</span>;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// ✅ With separation: When logic becomes complex, only modify service
|
|
448
|
-
@injectable()
|
|
449
|
-
export class UserService implements UserServiceInterface {
|
|
450
|
-
constructor(
|
|
451
|
-
@inject(UserApi) private api: UserApi,
|
|
452
|
-
@inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage
|
|
453
|
-
) {}
|
|
454
|
-
|
|
455
|
-
// ✅ Logic in service, clear and straightforward
|
|
456
|
-
async getUserName(): Promise<string> {
|
|
457
|
-
// Cache logic
|
|
458
|
-
const cached = this.storage.getItem('userName');
|
|
459
|
-
if (cached) return cached;
|
|
460
|
-
|
|
461
|
-
// API call
|
|
462
|
-
const user = await this.api.getUserInfo();
|
|
463
|
-
|
|
464
|
-
// Cache
|
|
465
|
-
this.storage.setItem('userName', user.name);
|
|
466
|
-
|
|
467
|
-
return user.name;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// ✅ UI component stays simple
|
|
472
|
-
function UserName() {
|
|
473
|
-
const userService = useIOC('UserServiceInterface');
|
|
474
|
-
const [name, setName] = useState('');
|
|
475
|
-
const [loading, setLoading] = useState(false);
|
|
476
|
-
|
|
477
|
-
useEffect(() => {
|
|
478
|
-
setLoading(true);
|
|
479
|
-
userService.getUserName()
|
|
480
|
-
.then(setName)
|
|
481
|
-
.finally(() => setLoading(false));
|
|
482
|
-
}, []);
|
|
483
|
-
|
|
484
|
-
if (loading) return <span>Loading...</span>;
|
|
485
|
-
return <span>{name}</span>;
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
**Summary: Even if component is simple, still separate because:**
|
|
490
|
-
|
|
491
|
-
- ✅ **Simple now doesn't mean simple later** - Requirements change
|
|
492
|
-
- ✅ **Logic can be reused** - Other components may need it
|
|
493
|
-
- ✅ **Easy to test** - Logic can be tested independently
|
|
494
|
-
- ✅ **Clear responsibilities** - UI only renders, logic independent
|
|
495
|
-
- ✅ **Easy to maintain** - Changing logic doesn't affect UI
|
|
496
|
-
|
|
497
|
-
---
|
|
498
|
-
|
|
499
|
-
## 🛠️ Implementation in the Project
|
|
500
|
-
|
|
501
|
-
### 1. File Structure
|
|
502
|
-
|
|
503
|
-
```
|
|
504
|
-
src/
|
|
505
|
-
├── base/
|
|
506
|
-
│ ├── port/ # Interface definition layer
|
|
507
|
-
│ │ ├── UserServiceInterface.ts
|
|
508
|
-
│ │ ├── I18nServiceInterface.ts
|
|
509
|
-
│ │ └── RouteServiceInterface.ts
|
|
510
|
-
│ └── services/ # Service implementation layer
|
|
511
|
-
│ ├── UserService.ts
|
|
512
|
-
│ ├── I18nService.ts
|
|
513
|
-
│ └── RouteService.ts
|
|
514
|
-
├── core/
|
|
515
|
-
│ ├── clientIoc/
|
|
516
|
-
│ │ ├── ClientIOC.ts # IOC container
|
|
517
|
-
│ │ └── ClientIOCRegister.ts # Registrar
|
|
518
|
-
│ └── globals.ts # Global instances
|
|
519
|
-
├── uikit/
|
|
520
|
-
│ ├── hooks/
|
|
521
|
-
│ │ └── useIOC.ts # React Hook
|
|
522
|
-
│ └── contexts/
|
|
523
|
-
│ └── IOCContext.tsx # React Context
|
|
524
|
-
└── config/
|
|
525
|
-
└── IOCIdentifier.ts # Identifier definitions
|
|
526
|
-
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### 2. IOC Identifier Definition
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
// config/IOCIdentifier.ts
|
|
533
|
-
export interface IOCIdentifierMap {
|
|
534
|
-
AppConfig: AppConfig;
|
|
535
|
-
Logger: LoggerInterface;
|
|
536
|
-
LocalStorageEncrypt: SyncStorageInterface<string, string>;
|
|
537
|
-
UserServiceInterface: UserServiceInterface;
|
|
538
|
-
I18nServiceInterface: I18nServiceInterface;
|
|
539
|
-
RouteServiceInterface: RouteServiceInterface;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
export const IOCIdentifier = {
|
|
543
|
-
AppConfig: 'AppConfig',
|
|
544
|
-
Logger: 'Logger',
|
|
545
|
-
LocalStorageEncrypt: 'LocalStorageEncrypt',
|
|
546
|
-
UserServiceInterface: 'UserServiceInterface',
|
|
547
|
-
I18nServiceInterface: 'I18nServiceInterface',
|
|
548
|
-
RouteServiceInterface: 'RouteServiceInterface'
|
|
549
|
-
} as const;
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### 3. Service Registration
|
|
553
|
-
|
|
554
|
-
```typescript
|
|
555
|
-
// src/core/clientIoc/ClientIOCRegister.ts
|
|
556
|
-
export class ClientIOCRegister implements IOCRegisterInterface {
|
|
557
|
-
constructor(protected options: IocRegisterOptions) {}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Register global services
|
|
561
|
-
*/
|
|
562
|
-
protected registerGlobals(ioc: IOCContainerInterface): void {
|
|
563
|
-
const { appConfig } = this.options;
|
|
564
|
-
const { dialogHandler, localStorageEncrypt, JSON, logger } = globals;
|
|
565
|
-
|
|
566
|
-
// ✅ Register global instances
|
|
567
|
-
ioc.bind(IOCIdentifier.JSONSerializer, JSON);
|
|
568
|
-
ioc.bind(IOCIdentifier.Logger, logger);
|
|
569
|
-
ioc.bind(IOCIdentifier.AppConfig, appConfig);
|
|
570
|
-
ioc.bind(IOCIdentifier.LocalStorageEncrypt, localStorageEncrypt);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Register business services
|
|
575
|
-
*/
|
|
576
|
-
protected registerImplement(ioc: IOCContainerInterface): void {
|
|
577
|
-
// ✅ Register service implementations
|
|
578
|
-
ioc.bind(
|
|
579
|
-
IOCIdentifier.I18nServiceInterface,
|
|
580
|
-
new I18nService(this.options.pathname)
|
|
581
|
-
);
|
|
582
|
-
|
|
583
|
-
ioc.bind(IOCIdentifier.RouteServiceInterface, new RouteService(/* ... */));
|
|
584
|
-
|
|
585
|
-
// ✅ Service can depend on other services
|
|
586
|
-
ioc.bind(IOCIdentifier.UserServiceInterface, ioc.get(UserService));
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Registration entry point
|
|
591
|
-
*/
|
|
592
|
-
register(ioc: IOCContainerInterface): void {
|
|
593
|
-
this.registerGlobals(ioc);
|
|
594
|
-
this.registerImplement(ioc);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### 4. Create IOC Container
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
// src/core/clientIoc/ClientIOC.ts
|
|
603
|
-
import { createIOCFunction } from '@qlover/corekit-bridge';
|
|
604
|
-
import { InversifyContainer } from '@/base/cases/InversifyContainer';
|
|
605
|
-
import { ClientIOCRegister } from './ClientIOCRegister';
|
|
606
|
-
|
|
607
|
-
export const clientIOC = {
|
|
608
|
-
create(options: IocRegisterOptions) {
|
|
609
|
-
// Create container
|
|
610
|
-
const container = new InversifyContainer();
|
|
611
|
-
|
|
612
|
-
// Create IOC function
|
|
613
|
-
const IOC = createIOCFunction(container);
|
|
614
|
-
|
|
615
|
-
// Register services
|
|
616
|
-
const register = new ClientIOCRegister(options);
|
|
617
|
-
register.register(container, IOC);
|
|
618
|
-
|
|
619
|
-
return IOC;
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### 5. Initialize in Bootstrap
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
// src/core/bootstraps/BootstrapClient.ts
|
|
628
|
-
export class BootstrapClient {
|
|
629
|
-
static async main(args: BootstrapClientArgs) {
|
|
630
|
-
const { root, bootHref, ioc } = args;
|
|
631
|
-
|
|
632
|
-
// ✅ Create IOC container
|
|
633
|
-
const IOC = ioc.create({
|
|
634
|
-
pathname: bootHref,
|
|
635
|
-
appConfig: appConfig
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
// Use IOC in Bootstrap
|
|
639
|
-
const bootstrap = new Bootstrap({
|
|
640
|
-
root,
|
|
641
|
-
logger,
|
|
642
|
-
ioc: {
|
|
643
|
-
manager: IOC,
|
|
644
|
-
register: iocRegister
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
await bootstrap.initialize();
|
|
649
|
-
await bootstrap.start();
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
---
|
|
655
|
-
|
|
656
|
-
## 📝 How to Use
|
|
657
|
-
|
|
658
|
-
### 1. Define Interface (Port)
|
|
659
|
-
|
|
660
|
-
```typescript
|
|
661
|
-
// src/base/port/UserServiceInterface.ts
|
|
662
|
-
export interface UserServiceInterface {
|
|
663
|
-
getUser(): Promise<UserInfo>;
|
|
664
|
-
login(username: string, password: string): Promise<void>;
|
|
665
|
-
logout(): Promise<void>;
|
|
666
|
-
isAuthenticated(): boolean;
|
|
667
|
-
}
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### 2. Implement Service
|
|
671
|
-
|
|
672
|
-
```typescript
|
|
673
|
-
// src/base/services/UserService.ts
|
|
674
|
-
import { injectable, inject } from 'inversify';
|
|
675
|
-
|
|
676
|
-
@injectable()
|
|
677
|
-
export class UserService implements UserServiceInterface {
|
|
678
|
-
constructor(
|
|
679
|
-
@inject(UserApi) private api: UserApi,
|
|
680
|
-
@inject(IOCIdentifier.AppConfig) private config: AppConfig,
|
|
681
|
-
@inject(IOCIdentifier.LocalStorageEncrypt) private storage: Storage,
|
|
682
|
-
@inject(IOCIdentifier.RouteServiceInterface) private router: RouteService
|
|
683
|
-
) {}
|
|
684
|
-
|
|
685
|
-
async getUser(): Promise<UserInfo> {
|
|
686
|
-
const token = this.storage.getItem(this.config.userTokenStorageKey);
|
|
687
|
-
if (!token) throw new Error('No token');
|
|
688
|
-
|
|
689
|
-
return await this.api.getUserInfo(token);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
async login(username: string, password: string): Promise<void> {
|
|
693
|
-
const response = await this.api.login({ username, password });
|
|
694
|
-
this.storage.setItem(this.config.userTokenStorageKey, response.token);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
async logout(): Promise<void> {
|
|
698
|
-
this.storage.removeItem(this.config.userTokenStorageKey);
|
|
699
|
-
await this.router.push('/login');
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
isAuthenticated(): boolean {
|
|
703
|
-
return !!this.storage.getItem(this.config.userTokenStorageKey);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
### 3. Use in UI Components
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
// src/pages/UserProfile.tsx
|
|
712
|
-
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
713
|
-
|
|
714
|
-
function UserProfile() {
|
|
715
|
-
// ✅ Get service from IOC container
|
|
716
|
-
const userService = useIOC('UserServiceInterface');
|
|
717
|
-
const [user, setUser] = useState<UserInfo | null>(null);
|
|
718
|
-
|
|
719
|
-
useEffect(() => {
|
|
720
|
-
userService.getUser().then(setUser);
|
|
721
|
-
}, []);
|
|
722
|
-
|
|
723
|
-
const handleLogout = () => {
|
|
724
|
-
userService.logout();
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// ✅ UI only responsible for rendering
|
|
728
|
-
return (
|
|
729
|
-
<div>
|
|
730
|
-
<h1>{user?.name}</h1>
|
|
731
|
-
<button onClick={handleLogout}>Logout</button>
|
|
732
|
-
</div>
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### 4. Use Other Services in Services
|
|
738
|
-
|
|
739
|
-
```typescript
|
|
740
|
-
// src/base/services/ProfileService.ts
|
|
741
|
-
@injectable()
|
|
742
|
-
export class ProfileService {
|
|
743
|
-
constructor(
|
|
744
|
-
// ✅ Service can depend on other services
|
|
745
|
-
@inject(IOCIdentifier.UserServiceInterface)
|
|
746
|
-
private userService: UserServiceInterface,
|
|
747
|
-
@inject(IOCIdentifier.I18nServiceInterface)
|
|
748
|
-
private i18n: I18nServiceInterface
|
|
749
|
-
) {}
|
|
750
|
-
|
|
751
|
-
async getUserProfile(): Promise<string> {
|
|
752
|
-
const user = await this.userService.getUser();
|
|
753
|
-
return this.i18n.t('profile.welcome', { name: user.name });
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
---
|
|
759
|
-
|
|
760
|
-
## 🧪 Testing
|
|
761
|
-
|
|
762
|
-
### Core Advantage: UI and logic can be tested independently, and also in combination
|
|
763
|
-
|
|
764
|
-
#### 1. Test Logic Independently (No UI needed)
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
// __tests__/src/base/services/UserService.test.ts
|
|
768
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
769
|
-
import { UserService } from '@/base/services/UserService';
|
|
770
|
-
|
|
771
|
-
describe('UserService (Logic Test)', () => {
|
|
772
|
-
let userService: UserService;
|
|
773
|
-
let mockApi: any;
|
|
774
|
-
let mockStorage: any;
|
|
775
|
-
let mockRouter: any;
|
|
776
|
-
let mockConfig: any;
|
|
777
|
-
|
|
778
|
-
beforeEach(() => {
|
|
779
|
-
// ✅ Only need to mock interfaces
|
|
780
|
-
mockApi = {
|
|
781
|
-
getUserInfo: vi.fn(),
|
|
782
|
-
login: vi.fn()
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
mockStorage = {
|
|
786
|
-
getItem: vi.fn(),
|
|
787
|
-
setItem: vi.fn(),
|
|
788
|
-
removeItem: vi.fn()
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
mockRouter = {
|
|
792
|
-
push: vi.fn()
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
mockConfig = {
|
|
796
|
-
userTokenStorageKey: '__test_token__'
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
// ✅ Create service
|
|
800
|
-
userService = new UserService(mockApi, mockConfig, mockStorage, mockRouter);
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
it('should get user when token exists', async () => {
|
|
804
|
-
// ✅ Set mock return value
|
|
805
|
-
mockStorage.getItem.mockReturnValue('test-token');
|
|
806
|
-
mockApi.getUserInfo.mockResolvedValue({ name: 'John' });
|
|
807
|
-
|
|
808
|
-
// ✅ Test logic
|
|
809
|
-
const user = await userService.getUser();
|
|
810
|
-
|
|
811
|
-
// ✅ Verify result
|
|
812
|
-
expect(user.name).toBe('John');
|
|
813
|
-
expect(mockStorage.getItem).toHaveBeenCalledWith('__test_token__');
|
|
814
|
-
expect(mockApi.getUserInfo).toHaveBeenCalledWith('test-token');
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
it('should throw error when no token', async () => {
|
|
818
|
-
// ✅ Test error scenario
|
|
819
|
-
mockStorage.getItem.mockReturnValue(null);
|
|
820
|
-
|
|
821
|
-
await expect(userService.getUser()).rejects.toThrow('No token');
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
it('should login and save token', async () => {
|
|
825
|
-
// ✅ Test login logic
|
|
826
|
-
mockApi.login.mockResolvedValue({ token: 'new-token' });
|
|
827
|
-
|
|
828
|
-
await userService.login('user', 'pass');
|
|
829
|
-
|
|
830
|
-
expect(mockApi.login).toHaveBeenCalledWith({
|
|
831
|
-
username: 'user',
|
|
832
|
-
password: 'pass'
|
|
833
|
-
});
|
|
834
|
-
expect(mockStorage.setItem).toHaveBeenCalledWith(
|
|
835
|
-
'__test_token__',
|
|
836
|
-
'new-token'
|
|
837
|
-
);
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
it('should logout and clear token', async () => {
|
|
841
|
-
// ✅ Test logout logic
|
|
842
|
-
await userService.logout();
|
|
843
|
-
|
|
844
|
-
expect(mockStorage.removeItem).toHaveBeenCalledWith('__test_token__');
|
|
845
|
-
expect(mockRouter.push).toHaveBeenCalledWith('/login');
|
|
846
|
-
});
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
// ✅✅✅ Advantages:
|
|
850
|
-
// 1. Don't need to render UI
|
|
851
|
-
// 2. Tests run fast (pure logic)
|
|
852
|
-
// 3. Easy to mock (only need to mock interfaces)
|
|
853
|
-
// 4. Can test all edge cases
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
#### 2. Test UI Independently (No real logic needed)
|
|
857
|
-
|
|
858
|
-
```typescript
|
|
859
|
-
// __tests__/src/pages/UserProfile.test.tsx
|
|
860
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
861
|
-
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
862
|
-
import { UserProfile } from '@/pages/UserProfile';
|
|
863
|
-
import { IOCProvider } from '@/uikit/contexts/IOCContext';
|
|
864
|
-
|
|
865
|
-
describe('UserProfile (UI Test)', () => {
|
|
866
|
-
it('should display user name', async () => {
|
|
867
|
-
// ✅ Mock service
|
|
868
|
-
const mockUserService = {
|
|
869
|
-
getUser: vi.fn().mockResolvedValue({ name: 'John Doe' }),
|
|
870
|
-
logout: vi.fn(),
|
|
871
|
-
isAuthenticated: vi.fn().mockReturnValue(true)
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
const mockIOC = (identifier: string) => {
|
|
875
|
-
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
876
|
-
};
|
|
877
|
-
|
|
878
|
-
// ✅ Render component
|
|
879
|
-
render(
|
|
880
|
-
<IOCProvider value={mockIOC}>
|
|
881
|
-
<UserProfile />
|
|
882
|
-
</IOCProvider>
|
|
883
|
-
);
|
|
884
|
-
|
|
885
|
-
// ✅ Verify UI
|
|
886
|
-
await waitFor(() => {
|
|
887
|
-
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
888
|
-
});
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
it('should call logout when button clicked', async () => {
|
|
892
|
-
const mockUserService = {
|
|
893
|
-
getUser: vi.fn().mockResolvedValue({ name: 'John' }),
|
|
894
|
-
logout: vi.fn(),
|
|
895
|
-
isAuthenticated: vi.fn().mockReturnValue(true)
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
const mockIOC = (identifier: string) => {
|
|
899
|
-
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
render(
|
|
903
|
-
<IOCProvider value={mockIOC}>
|
|
904
|
-
<UserProfile />
|
|
905
|
-
</IOCProvider>
|
|
906
|
-
);
|
|
907
|
-
|
|
908
|
-
// ✅ Simulate user action
|
|
909
|
-
const logoutButton = screen.getByText('Logout');
|
|
910
|
-
fireEvent.click(logoutButton);
|
|
911
|
-
|
|
912
|
-
// ✅ Verify service call
|
|
913
|
-
expect(mockUserService.logout).toHaveBeenCalled();
|
|
914
|
-
});
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// ✅✅✅ Advantages:
|
|
918
|
-
// 1. Don't need real service implementation
|
|
919
|
-
// 2. Can easily simulate various scenarios
|
|
920
|
-
// 3. UI tests focus on UI logic
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
#### 3. Combination Testing (UI + Logic)
|
|
924
|
-
|
|
925
|
-
```typescript
|
|
926
|
-
// __tests__/src/integration/UserFlow.test.tsx
|
|
927
|
-
import { describe, it, expect } from 'vitest';
|
|
928
|
-
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
929
|
-
import { UserProfile } from '@/pages/UserProfile';
|
|
930
|
-
import { UserService } from '@/base/services/UserService';
|
|
931
|
-
import { IOCProvider } from '@/uikit/contexts/IOCContext';
|
|
932
|
-
|
|
933
|
-
describe('User Flow (Integration Test)', () => {
|
|
934
|
-
it('should complete user login flow', async () => {
|
|
935
|
-
// ✅ Use real service implementation
|
|
936
|
-
const mockApi = {
|
|
937
|
-
getUserInfo: vi.fn().mockResolvedValue({ name: 'John' }),
|
|
938
|
-
login: vi.fn().mockResolvedValue({ token: 'test-token' })
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
const mockStorage = {
|
|
942
|
-
getItem: vi.fn(),
|
|
943
|
-
setItem: vi.fn(),
|
|
944
|
-
removeItem: vi.fn()
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
const mockRouter = { push: vi.fn() };
|
|
948
|
-
const mockConfig = { userTokenStorageKey: '__token__' };
|
|
949
|
-
|
|
950
|
-
// ✅ Create real service
|
|
951
|
-
const userService = new UserService(
|
|
952
|
-
mockApi,
|
|
953
|
-
mockConfig,
|
|
954
|
-
mockStorage,
|
|
955
|
-
mockRouter
|
|
956
|
-
);
|
|
957
|
-
|
|
958
|
-
const mockIOC = (identifier: string) => {
|
|
959
|
-
if (identifier === 'UserServiceInterface') return userService;
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
// ✅ Render real UI
|
|
963
|
-
render(
|
|
964
|
-
<IOCProvider value={mockIOC}>
|
|
965
|
-
<UserProfile />
|
|
966
|
-
</IOCProvider>
|
|
967
|
-
);
|
|
968
|
-
|
|
969
|
-
// ✅ Test complete flow
|
|
970
|
-
await waitFor(() => {
|
|
971
|
-
expect(screen.getByText('John')).toBeInTheDocument();
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
// ✅ Click logout
|
|
975
|
-
fireEvent.click(screen.getByText('Logout'));
|
|
976
|
-
|
|
977
|
-
// ✅ Verify entire flow
|
|
978
|
-
expect(mockStorage.removeItem).toHaveBeenCalledWith('__token__');
|
|
979
|
-
expect(mockRouter.push).toHaveBeenCalledWith('/login');
|
|
980
|
-
});
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
// ✅✅✅ Advantages:
|
|
984
|
-
// 1. Test real user flow
|
|
985
|
-
// 2. Can discover UI and logic integration issues
|
|
986
|
-
// 3. Closer to real usage scenarios
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
### Testing Strategy Summary
|
|
990
|
-
|
|
991
|
-
```
|
|
992
|
-
┌─────────────────────────────────────────┐
|
|
993
|
-
│ Testing Pyramid │
|
|
994
|
-
│ │
|
|
995
|
-
│ △ UI Tests (Few) │
|
|
996
|
-
│ ╱ ╲ │
|
|
997
|
-
│ ╱ ╲ │
|
|
998
|
-
│ ╱ ╲ │
|
|
999
|
-
│ ╱───────╲ Integration Tests (Some) │
|
|
1000
|
-
│ ╱ ╲ │
|
|
1001
|
-
│╱═══════════╲ Logic Tests (Many) │
|
|
1002
|
-
│ │
|
|
1003
|
-
│ Logic Tests: Fast, stable, comprehensive│
|
|
1004
|
-
│ Integration Tests: Verify integration │
|
|
1005
|
-
│ UI Tests: Verify user interactions │
|
|
1006
|
-
└─────────────────────────────────────────┘
|
|
1007
|
-
```
|
|
1008
|
-
|
|
1009
|
-
**Recommended Test Ratio:**
|
|
1010
|
-
|
|
1011
|
-
- 70% Logic tests (UserService.test.ts)
|
|
1012
|
-
- 20% Integration tests (UserFlow.test.tsx)
|
|
1013
|
-
- 10% UI tests (UserProfile.test.tsx)
|
|
1014
|
-
|
|
1015
|
-
---
|
|
1016
|
-
|
|
1017
|
-
## 💎 Best Practices
|
|
1018
|
-
|
|
1019
|
-
### 1. ✅ Always Define Interface
|
|
1020
|
-
|
|
1021
|
-
```typescript
|
|
1022
|
-
// ✅ Good practice: Define interface first
|
|
1023
|
-
export interface UserServiceInterface {
|
|
1024
|
-
getUser(): Promise<UserInfo>;
|
|
1025
|
-
logout(): Promise<void>;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// Then implement
|
|
1029
|
-
@injectable()
|
|
1030
|
-
export class UserService implements UserServiceInterface {
|
|
1031
|
-
// ...
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
// ❌ Bad practice: Write implementation directly
|
|
1035
|
-
@injectable()
|
|
1036
|
-
export class UserService {
|
|
1037
|
-
// No interface, hard to test
|
|
1038
|
-
}
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
### 2. ✅ Complete UI and Logic Separation
|
|
1042
|
-
|
|
1043
|
-
```typescript
|
|
1044
|
-
// ✅ Good practice: UI only responsible for rendering
|
|
1045
|
-
function UserProfile() {
|
|
1046
|
-
const userService = useIOC('UserServiceInterface');
|
|
1047
|
-
const [user, setUser] = useState(null);
|
|
1048
|
-
|
|
1049
|
-
useEffect(() => {
|
|
1050
|
-
userService.getUser().then(setUser);
|
|
1051
|
-
}, []);
|
|
1052
|
-
|
|
1053
|
-
return <div>{user?.name}</div>;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// ❌ Bad practice: Logic mixed in UI
|
|
1057
|
-
function UserProfile() {
|
|
1058
|
-
const [user, setUser] = useState(null);
|
|
1059
|
-
|
|
1060
|
-
useEffect(() => {
|
|
1061
|
-
fetch('/api/user')
|
|
1062
|
-
.then(res => res.json())
|
|
1063
|
-
.then(setUser);
|
|
1064
|
-
}, []);
|
|
1065
|
-
|
|
1066
|
-
return <div>{user?.name}</div>;
|
|
1067
|
-
}
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
### 3. ✅ Use Dependency Injection
|
|
1071
|
-
|
|
1072
|
-
```typescript
|
|
1073
|
-
// ✅ Good practice: Inject through constructor
|
|
1074
|
-
@injectable()
|
|
1075
|
-
export class UserService {
|
|
1076
|
-
constructor(
|
|
1077
|
-
@inject(UserApi) private api: UserApi,
|
|
1078
|
-
@inject(IOCIdentifier.AppConfig) private config: AppConfig
|
|
1079
|
-
) {}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// ❌ Bad practice: Create dependencies directly
|
|
1083
|
-
export class UserService {
|
|
1084
|
-
private api = new UserApi();
|
|
1085
|
-
private config = new AppConfig();
|
|
1086
|
-
}
|
|
1087
|
-
```
|
|
1088
|
-
|
|
1089
|
-
### 4. ✅ Single Responsibility for Services
|
|
1090
|
-
|
|
1091
|
-
```typescript
|
|
1092
|
-
// ✅ Good practice: Each service responsible for one thing
|
|
1093
|
-
@injectable()
|
|
1094
|
-
export class UserService {
|
|
1095
|
-
// Only responsible for user-related logic
|
|
1096
|
-
async getUser() {
|
|
1097
|
-
/* ... */
|
|
1098
|
-
}
|
|
1099
|
-
async logout() {
|
|
1100
|
-
/* ... */
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
@injectable()
|
|
1105
|
-
export class ThemeService {
|
|
1106
|
-
// Only responsible for theme-related logic
|
|
1107
|
-
setTheme() {
|
|
1108
|
-
/* ... */
|
|
1109
|
-
}
|
|
1110
|
-
getTheme() {
|
|
1111
|
-
/* ... */
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// ❌ Bad practice: One service does multiple things
|
|
1116
|
-
@injectable()
|
|
1117
|
-
export class ApplicationService {
|
|
1118
|
-
async getUser() {
|
|
1119
|
-
/* ... */
|
|
1120
|
-
}
|
|
1121
|
-
setTheme() {
|
|
1122
|
-
/* ... */
|
|
1123
|
-
}
|
|
1124
|
-
changeLanguage() {
|
|
1125
|
-
/* ... */
|
|
1126
|
-
}
|
|
1127
|
-
// Too many responsibilities!
|
|
1128
|
-
}
|
|
1129
|
-
```
|
|
1130
|
-
|
|
1131
|
-
### 5. ✅ Depend on Interfaces, Not Implementations
|
|
1132
|
-
|
|
1133
|
-
```typescript
|
|
1134
|
-
// ✅ Good practice
|
|
1135
|
-
@injectable()
|
|
1136
|
-
export class UserService {
|
|
1137
|
-
constructor(
|
|
1138
|
-
@inject('UserApiInterface') private api: UserApiInterface // Interface
|
|
1139
|
-
) {}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// ❌ Bad practice
|
|
1143
|
-
@injectable()
|
|
1144
|
-
export class UserService {
|
|
1145
|
-
constructor(
|
|
1146
|
-
@inject(UserApi) private api: UserApi // Concrete implementation
|
|
1147
|
-
) {}
|
|
1148
|
-
}
|
|
1149
|
-
```
|
|
1150
|
-
|
|
1151
|
-
### 6. ✅ Separate Even If Simple
|
|
1152
|
-
|
|
1153
|
-
```typescript
|
|
1154
|
-
// ✅ Good practice: Separate even if simple
|
|
1155
|
-
@injectable()
|
|
1156
|
-
export class CounterService {
|
|
1157
|
-
private count = 0;
|
|
1158
|
-
|
|
1159
|
-
increment() {
|
|
1160
|
-
this.count++;
|
|
1161
|
-
return this.count;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
function Counter() {
|
|
1166
|
-
const counterService = useIOC('CounterService');
|
|
1167
|
-
const [count, setCount] = useState(0);
|
|
1168
|
-
|
|
1169
|
-
const handleClick = () => {
|
|
1170
|
-
setCount(counterService.increment());
|
|
1171
|
-
};
|
|
1172
|
-
|
|
1173
|
-
return <button onClick={handleClick}>{count}</button>;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// ❌ Bad practice: Simple logic also mixed in UI
|
|
1177
|
-
function Counter() {
|
|
1178
|
-
const [count, setCount] = useState(0);
|
|
1179
|
-
|
|
1180
|
-
return (
|
|
1181
|
-
<button onClick={() => setCount(count + 1)}>
|
|
1182
|
-
{count}
|
|
1183
|
-
</button>
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
### 7. ✅ Write Comprehensive Tests
|
|
1189
|
-
|
|
1190
|
-
```typescript
|
|
1191
|
-
// ✅ Good practice: Logic tests + UI tests + Integration tests
|
|
1192
|
-
describe('UserService (Logic)', () => {
|
|
1193
|
-
it('should get user', async () => {
|
|
1194
|
-
/* ... */
|
|
1195
|
-
});
|
|
1196
|
-
it('should handle error', async () => {
|
|
1197
|
-
/* ... */
|
|
1198
|
-
});
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
describe('UserProfile (UI)', () => {
|
|
1202
|
-
it('should display user', async () => {
|
|
1203
|
-
/* ... */
|
|
1204
|
-
});
|
|
1205
|
-
});
|
|
1206
|
-
|
|
1207
|
-
describe('User Flow (Integration)', () => {
|
|
1208
|
-
it('should complete flow', async () => {
|
|
1209
|
-
/* ... */
|
|
1210
|
-
});
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
// ❌ Bad practice: Only UI tests
|
|
1214
|
-
describe('UserProfile', () => {
|
|
1215
|
-
it('should work', async () => {
|
|
1216
|
-
// Only test UI, logic not tested
|
|
1217
|
-
});
|
|
1218
|
-
});
|
|
1219
|
-
```
|
|
1220
|
-
|
|
1221
|
-
### 8. ✅ Use Type-safe Identifiers
|
|
1222
|
-
|
|
1223
|
-
```typescript
|
|
1224
|
-
// ✅ Good practice: Type-safe identifiers
|
|
1225
|
-
const userService = useIOC('UserServiceInterface');
|
|
1226
|
-
// TypeScript knows userService type
|
|
1227
|
-
|
|
1228
|
-
// ❌ Bad practice: String literals
|
|
1229
|
-
const userService = useIOC('UserService');
|
|
1230
|
-
// Easy to misspell, no type checking
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
---
|
|
1234
|
-
|
|
1235
|
-
## ❓ FAQ
|
|
1236
|
-
|
|
1237
|
-
### Q1: Does IOC increase complexity?
|
|
1238
|
-
|
|
1239
|
-
**A:** Short-term maybe, but long-term it greatly reduces complexity:
|
|
1240
|
-
|
|
1241
|
-
**Short-term (small projects):**
|
|
1242
|
-
|
|
1243
|
-
- Need to define interfaces
|
|
1244
|
-
- Need to register services
|
|
1245
|
-
- Need to learn IOC concepts
|
|
1246
|
-
|
|
1247
|
-
**Long-term (project grows):**
|
|
1248
|
-
|
|
1249
|
-
- ✅ Easy to test (save lots of testing time)
|
|
1250
|
-
- ✅ Easy to maintain (clear dependency relationships)
|
|
1251
|
-
- ✅ Easy to extend (adding new features is simple)
|
|
1252
|
-
- ✅ Team collaboration (clear responsibilities)
|
|
1253
|
-
|
|
1254
|
-
### Q2: Do all components need IOC?
|
|
1255
|
-
|
|
1256
|
-
**A:** Not necessarily, but recommended:
|
|
1257
|
-
|
|
1258
|
-
**Scenarios that need IOC:**
|
|
1259
|
-
|
|
1260
|
-
- ✅ Components with business logic
|
|
1261
|
-
- ✅ Components that call APIs
|
|
1262
|
-
- ✅ Components that access Storage
|
|
1263
|
-
- ✅ Components that need testing
|
|
1264
|
-
|
|
1265
|
-
**Scenarios that can skip IOC:**
|
|
1266
|
-
|
|
1267
|
-
- Pure presentational components (only receive props)
|
|
1268
|
-
- Very simple UI components (like Button, Icon)
|
|
1269
|
-
|
|
1270
|
-
### Q3: Why not directly import service?
|
|
1271
|
-
|
|
1272
|
-
**A:**
|
|
1273
|
-
|
|
1274
|
-
```typescript
|
|
1275
|
-
// ❌ Direct import
|
|
1276
|
-
import { userService } from '@/services/UserService';
|
|
1277
|
-
|
|
1278
|
-
function UserProfile() {
|
|
1279
|
-
// Problems:
|
|
1280
|
-
// 1. userService is singleton, can't replace in tests
|
|
1281
|
-
// 2. userService dependencies created at module load time
|
|
1282
|
-
// 3. Hard to mock
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// ✅ Use IOC
|
|
1286
|
-
function UserProfile() {
|
|
1287
|
-
const userService = useIOC('UserServiceInterface');
|
|
1288
|
-
|
|
1289
|
-
// Advantages:
|
|
1290
|
-
// 1. Can provide mock implementation in tests
|
|
1291
|
-
// 2. Dependencies managed by container, created on demand
|
|
1292
|
-
// 3. Easy to mock
|
|
1293
|
-
}
|
|
1294
|
-
```
|
|
1295
|
-
|
|
1296
|
-
### Q4: How to test components using IOC?
|
|
1297
|
-
|
|
1298
|
-
**A:** Provide mock IOC:
|
|
1299
|
-
|
|
1300
|
-
```typescript
|
|
1301
|
-
const mockIOC = (identifier: string) => {
|
|
1302
|
-
if (identifier === 'UserServiceInterface') {
|
|
1303
|
-
return mockUserService;
|
|
1304
|
-
}
|
|
1305
|
-
// ... other services
|
|
1306
|
-
};
|
|
1307
|
-
|
|
1308
|
-
render(
|
|
1309
|
-
<IOCProvider value={mockIOC}>
|
|
1310
|
-
<UserProfile />
|
|
1311
|
-
</IOCProvider>
|
|
1312
|
-
);
|
|
1313
|
-
```
|
|
1314
|
-
|
|
1315
|
-
### Q5: What's the difference between IOC and Context?
|
|
1316
|
-
|
|
1317
|
-
**A:**
|
|
1318
|
-
|
|
1319
|
-
| Feature | React Context | IOC Container |
|
|
1320
|
-
| ------------------------- | ------------------------- | --------------------- |
|
|
1321
|
-
| **Scope** | React component tree | Global |
|
|
1322
|
-
| **Dependency Management** | ❌ None | ✅ Yes |
|
|
1323
|
-
| **Lifecycle** | Component lifecycle | Application lifecycle |
|
|
1324
|
-
| **Testing** | ⚠️ Need Provider | ✅ Easy to mock |
|
|
1325
|
-
| **Type Safety** | ⚠️ Need manual definition | ✅ Auto-inference |
|
|
1326
|
-
|
|
1327
|
-
**Recommendation:**
|
|
1328
|
-
|
|
1329
|
-
- Use IOC to manage services (logic)
|
|
1330
|
-
- Use Context to manage UI state
|
|
1331
|
-
|
|
1332
|
-
---
|
|
1333
|
-
|
|
1334
|
-
## 📚 Related Documentation
|
|
1335
|
-
|
|
1336
|
-
- [Project Architecture Design](./index.md) - Understand overall architecture
|
|
1337
|
-
- [Bootstrap Initializer](./bootstrap.md) - IOC in Bootstrap application
|
|
1338
|
-
- [Environment Variable Management](./env.md) - AppConfig injection
|
|
1339
|
-
- [Store State Management](./store.md) - How application layer notifies UI layer (IOC + Store)
|
|
1340
|
-
- [Testing Guide](./test-guide.md) - Detailed testing strategies
|
|
1341
|
-
|
|
1342
|
-
---
|
|
1343
|
-
|
|
1344
|
-
## 🎉 Summary
|
|
1345
|
-
|
|
1346
|
-
Core value of IOC container:
|
|
1347
|
-
|
|
1348
|
-
1. **UI Separation** 🎨 - UI is UI, logic is logic
|
|
1349
|
-
2. **Testability** 🧪 - Logic can be tested independently, UI can be tested independently, and also in combination
|
|
1350
|
-
3. **Interfaces Required** 🔌 - Even with only one implementation, still need interface (for testing)
|
|
1351
|
-
4. **Complete Separation** 🏗️ - Even simple components, still separate (for future)
|
|
1352
|
-
5. **Dependency Management** 📦 - Container uniformly manages all dependencies
|
|
1353
|
-
6. **Decoupling** 🔗 - Components don't depend on concrete implementations
|
|
1354
|
-
7. **Easy to Maintain** 🛠️ - Clear dependency relationships
|
|
1355
|
-
8. **Easy to Extend** 🚀 - Easy to add new features
|
|
1356
|
-
|
|
1357
|
-
**Remember two core principles:**
|
|
1358
|
-
|
|
1359
|
-
1. **UI is UI, logic is logic, they must be separated!**
|
|
1360
|
-
2. **Even with only one implementation, still need interface; even if component is simple, still separate!**
|
|
1361
|
-
|
|
1362
|
-
---
|
|
1363
|
-
|
|
1364
|
-
**Feedback:**
|
|
1365
|
-
If you have any questions or suggestions about the IOC container, please discuss in the team channel or submit an Issue.
|