@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,1331 +0,0 @@
|
|
|
1
|
-
# Store State Management
|
|
2
|
-
|
|
3
|
-
## 📋 Table of Contents
|
|
4
|
-
|
|
5
|
-
- [Core Philosophy](#-core-philosophy) - Application layer notifies UI layer
|
|
6
|
-
- [What is Store](#-what-is-store) - State container
|
|
7
|
-
- [Why Need Store](#-why-need-store) - Solve communication problem
|
|
8
|
-
- [Core Problem](#-core-problem) - How application layer notifies UI layer
|
|
9
|
-
- [Implementation in the Project](#-implementation-in-the-project) - Practical guide
|
|
10
|
-
- [How to Use](#-how-to-use) - Service + Store + useStore
|
|
11
|
-
- [Testing](#-testing) - Independent testing and combination testing
|
|
12
|
-
- [Best Practices](#-best-practices) - 7 core practices
|
|
13
|
-
- [FAQ](#-faq) - Common questions
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 🎯 Core Philosophy
|
|
18
|
-
|
|
19
|
-
> **🚨 Core Problem: How does the application layer (Service) notify the UI layer to update while maintaining separation?**
|
|
20
|
-
|
|
21
|
-
> **⭐ Solution: Service contains Store, publishes state through `emit`, UI subscribes to state through `useStore`!**
|
|
22
|
-
|
|
23
|
-
### Core Concept
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
┌──────────────────────────────────────────────┐
|
|
27
|
-
│ Problem: UI and logic are separated, │
|
|
28
|
-
│ but how do they communicate? │
|
|
29
|
-
│ │
|
|
30
|
-
│ Service (Application Layer) │
|
|
31
|
-
│ ├── Business logic │
|
|
32
|
-
│ └── Data processing │
|
|
33
|
-
│ ↓ How to notify? │
|
|
34
|
-
│ Component (UI Layer) │
|
|
35
|
-
│ └── UI rendering │
|
|
36
|
-
│ │
|
|
37
|
-
│ ❌ Problem: Service changed data, how does │
|
|
38
|
-
│ UI know? │
|
|
39
|
-
└──────────────────────────────────────────────┘
|
|
40
|
-
|
|
41
|
-
┌──────────────────────────────────────────────┐
|
|
42
|
-
│ Solution: Store as bridge │
|
|
43
|
-
│ │
|
|
44
|
-
│ Service (Application Layer) │
|
|
45
|
-
│ ├── Business logic │
|
|
46
|
-
│ ├── Store (State container) │
|
|
47
|
-
│ │ ├── state │
|
|
48
|
-
│ │ └── emit() (Publish state) │
|
|
49
|
-
│ │ │
|
|
50
|
-
│ │ ↓ Publish-Subscribe pattern │
|
|
51
|
-
│ │ │
|
|
52
|
-
│ └── useStore (Subscribe) │
|
|
53
|
-
│ ↓ │
|
|
54
|
-
│ Component (UI Layer) │
|
|
55
|
-
│ └── Auto-update UI │
|
|
56
|
-
│ │
|
|
57
|
-
│ ✅ Service publishes state via emit │
|
|
58
|
-
│ ✅ UI subscribes to state via useStore │
|
|
59
|
-
│ ✅ Maintain separation, decoupled │
|
|
60
|
-
└──────────────────────────────────────────────┘
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## 🗂️ What is Store
|
|
66
|
-
|
|
67
|
-
Store is a **reactive state container** based on the **publish-subscribe pattern**.
|
|
68
|
-
|
|
69
|
-
### Simple Understanding
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
Store = State + Publish-Subscribe
|
|
73
|
-
|
|
74
|
-
Service owns Store
|
|
75
|
-
Service publishes state through Store.emit()
|
|
76
|
-
UI subscribes to state through useStore()
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Analogy
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
Store is like a radio station:
|
|
83
|
-
|
|
84
|
-
📻 Station (Store)
|
|
85
|
-
- Has program content (state)
|
|
86
|
-
- Can broadcast programs (emit)
|
|
87
|
-
- Listeners can tune in (subscribe)
|
|
88
|
-
|
|
89
|
-
🎤 Host (Service)
|
|
90
|
-
- Creates program content (business logic)
|
|
91
|
-
- Broadcasts via station (emit)
|
|
92
|
-
|
|
93
|
-
📱 Listener (UI Component)
|
|
94
|
-
- Tunes into station (useStore)
|
|
95
|
-
- Automatically reacts to new content (auto-update UI)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## 🤔 Why Need Store
|
|
101
|
-
|
|
102
|
-
### Core Problem: How to communicate after UI and logic separation?
|
|
103
|
-
|
|
104
|
-
We've already separated UI and logic through IOC, but here's the problem:
|
|
105
|
-
|
|
106
|
-
#### ❌ Problem Example: Without Store
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// Service (Logic layer)
|
|
110
|
-
@injectable()
|
|
111
|
-
export class UserService {
|
|
112
|
-
private user: UserInfo | null = null;
|
|
113
|
-
|
|
114
|
-
async login(username: string, password: string) {
|
|
115
|
-
const response = await this.api.login({ username, password });
|
|
116
|
-
this.user = response.user; // ✅ Login successful, user updated
|
|
117
|
-
|
|
118
|
-
// ❌ Problem: How does UI know user has been updated?
|
|
119
|
-
// ❌ Service cannot notify UI
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// UI component
|
|
124
|
-
function UserProfile() {
|
|
125
|
-
const userService = useIOC('UserServiceInterface');
|
|
126
|
-
|
|
127
|
-
// ❌ Problem: How to get userService.user?
|
|
128
|
-
// ❌ How to trigger UI re-render when userService.user updates?
|
|
129
|
-
|
|
130
|
-
return <div>{/* Cannot display user */}</div>;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 😰😰😰 Problem Summary:
|
|
134
|
-
// 1. UI cannot access Service's internal state
|
|
135
|
-
// 2. UI doesn't know when Service state updates
|
|
136
|
-
// 3. Need to manually call a method to get state? (breaks separation principle)
|
|
137
|
-
// 4. Need to poll for state? (poor performance)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
#### ✅ Solution: Use Store
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
// Service (Logic layer)
|
|
144
|
-
@injectable()
|
|
145
|
-
export class UserService extends StoreInterface<UserState> {
|
|
146
|
-
constructor() {
|
|
147
|
-
super(() => ({
|
|
148
|
-
user: null,
|
|
149
|
-
loading: false
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async login(username: string, password: string) {
|
|
154
|
-
// Set loading state
|
|
155
|
-
this.emit({ ...this.state, loading: true });
|
|
156
|
-
|
|
157
|
-
const response = await this.api.login({ username, password });
|
|
158
|
-
|
|
159
|
-
// ✅ Publish new state via emit, automatically notify all subscribers
|
|
160
|
-
this.emit({
|
|
161
|
-
user: response.user,
|
|
162
|
-
loading: false
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// UI component
|
|
168
|
-
function UserProfile() {
|
|
169
|
-
const userService = useIOC('UserServiceInterface');
|
|
170
|
-
|
|
171
|
-
// ✅ Subscribe to state via useStore
|
|
172
|
-
const { user, loading } = useStore(userService);
|
|
173
|
-
|
|
174
|
-
// ✅ Component automatically re-renders when userService.emit()
|
|
175
|
-
|
|
176
|
-
if (loading) return <div>Loading...</div>;
|
|
177
|
-
return <div>{user?.name}</div>;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ✅✅✅ Advantages Summary:
|
|
181
|
-
// 1. UI can subscribe to Service state
|
|
182
|
-
// 2. UI automatically updates when Service state updates
|
|
183
|
-
// 3. Maintain separation (Service doesn't know which UIs are listening)
|
|
184
|
-
// 4. High performance (only subscribed components update)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Comparison Summary
|
|
188
|
-
|
|
189
|
-
| Feature | Without Store | With Store |
|
|
190
|
-
| ----------------------- | -------------------------------------- | ------------------------------ |
|
|
191
|
-
| **State Access** | ❌ Cannot access internal state | ✅ Subscribe via useStore |
|
|
192
|
-
| **Update Notification** | ❌ UI doesn't know about state changes | ✅ emit automatically notifies |
|
|
193
|
-
| **UI Update** | ❌ Need manual trigger | ✅ Auto re-render |
|
|
194
|
-
| **Decoupling** | ❌ Service needs to know UI | ✅ Completely decoupled |
|
|
195
|
-
| **Performance** | ❌ Polling or global update | ✅ Precise subscriber updates |
|
|
196
|
-
| **Testability** | ❌ Hard to test state changes | ✅ Easy to test state |
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## ❓ Core Problem
|
|
201
|
-
|
|
202
|
-
### How does application layer notify UI layer while maintaining separation?
|
|
203
|
-
|
|
204
|
-
#### Problem Breakdown
|
|
205
|
-
|
|
206
|
-
1. **Application layer (Service) has state** - like user info, loading state
|
|
207
|
-
2. **UI layer needs to display this state** - show username, show loading animation
|
|
208
|
-
3. **Application layer state changes** - after login success, user info updates
|
|
209
|
-
4. **UI layer needs to auto-update** - after user info changes, UI automatically shows new name
|
|
210
|
-
5. **Maintain separation** - Service shouldn't directly manipulate UI, UI shouldn't directly access Service internals
|
|
211
|
-
|
|
212
|
-
#### Solution: Publish-Subscribe Pattern
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
// 1. Service defines state
|
|
216
|
-
interface UserState {
|
|
217
|
-
user: UserInfo | null;
|
|
218
|
-
loading: boolean;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 2. Service extends StoreInterface
|
|
222
|
-
@injectable()
|
|
223
|
-
export class UserService extends StoreInterface<UserState> {
|
|
224
|
-
constructor() {
|
|
225
|
-
super(() => ({
|
|
226
|
-
user: null,
|
|
227
|
-
loading: false
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 3. Service publishes state via emit
|
|
232
|
-
async login(username: string, password: string) {
|
|
233
|
-
this.emit({ ...this.state, loading: true }); // Publish: start loading
|
|
234
|
-
|
|
235
|
-
const response = await this.api.login({ username, password });
|
|
236
|
-
|
|
237
|
-
this.emit({
|
|
238
|
-
user: response.user,
|
|
239
|
-
loading: false
|
|
240
|
-
}); // Publish: loading complete, user logged in
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 4. Service doesn't need to know who's listening
|
|
244
|
-
// ✅ Completely decoupled
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// 5. UI subscribes to state via useStore
|
|
248
|
-
function LoginPage() {
|
|
249
|
-
const userService = useIOC('UserServiceInterface');
|
|
250
|
-
const { loading } = useStore(userService);
|
|
251
|
-
|
|
252
|
-
const handleLogin = () => {
|
|
253
|
-
userService.login('user', 'pass');
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// 6. When Service emits new state, UI auto-updates
|
|
257
|
-
return (
|
|
258
|
-
<button onClick={handleLogin} disabled={loading}>
|
|
259
|
-
{loading ? 'Logging in...' : 'Login'}
|
|
260
|
-
</button>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
#### Workflow
|
|
266
|
-
|
|
267
|
-
```
|
|
268
|
-
┌─────────────────────────────────────────────┐
|
|
269
|
-
│ Complete state update flow │
|
|
270
|
-
│ │
|
|
271
|
-
│ 1. User clicks button │
|
|
272
|
-
│ ↓ │
|
|
273
|
-
│ 2. UI calls Service method │
|
|
274
|
-
│ userService.login() │
|
|
275
|
-
│ ↓ │
|
|
276
|
-
│ 3. Service executes business logic │
|
|
277
|
-
│ - Call API │
|
|
278
|
-
│ - Process data │
|
|
279
|
-
│ ↓ │
|
|
280
|
-
│ 4. Service publishes new state via emit │
|
|
281
|
-
│ this.emit({ user: ..., loading: false })│
|
|
282
|
-
│ ↓ │
|
|
283
|
-
│ 5. Store notifies all subscribers │
|
|
284
|
-
│ listeners.forEach(listener => ...) │
|
|
285
|
-
│ ↓ │
|
|
286
|
-
│ 6. useStore receives notification │
|
|
287
|
-
│ Trigger component re-render │
|
|
288
|
-
│ ↓ │
|
|
289
|
-
│ 7. UI displays latest state │
|
|
290
|
-
│ Show username / Hide loading animation │
|
|
291
|
-
└─────────────────────────────────────────────┘
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## 🛠️ Implementation in the Project
|
|
297
|
-
|
|
298
|
-
### 1. File Structure
|
|
299
|
-
|
|
300
|
-
```
|
|
301
|
-
src/
|
|
302
|
-
├── base/
|
|
303
|
-
│ ├── services/
|
|
304
|
-
│ │ ├── UserService.ts # Service extends StoreInterface
|
|
305
|
-
│ │ ├── RouteService.ts # Service extends StoreInterface
|
|
306
|
-
│ │ └── I18nService.ts # Service extends StoreInterface
|
|
307
|
-
│ └── port/
|
|
308
|
-
│ └── UserServiceInterface.ts # Service interface
|
|
309
|
-
└── uikit/
|
|
310
|
-
└── hooks/
|
|
311
|
-
└── useStore.ts (from @brain-toolkit/react-kit)
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### 2. Store Base Class
|
|
315
|
-
|
|
316
|
-
Store system is based on `SliceStore` from `@brain-toolkit/react-kit`:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
// From @brain-toolkit/react-kit
|
|
320
|
-
export class SliceStore<T> {
|
|
321
|
-
protected state: T;
|
|
322
|
-
private listeners = new Set<(state: T) => void>();
|
|
323
|
-
|
|
324
|
-
constructor(stateFactory: () => T) {
|
|
325
|
-
this.state = stateFactory();
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Publish state
|
|
329
|
-
protected emit(newState: T) {
|
|
330
|
-
this.state = newState;
|
|
331
|
-
// Notify all subscribers
|
|
332
|
-
this.listeners.forEach((listener) => listener(this.state));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Subscribe to state
|
|
336
|
-
subscribe(listener: (state: T) => void) {
|
|
337
|
-
this.listeners.add(listener);
|
|
338
|
-
// Return unsubscribe function
|
|
339
|
-
return () => this.listeners.delete(listener);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Get current state
|
|
343
|
-
getState(): T {
|
|
344
|
-
return this.state;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### 3. StoreInterface Base Class
|
|
350
|
-
|
|
351
|
-
Project's Store base class, provides additional utility methods:
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
// From @qlover/corekit-bridge
|
|
355
|
-
export abstract class StoreInterface<
|
|
356
|
-
T extends StoreStateInterface
|
|
357
|
-
> extends SliceStore<T> {
|
|
358
|
-
constructor(protected stateFactory: () => T) {
|
|
359
|
-
super(stateFactory);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Reset state
|
|
363
|
-
resetState(): void {
|
|
364
|
-
this.emit(this.stateFactory());
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Clone state (for updates)
|
|
368
|
-
cloneState(source?: Partial<T>): T {
|
|
369
|
-
const cloned = clone(this.state);
|
|
370
|
-
if (typeof cloned === 'object' && cloned !== null) {
|
|
371
|
-
Object.assign(cloned, source);
|
|
372
|
-
}
|
|
373
|
-
return cloned;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
### 4. State Interface
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
// All state must implement this interface
|
|
382
|
-
export interface StoreStateInterface {
|
|
383
|
-
// Can define common properties here
|
|
384
|
-
// loading?: boolean;
|
|
385
|
-
// error?: Error | null;
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## 📝 How to Use
|
|
392
|
-
|
|
393
|
-
### 1. Define State Interface
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
// src/base/services/UserService.ts
|
|
397
|
-
export interface UserState extends StoreStateInterface {
|
|
398
|
-
user: UserInfo | null;
|
|
399
|
-
loading: boolean;
|
|
400
|
-
error: Error | null;
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### 2. Service Extends StoreInterface
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
// src/base/services/UserService.ts
|
|
408
|
-
import { StoreInterface } from '@qlover/corekit-bridge';
|
|
409
|
-
import { injectable, inject } from 'inversify';
|
|
410
|
-
|
|
411
|
-
@injectable()
|
|
412
|
-
export class UserService extends StoreInterface<UserState> {
|
|
413
|
-
constructor(
|
|
414
|
-
@inject(UserApi) private api: UserApi,
|
|
415
|
-
@inject(IOCIdentifier.AppConfig) private config: AppConfig
|
|
416
|
-
) {
|
|
417
|
-
// Initialize state
|
|
418
|
-
super(() => ({
|
|
419
|
-
user: null,
|
|
420
|
-
loading: false,
|
|
421
|
-
error: null
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Business method: publish state via emit
|
|
426
|
-
async login(username: string, password: string) {
|
|
427
|
-
// 1. Start loading
|
|
428
|
-
this.emit({
|
|
429
|
-
...this.state,
|
|
430
|
-
loading: true,
|
|
431
|
-
error: null
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
try {
|
|
435
|
-
// 2. Call API
|
|
436
|
-
const response = await this.api.login({ username, password });
|
|
437
|
-
|
|
438
|
-
// 3. Success: publish new state
|
|
439
|
-
this.emit({
|
|
440
|
-
user: response.user,
|
|
441
|
-
loading: false,
|
|
442
|
-
error: null
|
|
443
|
-
});
|
|
444
|
-
} catch (error) {
|
|
445
|
-
// 4. Failure: publish error state
|
|
446
|
-
this.emit({
|
|
447
|
-
...this.state,
|
|
448
|
-
loading: false,
|
|
449
|
-
error: error as Error
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
async logout() {
|
|
455
|
-
this.emit({
|
|
456
|
-
user: null,
|
|
457
|
-
loading: false,
|
|
458
|
-
error: null
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Use cloneState to simplify updates
|
|
463
|
-
setUser(user: UserInfo) {
|
|
464
|
-
this.emit(this.cloneState({ user }));
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### 3. UI Subscribes to State
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
// src/pages/LoginPage.tsx
|
|
473
|
-
import { useStore } from '@brain-toolkit/react-kit/hooks/useStore';
|
|
474
|
-
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
475
|
-
|
|
476
|
-
function LoginPage() {
|
|
477
|
-
const userService = useIOC('UserServiceInterface');
|
|
478
|
-
|
|
479
|
-
// ✅ Method 1: Subscribe to complete state
|
|
480
|
-
const { user, loading, error } = useStore(userService);
|
|
481
|
-
|
|
482
|
-
const handleLogin = async () => {
|
|
483
|
-
await userService.login('username', 'password');
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
if (loading) {
|
|
487
|
-
return <div>Loading...</div>;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return (
|
|
491
|
-
<div>
|
|
492
|
-
{error && <div>Error: {error.message}</div>}
|
|
493
|
-
<button onClick={handleLogin}>Login</button>
|
|
494
|
-
</div>
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### 4. Use Selectors (Performance Optimization)
|
|
500
|
-
|
|
501
|
-
```typescript
|
|
502
|
-
// src/pages/UserProfile.tsx
|
|
503
|
-
function UserProfile() {
|
|
504
|
-
const userService = useIOC('UserServiceInterface');
|
|
505
|
-
|
|
506
|
-
// ✅ Method 2: Only subscribe to needed state (better performance)
|
|
507
|
-
const user = useStore(userService, (state) => state.user);
|
|
508
|
-
|
|
509
|
-
// ✅ Only re-renders when user changes, loading changes won't trigger
|
|
510
|
-
|
|
511
|
-
return <div>{user?.name}</div>;
|
|
512
|
-
}
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
### 5. Define Selectors (Recommended)
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
// src/base/services/UserService.ts
|
|
519
|
-
@injectable()
|
|
520
|
-
export class UserService extends StoreInterface<UserState> {
|
|
521
|
-
// ... other code
|
|
522
|
-
|
|
523
|
-
// ✅ Define selectors
|
|
524
|
-
selector = {
|
|
525
|
-
user: (state: UserState) => state.user,
|
|
526
|
-
loading: (state: UserState) => state.loading,
|
|
527
|
-
error: (state: UserState) => state.error,
|
|
528
|
-
isLoggedIn: (state: UserState) => state.user !== null
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Usage
|
|
533
|
-
function UserProfile() {
|
|
534
|
-
const userService = useIOC('UserServiceInterface');
|
|
535
|
-
|
|
536
|
-
// ✅ Use predefined selectors
|
|
537
|
-
const user = useStore(userService, userService.selector.user);
|
|
538
|
-
const isLoggedIn = useStore(userService, userService.selector.isLoggedIn);
|
|
539
|
-
|
|
540
|
-
return <div>{isLoggedIn ? user?.name : 'Please login'}</div>;
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### 6. Real Project Examples
|
|
545
|
-
|
|
546
|
-
#### Example 1: UserService
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
// src/base/services/UserService.ts
|
|
550
|
-
@injectable()
|
|
551
|
-
export class UserService extends UserAuthServiceInterface {
|
|
552
|
-
constructor(
|
|
553
|
-
@inject(UserApi) userApi: UserApi,
|
|
554
|
-
@inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
|
|
555
|
-
@inject(IOCIdentifier.LocalStorageEncrypt) storage: Storage
|
|
556
|
-
) {
|
|
557
|
-
super(userApi, {
|
|
558
|
-
userStorage: {
|
|
559
|
-
key: appConfig.userInfoStorageKey,
|
|
560
|
-
storage: storage
|
|
561
|
-
},
|
|
562
|
-
credentialStorage: {
|
|
563
|
-
key: appConfig.userTokenStorageKey,
|
|
564
|
-
storage: storage
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// ✅ UserService's base class contains store
|
|
570
|
-
override get store(): UserAuthStore<UserApiState> {
|
|
571
|
-
return super.store as UserAuthStore<UserApiState>;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
override async logout(): Promise<void> {
|
|
575
|
-
await super.logout();
|
|
576
|
-
// ✅ store automatically notifies UI
|
|
577
|
-
this.routerService.gotoLogin();
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Usage
|
|
582
|
-
function Layout() {
|
|
583
|
-
const userService = useIOC(IOCIdentifier.UserServiceInterface);
|
|
584
|
-
|
|
585
|
-
// ✅ Subscribe to userService.store
|
|
586
|
-
useStore(userService.store);
|
|
587
|
-
|
|
588
|
-
if (userService.isAuthenticated()) {
|
|
589
|
-
return <Navigate to="/" replace />;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return <Outlet />;
|
|
593
|
-
}
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
#### Example 2: RouteService
|
|
597
|
-
|
|
598
|
-
```typescript
|
|
599
|
-
// src/base/services/RouteService.ts
|
|
600
|
-
export class RouteService extends StoreInterface<RouterServiceState> {
|
|
601
|
-
constructor(
|
|
602
|
-
protected uiBridge: UIBridgeInterface<NavigateFunction>,
|
|
603
|
-
protected i18nService: I18nServiceInterface,
|
|
604
|
-
protected options: RouterServiceOptions
|
|
605
|
-
) {
|
|
606
|
-
super(
|
|
607
|
-
() => new RouterServiceState(options.routes, !!options.hasLocalRoutes)
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// ✅ Publish route changes via emit
|
|
612
|
-
override changeRoutes(routes: RouteConfigValue[]): void {
|
|
613
|
-
this.emit(this.cloneState({ routes }));
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
override goto(path: string, options?: NavigateOptions): void {
|
|
617
|
-
const composedPath = this.composePath(path);
|
|
618
|
-
this.uiBridge.getUIBridge()(composedPath, options);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Usage
|
|
623
|
-
function AppRouterProvider() {
|
|
624
|
-
const routerService = useIOC(IOCIdentifier.RouteServiceInterface);
|
|
625
|
-
|
|
626
|
-
// ✅ Subscribe to routes changes
|
|
627
|
-
const routes = useStore(routerService, (state) => state.routes);
|
|
628
|
-
|
|
629
|
-
const router = createBrowserRouter(routes);
|
|
630
|
-
|
|
631
|
-
return <RouterProvider router={router} />;
|
|
632
|
-
}
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
#### Example 3: I18nService
|
|
636
|
-
|
|
637
|
-
```typescript
|
|
638
|
-
// src/base/services/I18nService.ts
|
|
639
|
-
export class I18nService extends StoreInterface<I18nServiceState> {
|
|
640
|
-
constructor(protected pathname: string) {
|
|
641
|
-
super(() => new I18nServiceState(i18n.language as I18nServiceLocale));
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
selector = {
|
|
645
|
-
loading: (state: I18nServiceState) => state.loading,
|
|
646
|
-
language: (state: I18nServiceState) => state.language
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
override async changeLanguage(lng: string): Promise<void> {
|
|
650
|
-
// ✅ Publish loading state
|
|
651
|
-
this.emit(this.cloneState({ loading: true }));
|
|
652
|
-
|
|
653
|
-
await i18n.changeLanguage(lng);
|
|
654
|
-
|
|
655
|
-
// ✅ Publish complete state
|
|
656
|
-
this.emit({
|
|
657
|
-
language: lng as I18nServiceLocale,
|
|
658
|
-
loading: false
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Usage
|
|
664
|
-
function LanguageSwitcher() {
|
|
665
|
-
const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
|
|
666
|
-
|
|
667
|
-
// ✅ Only subscribe to loading state
|
|
668
|
-
const loading = useStore(i18nService, i18nService.selector.loading);
|
|
669
|
-
|
|
670
|
-
return (
|
|
671
|
-
<Select
|
|
672
|
-
value={i18n.language}
|
|
673
|
-
loading={loading}
|
|
674
|
-
onChange={(lng) => i18nService.changeLanguage(lng)}
|
|
675
|
-
/>
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
---
|
|
681
|
-
|
|
682
|
-
## 🧪 Testing
|
|
683
|
-
|
|
684
|
-
### Core Advantage: Store can be tested independently, UI can mock Store
|
|
685
|
-
|
|
686
|
-
#### 1. Test Service and Store (Logic Test)
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
// __tests__/src/base/services/UserService.test.ts
|
|
690
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
691
|
-
import { UserService } from '@/base/services/UserService';
|
|
692
|
-
|
|
693
|
-
describe('UserService (Logic Test)', () => {
|
|
694
|
-
let userService: UserService;
|
|
695
|
-
let mockApi: any;
|
|
696
|
-
|
|
697
|
-
beforeEach(() => {
|
|
698
|
-
mockApi = {
|
|
699
|
-
login: vi.fn(),
|
|
700
|
-
getUserInfo: vi.fn()
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
userService = new UserService(mockApi, mockConfig, mockStorage);
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it('should update store state when login success', async () => {
|
|
707
|
-
// ✅ Test state changes
|
|
708
|
-
mockApi.login.mockResolvedValue({
|
|
709
|
-
user: { name: 'John', id: 1 },
|
|
710
|
-
token: 'test-token'
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
// Subscribe to state changes
|
|
714
|
-
const states: any[] = [];
|
|
715
|
-
userService.subscribe((state) => {
|
|
716
|
-
states.push({ ...state });
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// Call login
|
|
720
|
-
await userService.login('user', 'pass');
|
|
721
|
-
|
|
722
|
-
// ✅ Verify state change sequence
|
|
723
|
-
expect(states).toHaveLength(2);
|
|
724
|
-
|
|
725
|
-
// First emit: loading = true
|
|
726
|
-
expect(states[0]).toEqual({
|
|
727
|
-
user: null,
|
|
728
|
-
loading: true,
|
|
729
|
-
error: null
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// Second emit: loading = false, user = John
|
|
733
|
-
expect(states[1]).toEqual({
|
|
734
|
-
user: { name: 'John', id: 1 },
|
|
735
|
-
loading: false,
|
|
736
|
-
error: null
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
it('should update store state when login fails', async () => {
|
|
741
|
-
mockApi.login.mockRejectedValue(new Error('Invalid credentials'));
|
|
742
|
-
|
|
743
|
-
const states: any[] = [];
|
|
744
|
-
userService.subscribe((state) => states.push({ ...state }));
|
|
745
|
-
|
|
746
|
-
await expect(userService.login('user', 'wrong')).rejects.toThrow();
|
|
747
|
-
|
|
748
|
-
// ✅ Verify error state
|
|
749
|
-
expect(states[1]).toEqual({
|
|
750
|
-
user: null,
|
|
751
|
-
loading: false,
|
|
752
|
-
error: expect.any(Error)
|
|
753
|
-
});
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
it('should emit logout state', () => {
|
|
757
|
-
// First set user logged in
|
|
758
|
-
userService.emit({
|
|
759
|
-
user: { name: 'John', id: 1 },
|
|
760
|
-
loading: false,
|
|
761
|
-
error: null
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
// Logout
|
|
765
|
-
userService.logout();
|
|
766
|
-
|
|
767
|
-
// ✅ Verify state reset
|
|
768
|
-
expect(userService.getState()).toEqual({
|
|
769
|
-
user: null,
|
|
770
|
-
loading: false,
|
|
771
|
-
error: null
|
|
772
|
-
});
|
|
773
|
-
});
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
// ✅✅✅ Advantages:
|
|
777
|
-
// 1. Don't need to render UI
|
|
778
|
-
// 2. Can test all state changes
|
|
779
|
-
// 3. Can verify emit call sequence
|
|
780
|
-
// 4. Tests run fast
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
#### 2. Test UI Component (UI Test)
|
|
784
|
-
|
|
785
|
-
```typescript
|
|
786
|
-
// __tests__/src/pages/LoginPage.test.tsx
|
|
787
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
788
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
789
|
-
import { LoginPage } from '@/pages/LoginPage';
|
|
790
|
-
import { IOCProvider } from '@/uikit/contexts/IOCContext';
|
|
791
|
-
|
|
792
|
-
describe('LoginPage (UI Test)', () => {
|
|
793
|
-
it('should show loading when login', async () => {
|
|
794
|
-
// ✅ Mock Service and Store
|
|
795
|
-
const mockStore = {
|
|
796
|
-
user: null,
|
|
797
|
-
loading: false,
|
|
798
|
-
error: null
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
const mockUserService = {
|
|
802
|
-
login: vi.fn().mockImplementation(() => {
|
|
803
|
-
// Simulate state change
|
|
804
|
-
mockStore.loading = true;
|
|
805
|
-
return Promise.resolve();
|
|
806
|
-
}),
|
|
807
|
-
subscribe: vi.fn(),
|
|
808
|
-
getState: () => mockStore
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
const mockIOC = (identifier: string) => {
|
|
812
|
-
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
// ✅ Render component
|
|
816
|
-
const { rerender } = render(
|
|
817
|
-
<IOCProvider value={mockIOC}>
|
|
818
|
-
<LoginPage />
|
|
819
|
-
</IOCProvider>
|
|
820
|
-
);
|
|
821
|
-
|
|
822
|
-
// Click login button
|
|
823
|
-
const loginButton = screen.getByText('Login');
|
|
824
|
-
fireEvent.click(loginButton);
|
|
825
|
-
|
|
826
|
-
// ✅ Verify Service was called
|
|
827
|
-
expect(mockUserService.login).toHaveBeenCalled();
|
|
828
|
-
|
|
829
|
-
// Simulate state update
|
|
830
|
-
mockStore.loading = true;
|
|
831
|
-
rerender(
|
|
832
|
-
<IOCProvider value={mockIOC}>
|
|
833
|
-
<LoginPage />
|
|
834
|
-
</IOCProvider>
|
|
835
|
-
);
|
|
836
|
-
|
|
837
|
-
// ✅ Verify UI shows loading state
|
|
838
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
it('should show error message when login fails', () => {
|
|
842
|
-
const mockStore = {
|
|
843
|
-
user: null,
|
|
844
|
-
loading: false,
|
|
845
|
-
error: new Error('Invalid credentials')
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
const mockUserService = {
|
|
849
|
-
login: vi.fn(),
|
|
850
|
-
subscribe: vi.fn(),
|
|
851
|
-
getState: () => mockStore
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
const mockIOC = (identifier: string) => {
|
|
855
|
-
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
856
|
-
};
|
|
857
|
-
|
|
858
|
-
render(
|
|
859
|
-
<IOCProvider value={mockIOC}>
|
|
860
|
-
<LoginPage />
|
|
861
|
-
</IOCProvider>
|
|
862
|
-
);
|
|
863
|
-
|
|
864
|
-
// ✅ Verify error message displayed
|
|
865
|
-
expect(screen.getByText('Error: Invalid credentials')).toBeInTheDocument();
|
|
866
|
-
});
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
// ✅✅✅ Advantages:
|
|
870
|
-
// 1. Don't need real Service implementation
|
|
871
|
-
// 2. Can easily simulate various states
|
|
872
|
-
// 3. UI tests focus on UI logic
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
#### 3. Combination Testing (Integration Test)
|
|
876
|
-
|
|
877
|
-
```typescript
|
|
878
|
-
// __tests__/src/integration/UserLogin.test.tsx
|
|
879
|
-
import { describe, it, expect } from 'vitest';
|
|
880
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
881
|
-
import { LoginPage } from '@/pages/LoginPage';
|
|
882
|
-
import { UserService } from '@/base/services/UserService';
|
|
883
|
-
import { IOCProvider } from '@/uikit/contexts/IOCContext';
|
|
884
|
-
|
|
885
|
-
describe('User Login Flow (Integration Test)', () => {
|
|
886
|
-
it('should complete login flow', async () => {
|
|
887
|
-
// ✅ Use real Service and Store
|
|
888
|
-
const mockApi = {
|
|
889
|
-
login: vi.fn().mockResolvedValue({
|
|
890
|
-
user: { name: 'John', id: 1 },
|
|
891
|
-
token: 'test-token'
|
|
892
|
-
})
|
|
893
|
-
};
|
|
894
|
-
|
|
895
|
-
const userService = new UserService(mockApi, mockConfig, mockStorage);
|
|
896
|
-
|
|
897
|
-
const mockIOC = (identifier: string) => {
|
|
898
|
-
if (identifier === 'UserServiceInterface') return userService;
|
|
899
|
-
};
|
|
900
|
-
|
|
901
|
-
// ✅ Render real UI
|
|
902
|
-
render(
|
|
903
|
-
<IOCProvider value={mockIOC}>
|
|
904
|
-
<LoginPage />
|
|
905
|
-
</IOCProvider>
|
|
906
|
-
);
|
|
907
|
-
|
|
908
|
-
// ✅ Simulate user action
|
|
909
|
-
const loginButton = screen.getByText('Login');
|
|
910
|
-
fireEvent.click(loginButton);
|
|
911
|
-
|
|
912
|
-
// ✅ Verify loading state
|
|
913
|
-
await waitFor(() => {
|
|
914
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// ✅ Verify login success
|
|
918
|
-
await waitFor(() => {
|
|
919
|
-
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
920
|
-
expect(userService.getState().user).toEqual({
|
|
921
|
-
name: 'John',
|
|
922
|
-
id: 1
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
// ✅ Verify API was called
|
|
927
|
-
expect(mockApi.login).toHaveBeenCalled();
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
// ✅✅✅ Advantages:
|
|
932
|
-
// 1. Test real user flow
|
|
933
|
-
// 2. Verify Service and UI integration
|
|
934
|
-
// 3. Discover integration issues
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
### Testing Strategy Summary
|
|
938
|
-
|
|
939
|
-
```
|
|
940
|
-
┌────────────────────────────────────────┐
|
|
941
|
-
│ Testing Pyramid │
|
|
942
|
-
│ │
|
|
943
|
-
│ △ UI Tests (10%) │
|
|
944
|
-
│ ╱ ╲ │
|
|
945
|
-
│ ╱ ╲ Integration Tests (20%) │
|
|
946
|
-
│ ╱ ╲ │
|
|
947
|
-
│ ╱───────╲ │
|
|
948
|
-
│ ╱ ╲ Store + Service Tests (70%)│
|
|
949
|
-
│╱═══════════╲ │
|
|
950
|
-
│ │
|
|
951
|
-
│ Store Tests: Test state change logic │
|
|
952
|
-
│ Integration Tests: Test Service + UI │
|
|
953
|
-
│ UI Tests: Test UI interaction │
|
|
954
|
-
└────────────────────────────────────────┘
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
---
|
|
958
|
-
|
|
959
|
-
## 💎 Best Practices
|
|
960
|
-
|
|
961
|
-
### 1. ✅ Service Extends StoreInterface
|
|
962
|
-
|
|
963
|
-
```typescript
|
|
964
|
-
// ✅ Good practice: Service extends StoreInterface
|
|
965
|
-
@injectable()
|
|
966
|
-
export class UserService extends StoreInterface<UserState> {
|
|
967
|
-
constructor() {
|
|
968
|
-
super(() => ({
|
|
969
|
-
user: null,
|
|
970
|
-
loading: false
|
|
971
|
-
}));
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
async login(username: string, password: string) {
|
|
975
|
-
this.emit({ ...this.state, loading: true });
|
|
976
|
-
// ...
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// ❌ Bad practice: Service doesn't extend StoreInterface
|
|
981
|
-
@injectable()
|
|
982
|
-
export class UserService {
|
|
983
|
-
private user: UserInfo | null = null;
|
|
984
|
-
|
|
985
|
-
// Problem: UI cannot subscribe to state
|
|
986
|
-
}
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
### 2. ✅ Use emit to Publish State
|
|
990
|
-
|
|
991
|
-
```typescript
|
|
992
|
-
// ✅ Good practice: Publish state via emit
|
|
993
|
-
async login(username: string, password: string) {
|
|
994
|
-
this.emit({ ...this.state, loading: true });
|
|
995
|
-
|
|
996
|
-
const response = await this.api.login({ username, password });
|
|
997
|
-
|
|
998
|
-
this.emit({
|
|
999
|
-
user: response.user,
|
|
1000
|
-
loading: false
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// ❌ Bad practice: Directly modify state
|
|
1005
|
-
async login(username: string, password: string) {
|
|
1006
|
-
this.state.loading = true; // ❌ Won't notify subscribers
|
|
1007
|
-
|
|
1008
|
-
const response = await this.api.login({ username, password });
|
|
1009
|
-
|
|
1010
|
-
this.state.user = response.user; // ❌ Won't notify subscribers
|
|
1011
|
-
}
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
### 3. ✅ Use cloneState to Simplify Updates
|
|
1015
|
-
|
|
1016
|
-
```typescript
|
|
1017
|
-
// ✅ Good practice: Use cloneState
|
|
1018
|
-
setUser(user: UserInfo) {
|
|
1019
|
-
this.emit(this.cloneState({ user }));
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
setLoading(loading: boolean) {
|
|
1023
|
-
this.emit(this.cloneState({ loading }));
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// ⚠️ Also acceptable: Manual spread
|
|
1027
|
-
setUser(user: UserInfo) {
|
|
1028
|
-
this.emit({ ...this.state, user });
|
|
1029
|
-
}
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
### 4. ✅ Define Selectors
|
|
1033
|
-
|
|
1034
|
-
```typescript
|
|
1035
|
-
// ✅ Good practice: Define selectors
|
|
1036
|
-
@injectable()
|
|
1037
|
-
export class UserService extends StoreInterface<UserState> {
|
|
1038
|
-
selector = {
|
|
1039
|
-
user: (state: UserState) => state.user,
|
|
1040
|
-
loading: (state: UserState) => state.loading,
|
|
1041
|
-
isLoggedIn: (state: UserState) => state.user !== null
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Usage
|
|
1046
|
-
const isLoggedIn = useStore(userService, userService.selector.isLoggedIn);
|
|
1047
|
-
|
|
1048
|
-
// ❌ Bad practice: Inline selectors
|
|
1049
|
-
const isLoggedIn = useStore(userService, (state) => state.user !== null);
|
|
1050
|
-
// Problem: Creates new function every render
|
|
1051
|
-
```
|
|
1052
|
-
|
|
1053
|
-
### 5. ✅ Use Selectors for Performance Optimization
|
|
1054
|
-
|
|
1055
|
-
```typescript
|
|
1056
|
-
// ✅ Good practice: Only subscribe to needed state
|
|
1057
|
-
function UserName() {
|
|
1058
|
-
const userService = useIOC('UserServiceInterface');
|
|
1059
|
-
|
|
1060
|
-
// Only subscribe to user, loading changes won't trigger re-render
|
|
1061
|
-
const user = useStore(userService, (state) => state.user);
|
|
1062
|
-
|
|
1063
|
-
return <span>{user?.name}</span>;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// ❌ Bad practice: Subscribe to complete state
|
|
1067
|
-
function UserName() {
|
|
1068
|
-
const userService = useIOC('UserServiceInterface');
|
|
1069
|
-
|
|
1070
|
-
// loading changes will also trigger re-render
|
|
1071
|
-
const { user, loading } = useStore(userService);
|
|
1072
|
-
|
|
1073
|
-
return <span>{user?.name}</span>;
|
|
1074
|
-
}
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
### 6. ✅ Keep State Immutable
|
|
1078
|
-
|
|
1079
|
-
```typescript
|
|
1080
|
-
// ✅ Good practice: Create new object
|
|
1081
|
-
updateUser(changes: Partial<UserInfo>) {
|
|
1082
|
-
this.emit({
|
|
1083
|
-
...this.state,
|
|
1084
|
-
user: {
|
|
1085
|
-
...this.state.user,
|
|
1086
|
-
...changes
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
// ❌ Bad practice: Directly modify object
|
|
1092
|
-
updateUser(changes: Partial<UserInfo>) {
|
|
1093
|
-
this.state.user.name = changes.name; // ❌ Direct modification
|
|
1094
|
-
this.emit(this.state); // ❌ Same reference, may not trigger update
|
|
1095
|
-
}
|
|
1096
|
-
```
|
|
1097
|
-
|
|
1098
|
-
### 7. ✅ Reasonably Divide State
|
|
1099
|
-
|
|
1100
|
-
```typescript
|
|
1101
|
-
// ✅ Good practice: Each Service manages its own state
|
|
1102
|
-
class UserService extends StoreInterface<UserState> {
|
|
1103
|
-
// Only manage user-related state
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
class ThemeService extends StoreInterface<ThemeState> {
|
|
1107
|
-
// Only manage theme-related state
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
class I18nService extends StoreInterface<I18nState> {
|
|
1111
|
-
// Only manage i18n-related state
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// ❌ Bad practice: Global large Store
|
|
1115
|
-
class GlobalStore extends StoreInterface<GlobalState> {
|
|
1116
|
-
// Contains all state: user, theme, i18n, etc.
|
|
1117
|
-
// Problem: Any state change affects all subscribers
|
|
1118
|
-
}
|
|
1119
|
-
```
|
|
1120
|
-
|
|
1121
|
-
---
|
|
1122
|
-
|
|
1123
|
-
## ❓ FAQ
|
|
1124
|
-
|
|
1125
|
-
### Q1: Why not use Redux?
|
|
1126
|
-
|
|
1127
|
-
**A:**
|
|
1128
|
-
|
|
1129
|
-
| Feature | Redux | Store (SliceStore) |
|
|
1130
|
-
| ---------------------- | ------------------------------------- | ---------------------------- |
|
|
1131
|
-
| **Complexity** | ❌ High (Action, Reducer, Middleware) | ✅ Low (emit + subscribe) |
|
|
1132
|
-
| **Learning Curve** | ❌ Steep | ✅ Gentle |
|
|
1133
|
-
| **TypeScript Support** | ⚠️ Needs extra config | ✅ Native support |
|
|
1134
|
-
| **IOC Integration** | ⚠️ Needs extra work | ✅ Natural integration |
|
|
1135
|
-
| **Performance** | ✅ Good | ✅ Good |
|
|
1136
|
-
| **Use Case** | Large applications | Small to medium applications |
|
|
1137
|
-
|
|
1138
|
-
**Our Choice:**
|
|
1139
|
-
|
|
1140
|
-
- Project already uses IOC, don't need Redux's global state management
|
|
1141
|
-
- Each Service manages its own state, clearer
|
|
1142
|
-
- SliceStore is simple and powerful enough
|
|
1143
|
-
|
|
1144
|
-
### Q2: What's the difference between Store and React Context?
|
|
1145
|
-
|
|
1146
|
-
**A:**
|
|
1147
|
-
|
|
1148
|
-
| Feature | React Context | Store |
|
|
1149
|
-
| ----------------------- | ---------------------------------- | --------------------------------------- |
|
|
1150
|
-
| **Scope** | Component tree | Global (through IOC) |
|
|
1151
|
-
| **Performance** | ⚠️ Any value change re-renders all | ✅ Only subscribed value changes render |
|
|
1152
|
-
| **Selectors** | ❌ None | ✅ Yes |
|
|
1153
|
-
| **Service Integration** | ⚠️ Need manual | ✅ Natural integration |
|
|
1154
|
-
|
|
1155
|
-
**Recommendation:**
|
|
1156
|
-
|
|
1157
|
-
- Use Store to manage application state (Service state)
|
|
1158
|
-
- Use Context to manage UI state (like modals, temporary form data)
|
|
1159
|
-
|
|
1160
|
-
### Q3: How to avoid redundant renders?
|
|
1161
|
-
|
|
1162
|
-
**A:** Use selectors
|
|
1163
|
-
|
|
1164
|
-
```typescript
|
|
1165
|
-
// ❌ Problem: Subscribe to complete state
|
|
1166
|
-
const { user, loading, error } = useStore(userService);
|
|
1167
|
-
// loading changes will cause component re-render
|
|
1168
|
-
|
|
1169
|
-
// ✅ Solution: Only subscribe to needed state
|
|
1170
|
-
const user = useStore(userService, (state) => state.user);
|
|
1171
|
-
// Only user changes will cause re-render
|
|
1172
|
-
```
|
|
1173
|
-
|
|
1174
|
-
### Q4: Can I call emit outside Service?
|
|
1175
|
-
|
|
1176
|
-
**A:** Not recommended
|
|
1177
|
-
|
|
1178
|
-
```typescript
|
|
1179
|
-
// ❌ Bad practice
|
|
1180
|
-
function SomeComponent() {
|
|
1181
|
-
const userService = useIOC('UserServiceInterface');
|
|
1182
|
-
|
|
1183
|
-
// ❌ Directly call emit
|
|
1184
|
-
userService.emit({ user: newUser, loading: false });
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
// ✅ Good practice: Through Service method
|
|
1188
|
-
function SomeComponent() {
|
|
1189
|
-
const userService = useIOC('UserServiceInterface');
|
|
1190
|
-
|
|
1191
|
-
// ✅ Call Service method
|
|
1192
|
-
userService.setUser(newUser);
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// In Service
|
|
1196
|
-
@injectable()
|
|
1197
|
-
export class UserService extends StoreInterface<UserState> {
|
|
1198
|
-
setUser(user: UserInfo) {
|
|
1199
|
-
this.emit(this.cloneState({ user }));
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
```
|
|
1203
|
-
|
|
1204
|
-
**Reasons:**
|
|
1205
|
-
|
|
1206
|
-
- Maintain encapsulation
|
|
1207
|
-
- Easier to test
|
|
1208
|
-
- Business logic centralized in Service
|
|
1209
|
-
|
|
1210
|
-
### Q5: Store state update not working?
|
|
1211
|
-
|
|
1212
|
-
**A:** Check these points:
|
|
1213
|
-
|
|
1214
|
-
```typescript
|
|
1215
|
-
// ❌ Common mistake 1: Directly modify state
|
|
1216
|
-
this.state.loading = true; // Won't trigger update
|
|
1217
|
-
|
|
1218
|
-
// ✅ Correct: Use emit
|
|
1219
|
-
this.emit({ ...this.state, loading: true });
|
|
1220
|
-
|
|
1221
|
-
// ❌ Common mistake 2: Not creating new object
|
|
1222
|
-
const state = this.state;
|
|
1223
|
-
state.loading = true;
|
|
1224
|
-
this.emit(state); // Same reference, may not trigger update
|
|
1225
|
-
|
|
1226
|
-
// ✅ Correct: Create new object
|
|
1227
|
-
this.emit({ ...this.state, loading: true });
|
|
1228
|
-
|
|
1229
|
-
// ❌ Common mistake 3: Forgot to subscribe
|
|
1230
|
-
function MyComponent() {
|
|
1231
|
-
const userService = useIOC('UserServiceInterface');
|
|
1232
|
-
// Not calling useStore, cannot receive updates
|
|
1233
|
-
|
|
1234
|
-
return <div>{userService.getState().user?.name}</div>;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// ✅ Correct: Use useStore to subscribe
|
|
1238
|
-
function MyComponent() {
|
|
1239
|
-
const userService = useIOC('UserServiceInterface');
|
|
1240
|
-
const user = useStore(userService, (state) => state.user);
|
|
1241
|
-
|
|
1242
|
-
return <div>{user?.name}</div>;
|
|
1243
|
-
}
|
|
1244
|
-
```
|
|
1245
|
-
|
|
1246
|
-
### Q6: How to share state between Services?
|
|
1247
|
-
|
|
1248
|
-
**A:** Through IOC injection
|
|
1249
|
-
|
|
1250
|
-
```typescript
|
|
1251
|
-
// Service A
|
|
1252
|
-
@injectable()
|
|
1253
|
-
export class UserService extends StoreInterface<UserState> {
|
|
1254
|
-
// ...
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// Service B depends on Service A
|
|
1258
|
-
@injectable()
|
|
1259
|
-
export class ProfileService {
|
|
1260
|
-
constructor(
|
|
1261
|
-
@inject('UserServiceInterface')
|
|
1262
|
-
private userService: UserService
|
|
1263
|
-
) {}
|
|
1264
|
-
|
|
1265
|
-
async updateProfile(data: ProfileData) {
|
|
1266
|
-
// ✅ Access UserService state
|
|
1267
|
-
const user = this.userService.getState().user;
|
|
1268
|
-
|
|
1269
|
-
// ✅ Can also subscribe to UserService state
|
|
1270
|
-
this.userService.subscribe((state) => {
|
|
1271
|
-
console.log('User state changed:', state);
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
```
|
|
1276
|
-
|
|
1277
|
-
---
|
|
1278
|
-
|
|
1279
|
-
## 📚 Related Documentation
|
|
1280
|
-
|
|
1281
|
-
- [Project Architecture Design](./index.md) - Understand overall architecture
|
|
1282
|
-
- [IOC Container](./ioc.md) - Dependency injection and UI separation
|
|
1283
|
-
- [Bootstrap Initializer](./bootstrap.md) - Application startup and initialization
|
|
1284
|
-
- [Testing Guide](./test-guide.md) - Detailed testing strategies
|
|
1285
|
-
|
|
1286
|
-
---
|
|
1287
|
-
|
|
1288
|
-
## 🎉 Summary
|
|
1289
|
-
|
|
1290
|
-
Core value of Store state management:
|
|
1291
|
-
|
|
1292
|
-
1. **Solve Communication Problem** 📡 - Application layer notifies UI layer while maintaining separation
|
|
1293
|
-
2. **Publish-Subscribe Pattern** 🔔 - Service emits, UI uses useStore
|
|
1294
|
-
3. **Auto-update UI** ⚡ - UI automatically re-renders when state changes
|
|
1295
|
-
4. **Maintain Decoupling** 🔗 - Service doesn't know which UIs are listening
|
|
1296
|
-
5. **Easy to Test** 🧪 - Store can be tested independently
|
|
1297
|
-
6. **Performance Optimization** 🚀 - Selectors only subscribe to needed state
|
|
1298
|
-
7. **Type Safety** 🔒 - Full TypeScript support
|
|
1299
|
-
|
|
1300
|
-
**Remember the core pattern:**
|
|
1301
|
-
|
|
1302
|
-
```typescript
|
|
1303
|
-
// 1. Service extends StoreInterface
|
|
1304
|
-
class MyService extends StoreInterface<MyState> {
|
|
1305
|
-
// 2. Publish state via emit
|
|
1306
|
-
doSomething() {
|
|
1307
|
-
this.emit({ ...this.state, data: newData });
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
// 3. UI subscribes to state via useStore
|
|
1312
|
-
function MyComponent() {
|
|
1313
|
-
const myService = useIOC('MyServiceInterface');
|
|
1314
|
-
const data = useStore(myService, (state) => state.data);
|
|
1315
|
-
|
|
1316
|
-
return <div>{data}</div>;
|
|
1317
|
-
}
|
|
1318
|
-
```
|
|
1319
|
-
|
|
1320
|
-
**Core Principles:**
|
|
1321
|
-
|
|
1322
|
-
- ✅ Service publishes state via emit
|
|
1323
|
-
- ✅ UI subscribes to state via useStore
|
|
1324
|
-
- ✅ Use selectors for performance optimization
|
|
1325
|
-
- ✅ Keep state immutable
|
|
1326
|
-
- ✅ Each Service manages its own state
|
|
1327
|
-
|
|
1328
|
-
---
|
|
1329
|
-
|
|
1330
|
-
**Feedback:**
|
|
1331
|
-
If you have any questions or suggestions about Store state management, please discuss in the team channel or submit an Issue.
|