@qlover/create-app 0.7.14 → 0.8.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.
Files changed (361) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/configs/_common/.gitignore.template +6 -0
  3. package/dist/configs/_common/.prettierignore +17 -5
  4. package/dist/configs/_common/.vscode/settings.json +6 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/templates/next-app/.env.template +1 -1
  8. package/dist/templates/next-app/README.en.md +130 -0
  9. package/dist/templates/next-app/README.md +114 -20
  10. package/dist/templates/next-app/config/Identifier/api.ts +5 -5
  11. package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
  12. package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
  13. package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
  14. package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
  15. package/dist/templates/next-app/config/Identifier/index.ts +2 -12
  16. package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
  17. package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
  18. package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
  19. package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
  20. package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
  21. package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
  22. package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
  23. package/dist/templates/next-app/config/adminNavs.ts +19 -0
  24. package/dist/templates/next-app/config/common.ts +22 -13
  25. package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
  26. package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
  27. package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
  28. package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
  29. package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
  30. package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
  31. package/dist/templates/next-app/docs/en/api.md +387 -0
  32. package/dist/templates/next-app/docs/en/component.md +544 -0
  33. package/dist/templates/next-app/docs/en/database.md +496 -0
  34. package/dist/templates/next-app/docs/en/development-guide.md +727 -0
  35. package/dist/templates/next-app/docs/en/env.md +563 -0
  36. package/dist/templates/next-app/docs/en/i18n.md +287 -0
  37. package/dist/templates/next-app/docs/en/index.md +165 -0
  38. package/dist/templates/next-app/docs/en/page.md +457 -0
  39. package/dist/templates/next-app/docs/en/project-structure.md +176 -0
  40. package/dist/templates/next-app/docs/en/router.md +427 -0
  41. package/dist/templates/next-app/docs/en/theme.md +532 -0
  42. package/dist/templates/next-app/docs/en/validator.md +478 -0
  43. package/dist/templates/next-app/docs/zh/api.md +387 -0
  44. package/dist/templates/next-app/docs/zh/component.md +544 -0
  45. package/dist/templates/next-app/docs/zh/database.md +496 -0
  46. package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
  47. package/dist/templates/next-app/docs/zh/env.md +563 -0
  48. package/dist/templates/next-app/docs/zh/i18n.md +287 -0
  49. package/dist/templates/next-app/docs/zh/index.md +165 -0
  50. package/dist/templates/next-app/docs/zh/page.md +457 -0
  51. package/dist/templates/next-app/docs/zh/project-structure.md +176 -0
  52. package/dist/templates/next-app/docs/zh/router.md +427 -0
  53. package/dist/templates/next-app/docs/zh/theme.md +532 -0
  54. package/dist/templates/next-app/docs/zh/validator.md +476 -0
  55. package/dist/templates/next-app/make/generateLocales.ts +19 -12
  56. package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
  57. package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
  58. package/dist/templates/next-app/package.json +7 -3
  59. package/dist/templates/next-app/public/locales/en.json +172 -207
  60. package/dist/templates/next-app/public/locales/zh.json +172 -207
  61. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
  62. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
  63. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
  64. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
  65. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
  66. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
  67. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
  68. package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
  69. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
  70. package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
  71. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
  72. package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
  73. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
  74. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
  75. package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
  76. package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
  77. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
  78. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
  79. package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
  80. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
  81. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
  82. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
  83. package/dist/templates/next-app/src/i18n/request.ts +30 -1
  84. package/dist/templates/next-app/src/server/PageParams.ts +2 -10
  85. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
  86. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
  87. package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
  88. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
  89. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
  90. package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
  91. package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
  92. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
  93. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
  94. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
  95. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
  96. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
  97. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
  98. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
  99. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
  100. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
  101. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
  102. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
  103. package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
  104. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
  105. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
  106. package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
  107. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
  108. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
  109. package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
  110. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
  111. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
  112. package/dist/templates/react-app/.prettierignore +17 -0
  113. package/dist/templates/react-app/README.en.md +71 -54
  114. package/dist/templates/react-app/README.md +35 -18
  115. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
  116. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
  117. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
  118. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
  119. package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
  120. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
  121. package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
  122. package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
  123. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
  124. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
  125. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
  126. package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
  127. package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
  128. package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
  129. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
  130. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
  131. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
  132. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
  133. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
  134. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
  135. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
  136. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
  137. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
  138. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
  139. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
  140. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
  141. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
  142. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
  143. package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
  144. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
  145. package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
  146. package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
  147. package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
  148. package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
  149. package/dist/templates/react-app/config/Identifier/index.ts +1 -9
  150. package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
  151. package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
  152. package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
  153. package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
  154. package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
  155. package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
  156. package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
  157. package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
  158. package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
  159. package/dist/templates/react-app/config/app.router.ts +66 -69
  160. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
  161. package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
  162. package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
  163. package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
  164. package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
  165. package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
  166. package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
  167. package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
  168. package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
  169. package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
  170. package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
  171. package/dist/templates/react-app/config/theme.ts +14 -4
  172. package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
  173. package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
  174. package/dist/templates/react-app/docs/en/env.md +1132 -278
  175. package/dist/templates/react-app/docs/en/i18n.md +858 -147
  176. package/dist/templates/react-app/docs/en/index.md +733 -104
  177. package/dist/templates/react-app/docs/en/ioc.md +1228 -287
  178. package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
  179. package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
  180. package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
  181. package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
  182. package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
  183. package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
  184. package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
  185. package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
  186. package/dist/templates/react-app/docs/en/store.md +1194 -184
  187. package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
  188. package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
  189. package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
  190. package/dist/templates/react-app/docs/zh/env.md +1132 -275
  191. package/dist/templates/react-app/docs/zh/i18n.md +858 -147
  192. package/dist/templates/react-app/docs/zh/index.md +717 -104
  193. package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
  194. package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
  195. package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
  196. package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
  197. package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
  198. package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
  199. package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
  200. package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
  201. package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
  202. package/dist/templates/react-app/docs/zh/store.md +1192 -184
  203. package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
  204. package/dist/templates/react-app/e2e/App.spec.ts +319 -0
  205. package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
  206. package/dist/templates/react-app/e2e/main.spec.ts +20 -0
  207. package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
  208. package/dist/templates/react-app/eslint.config.mjs +247 -0
  209. package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
  210. package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
  211. package/dist/templates/react-app/package.json +31 -3
  212. package/dist/templates/react-app/playwright.config.ts +79 -0
  213. package/dist/templates/react-app/public/locales/en/common.json +190 -179
  214. package/dist/templates/react-app/public/locales/zh/common.json +190 -179
  215. package/dist/templates/react-app/src/App.tsx +15 -42
  216. package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
  217. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
  218. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  219. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
  220. package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
  221. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
  222. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  223. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
  224. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
  225. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
  226. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
  227. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
  228. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
  229. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
  230. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
  231. package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
  232. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
  233. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
  234. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
  235. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
  236. package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
  237. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
  238. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
  239. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
  240. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
  241. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
  242. package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
  243. package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
  244. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
  245. package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
  246. package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
  247. package/dist/templates/react-app/src/base/types/Page.ts +14 -2
  248. package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
  249. package/dist/templates/react-app/src/core/IOC.ts +5 -46
  250. package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
  251. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
  252. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
  253. package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  254. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
  255. package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
  256. package/dist/templates/react-app/src/core/globals.ts +9 -9
  257. package/dist/templates/react-app/src/main.tsx +4 -4
  258. package/dist/templates/react-app/src/pages/404.tsx +6 -3
  259. package/dist/templates/react-app/src/pages/500.tsx +5 -2
  260. package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
  261. package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
  262. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
  263. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
  264. package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
  265. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
  266. package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
  267. package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
  268. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
  269. package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
  270. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
  271. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
  272. package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
  273. package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
  274. package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
  275. package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
  276. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
  277. package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  278. package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  279. package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
  280. package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  281. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
  282. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
  283. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  284. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
  285. package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
  286. package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
  287. package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
  288. package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
  289. package/dist/templates/react-app/src/styles/css/page.css +4 -3
  290. package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
  291. package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
  292. package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
  293. package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
  294. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
  295. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
  296. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
  297. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
  298. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
  299. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
  300. package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
  301. package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
  302. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
  303. package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
  304. package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
  305. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
  306. package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
  307. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
  308. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
  309. package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
  310. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
  311. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
  312. package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
  313. package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
  314. package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
  315. package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
  316. package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
  317. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
  318. package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
  319. package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
  320. package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
  321. package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
  322. package/dist/templates/react-app/tsconfig.e2e.json +21 -0
  323. package/dist/templates/react-app/tsconfig.json +8 -1
  324. package/dist/templates/react-app/tsconfig.node.json +1 -1
  325. package/dist/templates/react-app/tsconfig.test.json +3 -1
  326. package/dist/templates/react-app/vite.config.ts +50 -34
  327. package/package.json +2 -1
  328. package/dist/configs/react-app/eslint.config.js +0 -94
  329. package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
  330. package/dist/templates/next-app/config/Identifier/common.ts +0 -69
  331. package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
  332. package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
  333. package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
  334. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
  335. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
  336. package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
  337. package/dist/templates/next-app/docs/env.md +0 -94
  338. package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
  339. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
  340. package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
  341. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
  342. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
  343. package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
  344. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
  345. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
  346. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
  347. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
  348. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
  349. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
  350. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
  351. package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
  352. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
  353. package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
  354. package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
  355. package/dist/templates/react-app/config/i18n.ts +0 -15
  356. package/dist/templates/react-app/docs/en/project-structure.md +0 -434
  357. package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
  358. package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
  359. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
  360. package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
  361. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
@@ -1,168 +1,370 @@
1
1
  # Store State Management
2
2
 
3
- ## Core Concepts
3
+ ## 📋 Table of Contents
4
4
 
5
- The Store design philosophy is based on the following core concepts:
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
6
14
 
7
- 1. **Logic and UI Separation**
8
- - Business logic centralized in Store management
9
- - UI components only handle rendering and user interaction
10
- - Logic dependency injection through IOC container
15
+ ---
11
16
 
12
- 2. **Reactive Data Flow**
13
- - Based on publish-subscribe pattern
14
- - State changes automatically trigger UI updates
15
- - Precise component re-rendering control
17
+ ## 🎯 Core Philosophy
16
18
 
17
- 3. **State Slice Management**
18
- - Break down complex states into independent slices
19
- - Each slice handles specific business domain
20
- - Slices can be combined and communicate
19
+ > **🚨 Core Problem: How does the application layer (Service) notify the UI layer to update while maintaining separation?**
21
20
 
22
- ## How It Works
21
+ > **⭐ Solution: Service contains Store, publishes state through `emit`, UI subscribes to state through `useStore`!**
23
22
 
24
- ### 1. State Subscription Mechanism
23
+ ### Core Concept
25
24
 
26
- ```typescript
27
- // Store implements publish-subscribe mechanism internally
28
- class SliceStore<T> {
29
- private listeners = new Set<(state: T) => void>();
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
+ ```
30
62
 
31
- // Publish state updates
32
- protected emit(newState: T) {
33
- this.state = newState;
34
- this.listeners.forEach((listener) => listener(this.state));
35
- }
63
+ ---
36
64
 
37
- // Subscribe to state changes
38
- subscribe(listener: (state: T) => void) {
39
- this.listeners.add(listener);
40
- return () => this.listeners.delete(listener);
41
- }
42
- }
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()
43
77
  ```
44
78
 
45
- ### 2. State Update Flow
79
+ ### Analogy
46
80
 
47
81
  ```
48
- User Action → Call Store Method Update State → Notify Subscribers → UI Update
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)
49
96
  ```
50
97
 
51
- 1. User triggers action (e.g., button click)
52
- 2. Calls method in Store
53
- 3. Store uses emit to publish new state
54
- 4. Components subscribed to that state receive notification
55
- 5. Components re-render, displaying latest state
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:
56
105
 
57
- ### 3. Component Integration
106
+ #### Problem Example: Without Store
58
107
 
59
- ```tsx
60
- // Using Store in components
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
61
124
  function UserProfile() {
62
- // useStore hook automatically handles subscription and unsubscription
63
- const user = useStore(IOC(UserService), (state) => state.userInfo);
125
+ const userService = useIOC('UserServiceInterface');
126
+
127
+ // ❌ Problem: How to get userService.user?
128
+ // ❌ How to trigger UI re-render when userService.user updates?
64
129
 
65
- return <div>{user.name}</div>;
130
+ return <div>{/* Cannot display user */}</div>;
66
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)
67
138
  ```
68
139
 
69
- ### 4. State Slice Example
140
+ #### Solution: Use Store
70
141
 
71
142
  ```typescript
72
- // Authentication slice
73
- class AuthStore extends StoreInterface<AuthState> {
143
+ // Service (Logic layer)
144
+ @injectable()
145
+ export class UserService extends StoreInterface<UserState> {
74
146
  constructor() {
75
147
  super(() => ({
76
- isLoggedIn: false,
77
- user: null
148
+ user: null,
149
+ loading: false
78
150
  }));
79
151
  }
80
152
 
81
- login(credentials: Credentials) {
82
- // Handle login logic
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
83
160
  this.emit({
84
- isLoggedIn: true,
85
- user: userData
161
+ user: response.user,
162
+ loading: false
86
163
  });
87
164
  }
88
165
  }
89
166
 
90
- // Theme settings slice
91
- class ThemeStore extends StoreInterface<ThemeState> {
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> {
92
224
  constructor() {
93
225
  super(() => ({
94
- mode: 'light',
95
- colors: defaultColors
226
+ user: null,
227
+ loading: false
96
228
  }));
97
229
  }
98
230
 
99
- toggleTheme() {
100
- const mode = this.state.mode === 'light' ? 'dark' : 'light';
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
+
101
237
  this.emit({
102
- ...this.state,
103
- mode
104
- });
238
+ user: response.user,
239
+ loading: false
240
+ }); // Publish: loading complete, user logged in
105
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
+ );
106
262
  }
107
263
  ```
108
264
 
109
- ## Overview
265
+ #### Workflow
110
266
 
111
- Store is the application's state management solution, implemented based on `@qlover/slice-store-react`. It manages state using slices and has the following features:
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
+ ```
112
293
 
113
- - **Type Safety**: Based on TypeScript, providing complete type inference
114
- - **Lightweight**: No complex configuration, easy to use
115
- - **High Performance**: Precise component updates, avoiding unnecessary renders
116
- - **Modular**: Supports state slicing, convenient for managing large applications
117
- - **IOC Integration**: Perfect integration with dependency injection system
294
+ ---
118
295
 
119
- ## Core Concepts
296
+ ## 🛠️ Implementation in the Project
120
297
 
121
- ### 1. Store Interface
298
+ ### 1. File Structure
122
299
 
123
- The Store system is based on two core interfaces:
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
124
315
 
125
- #### StoreStateInterface
316
+ Store system is based on `SliceStore` from `@brain-toolkit/react-kit`:
126
317
 
127
318
  ```typescript
128
- /**
129
- * Store state interface
130
- *
131
- * Purpose: Define contract for store state objects
132
- * Core Idea: Enforce consistent structure for store states
133
- * Main Function: Serve as base for all store state types
134
- * Main Goal: Ensure state type safety and extensibility
135
- */
136
- interface StoreStateInterface {
137
- // Define your own properties here
138
- // ...
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
+ }
139
346
  }
140
347
  ```
141
348
 
142
- #### StoreInterface
349
+ ### 3. StoreInterface Base Class
350
+
351
+ Project's Store base class, provides additional utility methods:
143
352
 
144
353
  ```typescript
145
- /**
146
- * Store interface
147
- *
148
- * Purpose: Abstract base class for all state stores
149
- * Core Idea: Provide unified state management API, including reset and clone helper methods
150
- * Main Function: Extend SliceStore, add resetState and cloneState utility methods
151
- * Main Goal: Simplify store implementation and ensure consistency
152
- */
153
- abstract class StoreInterface<
354
+ // From @qlover/corekit-bridge
355
+ export abstract class StoreInterface<
154
356
  T extends StoreStateInterface
155
357
  > extends SliceStore<T> {
156
358
  constructor(protected stateFactory: () => T) {
157
359
  super(stateFactory);
158
360
  }
159
361
 
160
- // Reset store state
362
+ // Reset state
161
363
  resetState(): void {
162
364
  this.emit(this.stateFactory());
163
365
  }
164
366
 
165
- // Clone store state
367
+ // Clone state (for updates)
166
368
  cloneState(source?: Partial<T>): T {
167
369
  const cloned = clone(this.state);
168
370
  if (typeof cloned === 'object' && cloned !== null) {
@@ -173,149 +375,957 @@ abstract class StoreInterface<
173
375
  }
174
376
  ```
175
377
 
176
- ### 2. State Slices
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
+ ```
177
388
 
178
- State slices divide application state into independent parts:
389
+ ---
390
+
391
+ ## 📝 How to Use
392
+
393
+ ### 1. Define State Interface
179
394
 
180
395
  ```typescript
181
- // User state slice example
182
- class UserState implements StoreStateInterface {
183
- isLoggedIn: boolean = false;
184
- userInfo: {
185
- name: string;
186
- role: string;
187
- } | null = null;
396
+ // src/base/services/UserService.ts
397
+ export interface UserState extends StoreStateInterface {
398
+ user: UserInfo | null;
399
+ loading: boolean;
400
+ error: Error | null;
188
401
  }
402
+ ```
189
403
 
190
- // Store implementation example
191
- export class UserStore extends StoreInterface<UserState> {
192
- constructor() {
193
- super(() => new UserState());
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 }));
194
465
  }
195
466
  }
196
467
  ```
197
468
 
198
- ## Usage in Project
469
+ ### 3. UI Subscribes to State
199
470
 
200
- ### 1. Creating Store Controller
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)
201
500
 
202
501
  ```typescript
203
- import { StoreInterface, StoreStateInterface } from '@qlover/corekit-bridge';
502
+ // src/pages/UserProfile.tsx
503
+ function UserProfile() {
504
+ const userService = useIOC('UserServiceInterface');
204
505
 
205
- interface ExecutorState extends StoreStateInterface {
206
- helloState: string;
207
- tasks: Task[];
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>;
208
512
  }
513
+ ```
209
514
 
515
+ ### 5. Define Selectors (Recommended)
516
+
517
+ ```typescript
518
+ // src/base/services/UserService.ts
210
519
  @injectable()
211
- export class ExecutorController extends StoreInterface<ExecutorState> {
212
- constructor() {
213
- super(() => ({
214
- helloState: '',
215
- tasks: []
216
- }));
217
- }
520
+ export class UserService extends StoreInterface<UserState> {
521
+ // ... other code
218
522
 
219
- // Selectors
523
+ // ✅ Define selectors
220
524
  selector = {
221
- helloState: (state: ExecutorState) => state.helloState,
222
- tasks: (state: ExecutorState) => state.tasks
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
223
529
  };
224
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
+ }
225
542
  ```
226
543
 
227
- ### 2. Using in Components
544
+ ### 6. Real Project Examples
228
545
 
229
- Use the `useStore` Hook to access state:
546
+ #### Example 1: UserService
230
547
 
231
- ```tsx
232
- function MyComponent() {
233
- // Get complete state
234
- const state = useStore(IOC(ExecutorController));
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
+ }
235
568
 
236
- // Use selector to get specific state
237
- const helloState = useStore(
238
- IOC(ExecutorController),
239
- (controller) => controller.selector.helloState
240
- );
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);
241
669
 
242
670
  return (
243
- <div>
244
- <h1>{helloState}</h1>
245
- </div>
671
+ <Select
672
+ value={i18n.language}
673
+ loading={loading}
674
+ onChange={(lng) => i18nService.changeLanguage(lng)}
675
+ />
246
676
  );
247
677
  }
248
678
  ```
249
679
 
250
- ### 3. Updating State
680
+ ---
681
+
682
+ ## 🧪 Testing
683
+
684
+ ### Core Advantage: Store can be tested independently, UI can mock Store
251
685
 
252
- Update state through controller methods:
686
+ #### 1. Test Service and Store (Logic Test)
253
687
 
254
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
255
965
  @injectable()
256
- class ExecutorController extends StoreInterface<ExecutorState> {
257
- // ... constructor and other code
966
+ export class UserService extends StoreInterface<UserState> {
967
+ constructor() {
968
+ super(() => ({
969
+ user: null,
970
+ loading: false
971
+ }));
972
+ }
258
973
 
259
- updateHelloState(newState: string) {
260
- this.emit({ ...this.state, helloState: newState });
974
+ async login(username: string, password: string) {
975
+ this.emit({ ...this.state, loading: true });
976
+ // ...
261
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
+ ```
262
1052
 
263
- async fetchTasks() {
264
- const tasks = await api.getTasks();
265
- this.emit({ ...this.state, tasks });
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 }));
266
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?
267
1247
 
268
- // Use cloneState for state updates
269
- updateWithClone(newState: Partial<ExecutorState>) {
270
- this.emit(this.cloneState(newState));
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
+ });
271
1273
  }
272
1274
  }
273
1275
  ```
274
1276
 
275
- ## Best Practices
1277
+ ---
276
1278
 
277
- 1. **State Organization**
278
- - Divide state by functional modules
279
- - Avoid state redundancy
280
- - Keep state flat
1279
+ ## 📚 Related Documentation
281
1280
 
282
- 2. **Performance Optimization**
283
- - Use selectors to get state, avoid unnecessary re-renders
284
- - Reasonably split components, avoid large components subscribing to too many states
285
- - Use `cloneState` method to ensure state update immutability
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
286
1285
 
287
- 3. **Type Safety**
288
- - Define interfaces for all states
289
- - Use TypeScript's type inference
290
- - Avoid using any type
1286
+ ---
291
1287
 
292
- 4. **Bootstrap Integration**
293
- - Initialize store during Bootstrap phase
294
- - Manage store instances through IOC container
295
- - Use plugin system to extend functionality
1288
+ ## 🎉 Summary
296
1289
 
297
- ## Common Issues
1290
+ Core value of Store state management:
298
1291
 
299
- ### 1. State Updates Not Working
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
300
1299
 
301
- Check the following:
1300
+ **Remember the core pattern:**
302
1301
 
303
- - Ensure correct use of `emit` method to update state
304
- - Use `cloneState` method to ensure state immutability
305
- - Check if component correctly subscribes to state
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
+ }
306
1310
 
307
- ### 2. Component Re-rendering Issues
1311
+ // 3. UI subscribes to state via useStore
1312
+ function MyComponent() {
1313
+ const myService = useIOC('MyServiceInterface');
1314
+ const data = useStore(myService, (state) => state.data);
308
1315
 
309
- Possible solutions:
1316
+ return <div>{data}</div>;
1317
+ }
1318
+ ```
310
1319
 
311
- - Use selectors to subscribe only to needed state
312
- - Check if dependencies are correctly set
313
- - Consider using React.memo for component optimization
1320
+ **Core Principles:**
314
1321
 
315
- ### 3. TypeScript Type Errors
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
316
1327
 
317
- Common solutions:
1328
+ ---
318
1329
 
319
- - Ensure correct inheritance from StoreInterface
320
- - Check if generic parameters are correct
321
- - Ensure state types implement StoreStateInterface
1330
+ **Feedback:**
1331
+ If you have any questions or suggestions about Store state management, please discuss in the team channel or submit an Issue.