@qlover/create-app 0.7.15 → 0.9.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 (363) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
  3. package/dist/configs/_common/.github/workflows/release.yml +2 -2
  4. package/dist/configs/_common/.gitignore.template +6 -0
  5. package/dist/configs/_common/.prettierignore +17 -5
  6. package/dist/configs/_common/.vscode/settings.json +6 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/templates/next-app/.env.template +1 -1
  10. package/dist/templates/next-app/README.en.md +0 -1
  11. package/dist/templates/next-app/README.md +0 -1
  12. package/dist/templates/next-app/config/Identifier/api.ts +5 -5
  13. package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
  14. package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
  15. package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
  16. package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
  17. package/dist/templates/next-app/config/Identifier/index.ts +2 -12
  18. package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
  19. package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
  20. package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
  21. package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
  22. package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
  23. package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
  24. package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
  25. package/dist/templates/next-app/config/adminNavs.ts +19 -0
  26. package/dist/templates/next-app/config/common.ts +22 -13
  27. package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
  28. package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
  29. package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
  30. package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
  31. package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
  32. package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
  33. package/dist/templates/next-app/docs/en/index.md +0 -1
  34. package/dist/templates/next-app/docs/en/project-structure.md +0 -1
  35. package/dist/templates/next-app/docs/zh/index.md +0 -1
  36. package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
  37. package/dist/templates/next-app/make/generateLocales.ts +19 -12
  38. package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
  39. package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
  40. package/dist/templates/next-app/package.json +7 -3
  41. package/dist/templates/next-app/public/locales/en.json +172 -207
  42. package/dist/templates/next-app/public/locales/zh.json +172 -207
  43. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
  44. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
  45. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
  46. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
  47. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
  48. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
  49. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
  50. package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
  51. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
  52. package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
  53. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
  54. package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
  55. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
  56. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
  57. package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
  58. package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
  59. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
  60. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
  61. package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
  62. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
  63. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
  64. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
  65. package/dist/templates/next-app/src/i18n/request.ts +30 -1
  66. package/dist/templates/next-app/src/server/PageParams.ts +2 -10
  67. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
  68. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
  69. package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
  70. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
  71. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
  72. package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
  73. package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
  74. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
  75. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
  76. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
  77. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
  78. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
  79. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
  80. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
  81. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
  82. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
  83. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
  84. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
  85. package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
  86. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
  87. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
  88. package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
  89. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
  90. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
  91. package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
  92. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
  93. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
  94. package/dist/templates/react-app/.prettierignore +17 -0
  95. package/dist/templates/react-app/README.en.md +71 -54
  96. package/dist/templates/react-app/README.md +35 -18
  97. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
  98. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
  99. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
  100. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
  101. package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
  102. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
  103. package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
  104. package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
  105. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
  106. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
  107. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
  108. package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
  109. package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
  110. package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
  111. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
  112. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
  113. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
  114. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
  115. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
  116. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
  117. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
  118. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
  119. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
  120. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
  121. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
  122. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
  123. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
  124. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
  125. package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
  126. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
  127. package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
  128. package/dist/templates/react-app/config/IOCIdentifier.ts +11 -8
  129. package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
  130. package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
  131. package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
  132. package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
  133. package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
  134. package/dist/templates/react-app/config/Identifier/index.ts +1 -9
  135. package/dist/templates/react-app/config/Identifier/pages/index.ts +9 -0
  136. package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
  137. package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
  138. package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
  139. package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
  140. package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
  141. package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
  142. package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
  143. package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
  144. package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
  145. package/dist/templates/react-app/config/app.router.ts +81 -61
  146. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
  147. package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
  148. package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
  149. package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
  150. package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
  151. package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
  152. package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
  153. package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
  154. package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
  155. package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
  156. package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
  157. package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
  158. package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
  159. package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
  160. package/dist/templates/react-app/config/theme.ts +14 -4
  161. package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
  162. package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
  163. package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
  164. package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
  165. package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
  166. package/dist/templates/react-app/docs/en/env.md +1132 -278
  167. package/dist/templates/react-app/docs/en/i18n.md +858 -147
  168. package/dist/templates/react-app/docs/en/index.md +733 -104
  169. package/dist/templates/react-app/docs/en/ioc.md +1228 -287
  170. package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
  171. package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
  172. package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
  173. package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
  174. package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
  175. package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
  176. package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
  177. package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
  178. package/dist/templates/react-app/docs/en/store.md +1194 -184
  179. package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
  180. package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
  181. package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
  182. package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
  183. package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
  184. package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
  185. package/dist/templates/react-app/docs/zh/env.md +1132 -275
  186. package/dist/templates/react-app/docs/zh/i18n.md +858 -147
  187. package/dist/templates/react-app/docs/zh/index.md +717 -104
  188. package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
  189. package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
  190. package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
  191. package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
  192. package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
  193. package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
  194. package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
  195. package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
  196. package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
  197. package/dist/templates/react-app/docs/zh/store.md +1192 -184
  198. package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
  199. package/dist/templates/react-app/e2e/App.spec.ts +319 -0
  200. package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
  201. package/dist/templates/react-app/e2e/main.spec.ts +20 -0
  202. package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
  203. package/dist/templates/react-app/eslint.config.mjs +247 -0
  204. package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
  205. package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
  206. package/dist/templates/react-app/package.json +31 -3
  207. package/dist/templates/react-app/playwright.config.ts +79 -0
  208. package/dist/templates/react-app/public/locales/en/common.json +233 -179
  209. package/dist/templates/react-app/public/locales/zh/common.json +233 -179
  210. package/dist/templates/react-app/src/App.tsx +15 -42
  211. package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
  212. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
  213. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  214. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
  215. package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
  216. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
  217. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  218. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
  219. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
  220. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
  221. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
  222. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
  223. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
  224. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
  225. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
  226. package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
  227. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
  228. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
  229. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
  230. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
  231. package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
  232. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
  233. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
  234. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
  235. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
  236. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
  237. package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
  238. package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
  239. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
  240. package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
  241. package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
  242. package/dist/templates/react-app/src/base/types/Page.ts +14 -2
  243. package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
  244. package/dist/templates/react-app/src/core/IOC.ts +5 -46
  245. package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
  246. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
  247. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
  248. package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  249. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
  250. package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
  251. package/dist/templates/react-app/src/core/globals.ts +9 -9
  252. package/dist/templates/react-app/src/main.tsx +4 -4
  253. package/dist/templates/react-app/src/pages/404.tsx +6 -3
  254. package/dist/templates/react-app/src/pages/500.tsx +5 -2
  255. package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
  256. package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
  257. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
  258. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
  259. package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
  260. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
  261. package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
  262. package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
  263. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
  264. package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
  265. package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
  266. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
  267. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
  268. package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
  269. package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
  270. package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
  271. package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
  272. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
  273. package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  274. package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  275. package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
  276. package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  277. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
  278. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
  279. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  280. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
  281. package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
  282. package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
  283. package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
  284. package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
  285. package/dist/templates/react-app/src/styles/css/page.css +4 -3
  286. package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
  287. package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
  288. package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
  289. package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
  290. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
  291. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
  292. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
  293. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
  294. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
  295. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
  296. package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
  297. package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
  298. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
  299. package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
  300. package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
  301. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
  302. package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
  303. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
  304. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
  305. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
  306. package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
  307. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
  308. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
  309. package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
  310. package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
  311. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
  312. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
  313. package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
  314. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
  315. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
  316. package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
  317. package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
  318. package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
  319. package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
  320. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
  321. package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
  322. package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
  323. package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
  324. package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
  325. package/dist/templates/react-app/tsconfig.e2e.json +21 -0
  326. package/dist/templates/react-app/tsconfig.json +8 -1
  327. package/dist/templates/react-app/tsconfig.node.json +1 -1
  328. package/dist/templates/react-app/tsconfig.test.json +3 -1
  329. package/dist/templates/react-app/vite.config.ts +50 -34
  330. package/package.json +2 -1
  331. package/dist/configs/react-app/eslint.config.js +0 -94
  332. package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
  333. package/dist/templates/next-app/config/Identifier/common.ts +0 -69
  334. package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
  335. package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
  336. package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
  337. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
  338. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
  339. package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
  340. package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
  341. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
  342. package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
  343. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
  344. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
  345. package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
  346. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
  347. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
  348. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
  349. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
  350. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
  351. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
  352. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
  353. package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
  354. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
  355. package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
  356. package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
  357. package/dist/templates/react-app/config/i18n.ts +0 -15
  358. package/dist/templates/react-app/docs/en/project-structure.md +0 -434
  359. package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
  360. package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
  361. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
  362. package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
  363. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
@@ -1,168 +1,368 @@
1
1
  # Store 状态管理
2
2
 
3
- ## 核心思想
3
+ ## 📋 目录
4
4
 
5
- Store 的设计理念基于以下几个核心思想:
5
+ - [核心理念](#-核心理念) - 应用层通知 UI 层
6
+ - [什么是 Store](#-什么是-store) - 状态容器
7
+ - [为什么需要 Store](#-为什么需要-store) - 解决通信问题
8
+ - [核心问题](#-核心问题) - 应用层如何通知 UI 层
9
+ - [项目中的实现](#-项目中的实现) - 实战指南
10
+ - [使用方式](#-使用方式) - Service + Store + useStore
11
+ - [测试](#-测试) - 独立测试和组合测试
12
+ - [最佳实践](#-最佳实践) - 7 条核心实践
13
+ - [常见问题](#-常见问题) - FAQ
6
14
 
7
- 1. **逻辑与 UI 分离**
8
- - 业务逻辑集中在 Store 中管理
9
- - UI 组件只负责渲染和用户交互
10
- - 通过 IOC 容器实现逻辑的依赖注入
15
+ ---
11
16
 
12
- 2. **响应式数据流**
13
- - 基于发布订阅模式
14
- - 状态变更自动触发 UI 更新
15
- - 精确的组件重渲染控制
17
+ ## 🎯 核心理念
16
18
 
17
- 3. **状态分片管理**
18
- - 将复杂状态分解为独立的分片
19
- - 每个分片负责特定的业务领域
20
- - 分片之间可以组合和通信
19
+ > **🚨 核心问题:应用层(Service)如何通知 UI 层更新,同时保持分离?**
21
20
 
22
- ## 工作原理
21
+ > **⭐ 解决方案:Service 包含 Store,通过 `emit` 发布状态,UI 通过 `useStore` 订阅状态!**
23
22
 
24
- ### 1. 状态订阅机制
23
+ ### 核心概念
25
24
 
26
- ```typescript
27
- // Store 内部实现了发布订阅机制
28
- class SliceStore<T> {
29
- private listeners = new Set<(state: T) => void>();
25
+ ```
26
+ ┌──────────────────────────────────────────────┐
27
+ │ 问题:UI 和逻辑已经分离了,但如何通信? │
28
+ │ │
29
+ │ Service (应用层) │
30
+ │ ├── 业务逻辑 │
31
+ │ └── 数据处理 │
32
+ │ ↓ 如何通知? │
33
+ │ Component (UI 层) │
34
+ │ └── UI 渲染 │
35
+ │ │
36
+ │ ❌ 问题:Service 改变了数据,UI 如何知道? │
37
+ └──────────────────────────────────────────────┘
38
+
39
+ ┌──────────────────────────────────────────────┐
40
+ │ 解决方案:Store 作为桥梁 │
41
+ │ │
42
+ │ Service (应用层) │
43
+ │ ├── 业务逻辑 │
44
+ │ ├── Store (状态容器) │
45
+ │ │ ├── state (状态) │
46
+ │ │ └── emit() (发布状态) │
47
+ │ │ │
48
+ │ │ ↓ 发布订阅模式 │
49
+ │ │ │
50
+ │ └── useStore (订阅) │
51
+ │ ↓ │
52
+ │ Component (UI 层) │
53
+ │ └── 自动更新 UI │
54
+ │ │
55
+ │ ✅ Service 通过 emit 发布状态 │
56
+ │ ✅ UI 通过 useStore 订阅状态 │
57
+ │ ✅ 保持分离,解耦合 │
58
+ └──────────────────────────────────────────────┘
59
+ ```
30
60
 
31
- // 发布状态更新
32
- protected emit(newState: T) {
33
- this.state = newState;
34
- this.listeners.forEach((listener) => listener(this.state));
35
- }
61
+ ---
36
62
 
37
- // 订阅状态变化
38
- subscribe(listener: (state: T) => void) {
39
- this.listeners.add(listener);
40
- return () => this.listeners.delete(listener);
41
- }
42
- }
63
+ ## 🗂️ 什么是 Store
64
+
65
+ Store 是一个**响应式状态容器**,基于**发布订阅模式**实现。
66
+
67
+ ### 简单理解
68
+
69
+ ```
70
+ Store = 状态 + 发布订阅
71
+
72
+ Service 拥有 Store
73
+ Service 通过 Store.emit() 发布状态
74
+ UI 通过 useStore() 订阅状态
43
75
  ```
44
76
 
45
- ### 2. 状态更新流程
77
+ ### 类比理解
46
78
 
47
79
  ```
48
- 用户操作 → 调用 Store 方法 → 更新状态 → 通知订阅者 → UI 更新
80
+ Store 就像一个广播电台:
81
+
82
+ 📻 电台(Store)
83
+ - 有节目内容(state)
84
+ - 可以广播节目(emit)
85
+ - 听众可以收听(subscribe)
86
+
87
+ 🎤 主持人(Service)
88
+ - 制作节目内容(业务逻辑)
89
+ - 通过电台广播(emit)
90
+
91
+ 📱 听众(UI Component)
92
+ - 收听电台(useStore)
93
+ - 听到新内容自动反应(自动更新 UI)
49
94
  ```
50
95
 
51
- 1. 用户触发操作(如点击按钮)
52
- 2. 调用 Store 中的方法
53
- 3. Store 使用 emit 发布新状态
54
- 4. 订阅该状态的组件收到通知
55
- 5. 组件重新渲染,显示最新状态
96
+ ---
97
+
98
+ ## 🤔 为什么需要 Store
99
+
100
+ ### 核心问题:UI 和逻辑分离后,如何通信?
101
+
102
+ 我们已经通过 IOC 实现了 UI 和逻辑分离,但问题来了:
56
103
 
57
- ### 3. 组件集成
104
+ #### 问题示例:没有 Store
58
105
 
59
- ```tsx
60
- // 在组件中使用 Store
106
+ ```typescript
107
+ // Service(逻辑层)
108
+ @injectable()
109
+ export class UserService {
110
+ private user: UserInfo | null = null;
111
+
112
+ async login(username: string, password: string) {
113
+ const response = await this.api.login({ username, password });
114
+ this.user = response.user; // ✅ 登录成功,user 已更新
115
+
116
+ // ❌ 问题:UI 如何知道 user 已经更新?
117
+ // ❌ Service 无法通知 UI
118
+ }
119
+ }
120
+
121
+ // UI 组件
61
122
  function UserProfile() {
62
- // useStore hook 自动处理订阅和取消订阅
63
- const user = useStore(IOC(UserService), (state) => state.userInfo);
123
+ const userService = useIOC('UserServiceInterface');
124
+
125
+ // ❌ 问题:如何获取 userService.user?
126
+ // ❌ userService.user 更新后,如何触发 UI 重新渲染?
64
127
 
65
- return <div>{user.name}</div>;
128
+ return <div>{/* 无法显示 user */}</div>;
66
129
  }
130
+
131
+ // 😰😰😰 问题总结:
132
+ // 1. UI 无法获取 Service 的内部状态
133
+ // 2. Service 状态更新后,UI 不知道
134
+ // 3. 需要手动调用某个方法来获取状态?(打破分离原则)
135
+ // 4. 需要轮询检查状态?(性能差)
67
136
  ```
68
137
 
69
- ### 4. 状态分片示例
138
+ #### 解决方案:使用 Store
70
139
 
71
140
  ```typescript
72
- // 用户认证分片
73
- class AuthStore extends StoreInterface<AuthState> {
141
+ // Service(逻辑层)
142
+ @injectable()
143
+ export class UserService extends StoreInterface<UserState> {
74
144
  constructor() {
75
145
  super(() => ({
76
- isLoggedIn: false,
77
- user: null
146
+ user: null,
147
+ loading: false
78
148
  }));
79
149
  }
80
150
 
81
- login(credentials: Credentials) {
82
- // 处理登录逻辑
151
+ async login(username: string, password: string) {
152
+ // 设置加载状态
153
+ this.emit({ ...this.state, loading: true });
154
+
155
+ const response = await this.api.login({ username, password });
156
+
157
+ // ✅ 通过 emit 发布新状态,自动通知所有订阅者
83
158
  this.emit({
84
- isLoggedIn: true,
85
- user: userData
159
+ user: response.user,
160
+ loading: false
86
161
  });
87
162
  }
88
163
  }
89
164
 
90
- // 主题设置分片
91
- class ThemeStore extends StoreInterface<ThemeState> {
165
+ // UI 组件
166
+ function UserProfile() {
167
+ const userService = useIOC('UserServiceInterface');
168
+
169
+ // ✅ 通过 useStore 订阅状态
170
+ const { user, loading } = useStore(userService);
171
+
172
+ // ✅ userService.emit() 时,组件会自动重新渲染
173
+
174
+ if (loading) return <div>Loading...</div>;
175
+ return <div>{user?.name}</div>;
176
+ }
177
+
178
+ // ✅✅✅ 优势总结:
179
+ // 1. UI 可以订阅 Service 的状态
180
+ // 2. Service 状态更新后,UI 自动更新
181
+ // 3. 保持分离(Service 不知道有哪些 UI 在监听)
182
+ // 4. 高性能(只有订阅的组件才会更新)
183
+ ```
184
+
185
+ ### 对比总结
186
+
187
+ | 特性 | 没有 Store | 有 Store |
188
+ | ---------------- | ---------------------- | --------------------- |
189
+ | **状态获取** | ❌ 无法获取内部状态 | ✅ 通过 useStore 订阅 |
190
+ | **状态更新通知** | ❌ UI 不知道状态变化 | ✅ emit 自动通知 |
191
+ | **UI 更新** | ❌ 需要手动触发 | ✅ 自动重新渲染 |
192
+ | **解耦** | ❌ Service 需要知道 UI | ✅ 完全解耦 |
193
+ | **性能** | ❌ 轮询或全局更新 | ✅ 精确更新订阅者 |
194
+ | **可测试性** | ❌ 难以测试状态变化 | ✅ 易于测试状态 |
195
+
196
+ ---
197
+
198
+ ## ❓ 核心问题
199
+
200
+ ### 应用层如何通知 UI 层,同时保持分离?
201
+
202
+ #### 问题拆解
203
+
204
+ 1. **应用层(Service)有状态** - 如用户信息、加载状态
205
+ 2. **UI 层需要显示这些状态** - 显示用户名、显示加载动画
206
+ 3. **应用层状态会变化** - 登录成功后,用户信息更新
207
+ 4. **UI 层需要自动更新** - 用户信息变化后,UI 自动显示新名字
208
+ 5. **保持分离** - Service 不应该直接操作 UI,UI 不应该直接访问 Service 内部
209
+
210
+ #### 解决方案:发布订阅模式
211
+
212
+ ```typescript
213
+ // 1. Service 定义状态
214
+ interface UserState {
215
+ user: UserInfo | null;
216
+ loading: boolean;
217
+ }
218
+
219
+ // 2. Service 继承 StoreInterface
220
+ @injectable()
221
+ export class UserService extends StoreInterface<UserState> {
92
222
  constructor() {
93
223
  super(() => ({
94
- mode: 'light',
95
- colors: defaultColors
224
+ user: null,
225
+ loading: false
96
226
  }));
97
227
  }
98
228
 
99
- toggleTheme() {
100
- const mode = this.state.mode === 'light' ? 'dark' : 'light';
229
+ // 3. Service 通过 emit 发布状态
230
+ async login(username: string, password: string) {
231
+ this.emit({ ...this.state, loading: true }); // 发布:开始加载
232
+
233
+ const response = await this.api.login({ username, password });
234
+
101
235
  this.emit({
102
- ...this.state,
103
- mode
104
- });
236
+ user: response.user,
237
+ loading: false
238
+ }); // 发布:加载完成,用户已登录
105
239
  }
240
+
241
+ // 4. Service 不需要知道谁在监听
242
+ // ✅ 完全解耦
243
+ }
244
+
245
+ // 5. UI 通过 useStore 订阅状态
246
+ function LoginPage() {
247
+ const userService = useIOC('UserServiceInterface');
248
+ const { loading } = useStore(userService);
249
+
250
+ const handleLogin = () => {
251
+ userService.login('user', 'pass');
252
+ };
253
+
254
+ // 6. 当 Service emit 新状态时,UI 自动更新
255
+ return (
256
+ <button onClick={handleLogin} disabled={loading}>
257
+ {loading ? 'Logging in...' : 'Login'}
258
+ </button>
259
+ );
106
260
  }
107
261
  ```
108
262
 
109
- ## 概述
263
+ #### 工作流程
110
264
 
111
- Store 是应用的状态管理解决方案,基于 `@qlover/slice-store-react` 实现。它采用分片(Slice)的方式来管理状态,具有以下特点:
265
+ ```
266
+ ┌─────────────────────────────────────────────┐
267
+ │ 完整的状态更新流程 │
268
+ │ │
269
+ │ 1. 用户点击按钮 │
270
+ │ ↓ │
271
+ │ 2. UI 调用 Service 方法 │
272
+ │ userService.login() │
273
+ │ ↓ │
274
+ │ 3. Service 执行业务逻辑 │
275
+ │ - 调用 API │
276
+ │ - 处理数据 │
277
+ │ ↓ │
278
+ │ 4. Service 通过 emit 发布新状态 │
279
+ │ this.emit({ user: ..., loading: false })│
280
+ │ ↓ │
281
+ │ 5. Store 通知所有订阅者 │
282
+ │ listeners.forEach(listener => ...) │
283
+ │ ↓ │
284
+ │ 6. useStore 收到通知 │
285
+ │ 触发组件重新渲染 │
286
+ │ ↓ │
287
+ │ 7. UI 显示最新状态 │
288
+ │ 显示用户名 / 隐藏加载动画 │
289
+ └─────────────────────────────────────────────┘
290
+ ```
112
291
 
113
- - **类型安全**:基于 TypeScript,提供完整的类型推导
114
- - **轻量级**:无需复杂的配置,易于使用
115
- - **高性能**:精确的组件更新,避免不必要的渲染
116
- - **模块化**:支持状态分片,便于管理大型应用
117
- - **IOC 集成**:与依赖注入系统完美配合
292
+ ---
118
293
 
119
- ## 核心概念
294
+ ## 🛠️ 项目中的实现
120
295
 
121
- ### 1. Store 接口
296
+ ### 1. 文件结构
122
297
 
123
- Store 系统基于两个核心接口:
298
+ ```
299
+ src/
300
+ ├── base/
301
+ │ ├── services/
302
+ │ │ ├── UserService.ts # Service 继承 StoreInterface
303
+ │ │ ├── RouteService.ts # Service 继承 StoreInterface
304
+ │ │ └── I18nService.ts # Service 继承 StoreInterface
305
+ │ └── port/
306
+ │ └── UserServiceInterface.ts # Service 接口
307
+ └── uikit/
308
+ └── hooks/
309
+ └── useStore.ts (from @brain-toolkit/react-kit)
310
+ ```
311
+
312
+ ### 2. Store 基类
124
313
 
125
- #### StoreStateInterface
314
+ Store 系统基于 `@brain-toolkit/react-kit` 的 `SliceStore`:
126
315
 
127
316
  ```typescript
128
- /**
129
- * Store 状态接口
130
- *
131
- * 作用:定义 store 状态对象的契约
132
- * 核心思想:为 store 状态强制实施一致的结构
133
- * 主要功能:作为所有 store 状态类型的基础
134
- * 主要目的:确保状态类型安全和可扩展性
135
- */
136
- interface StoreStateInterface {
137
- // 可以在这里定义自己的属性
138
- // ...
317
+ // 来自 @brain-toolkit/react-kit
318
+ export class SliceStore<T> {
319
+ protected state: T;
320
+ private listeners = new Set<(state: T) => void>();
321
+
322
+ constructor(stateFactory: () => T) {
323
+ this.state = stateFactory();
324
+ }
325
+
326
+ // 发布状态
327
+ protected emit(newState: T) {
328
+ this.state = newState;
329
+ // 通知所有订阅者
330
+ this.listeners.forEach((listener) => listener(this.state));
331
+ }
332
+
333
+ // 订阅状态
334
+ subscribe(listener: (state: T) => void) {
335
+ this.listeners.add(listener);
336
+ // 返回取消订阅函数
337
+ return () => this.listeners.delete(listener);
338
+ }
339
+
340
+ // 获取当前状态
341
+ getState(): T {
342
+ return this.state;
343
+ }
139
344
  }
140
345
  ```
141
346
 
142
- #### StoreInterface
347
+ ### 3. StoreInterface 基类
348
+
349
+ 项目中的 Store 基类,提供额外的工具方法:
143
350
 
144
351
  ```typescript
145
- /**
146
- * Store 接口
147
- *
148
- * 作用:所有状态存储的抽象基类
149
- * 核心思想:提供统一的状态管理 API,包含重置和克隆辅助方法
150
- * 主要功能:扩展 SliceStore,添加 resetState 和 cloneState 工具方法
151
- * 主要目的:简化 store 实现并确保一致性
152
- */
153
- abstract class StoreInterface<
352
+ // 来自 @qlover/corekit-bridge
353
+ export abstract class StoreInterface<
154
354
  T extends StoreStateInterface
155
355
  > extends SliceStore<T> {
156
356
  constructor(protected stateFactory: () => T) {
157
357
  super(stateFactory);
158
358
  }
159
359
 
160
- // 重置 store 状态
360
+ // 重置状态
161
361
  resetState(): void {
162
362
  this.emit(this.stateFactory());
163
363
  }
164
364
 
165
- // 克隆 store 状态
365
+ // 克隆状态(用于更新)
166
366
  cloneState(source?: Partial<T>): T {
167
367
  const cloned = clone(this.state);
168
368
  if (typeof cloned === 'object' && cloned !== null) {
@@ -173,149 +373,957 @@ abstract class StoreInterface<
173
373
  }
174
374
  ```
175
375
 
176
- ### 2. 状态分片
376
+ ### 4. 状态接口
377
+
378
+ ```typescript
379
+ // 所有状态必须实现此接口
380
+ export interface StoreStateInterface {
381
+ // 可以在这里定义通用属性
382
+ // loading?: boolean;
383
+ // error?: Error | null;
384
+ }
385
+ ```
177
386
 
178
- 状态分片(Slice)是将应用状态分割成独立的部分:
387
+ ---
388
+
389
+ ## 📝 使用方式
390
+
391
+ ### 1. 定义状态接口
179
392
 
180
393
  ```typescript
181
- // 用户状态分片示例
182
- class UserState implements StoreStateInterface {
183
- isLoggedIn: boolean = false;
184
- userInfo: {
185
- name: string;
186
- role: string;
187
- } | null = null;
394
+ // src/base/services/UserService.ts
395
+ export interface UserState extends StoreStateInterface {
396
+ user: UserInfo | null;
397
+ loading: boolean;
398
+ error: Error | null;
188
399
  }
400
+ ```
189
401
 
190
- // Store 实现示例
191
- export class UserStore extends StoreInterface<UserState> {
192
- constructor() {
193
- super(() => new UserState());
402
+ ### 2. Service 继承 StoreInterface
403
+
404
+ ```typescript
405
+ // src/base/services/UserService.ts
406
+ import { StoreInterface } from '@qlover/corekit-bridge';
407
+ import { injectable, inject } from 'inversify';
408
+
409
+ @injectable()
410
+ export class UserService extends StoreInterface<UserState> {
411
+ constructor(
412
+ @inject(UserApi) private api: UserApi,
413
+ @inject(IOCIdentifier.AppConfig) private config: AppConfig
414
+ ) {
415
+ // 初始化状态
416
+ super(() => ({
417
+ user: null,
418
+ loading: false,
419
+ error: null
420
+ }));
421
+ }
422
+
423
+ // 业务方法:通过 emit 发布状态
424
+ async login(username: string, password: string) {
425
+ // 1. 开始加载
426
+ this.emit({
427
+ ...this.state,
428
+ loading: true,
429
+ error: null
430
+ });
431
+
432
+ try {
433
+ // 2. 调用 API
434
+ const response = await this.api.login({ username, password });
435
+
436
+ // 3. 成功:发布新状态
437
+ this.emit({
438
+ user: response.user,
439
+ loading: false,
440
+ error: null
441
+ });
442
+ } catch (error) {
443
+ // 4. 失败:发布错误状态
444
+ this.emit({
445
+ ...this.state,
446
+ loading: false,
447
+ error: error as Error
448
+ });
449
+ }
450
+ }
451
+
452
+ async logout() {
453
+ this.emit({
454
+ user: null,
455
+ loading: false,
456
+ error: null
457
+ });
458
+ }
459
+
460
+ // 使用 cloneState 简化更新
461
+ setUser(user: UserInfo) {
462
+ this.emit(this.cloneState({ user }));
194
463
  }
195
464
  }
196
465
  ```
197
466
 
198
- ## 在项目中使用
467
+ ### 3. UI 订阅状态
199
468
 
200
- ### 1. 创建 Store Controller
469
+ ```typescript
470
+ // src/pages/LoginPage.tsx
471
+ import { useStore } from '@brain-toolkit/react-kit/hooks/useStore';
472
+ import { useIOC } from '@/uikit/hooks/useIOC';
473
+
474
+ function LoginPage() {
475
+ const userService = useIOC('UserServiceInterface');
476
+
477
+ // ✅ 方式 1:订阅完整状态
478
+ const { user, loading, error } = useStore(userService);
479
+
480
+ const handleLogin = async () => {
481
+ await userService.login('username', 'password');
482
+ };
483
+
484
+ if (loading) {
485
+ return <div>Loading...</div>;
486
+ }
487
+
488
+ return (
489
+ <div>
490
+ {error && <div>Error: {error.message}</div>}
491
+ <button onClick={handleLogin}>Login</button>
492
+ </div>
493
+ );
494
+ }
495
+ ```
496
+
497
+ ### 4. 使用选择器(性能优化)
201
498
 
202
499
  ```typescript
203
- import { StoreInterface, StoreStateInterface } from '@qlover/corekit-bridge';
500
+ // src/pages/UserProfile.tsx
501
+ function UserProfile() {
502
+ const userService = useIOC('UserServiceInterface');
204
503
 
205
- interface ExecutorState extends StoreStateInterface {
206
- helloState: string;
207
- tasks: Task[];
504
+ // 方式 2:只订阅需要的状态(性能更好)
505
+ const user = useStore(userService, (state) => state.user);
506
+
507
+ // ✅ 只有 user 变化时才重新渲染,loading 变化不会触发
508
+
509
+ return <div>{user?.name}</div>;
208
510
  }
511
+ ```
209
512
 
513
+ ### 5. 定义选择器(推荐)
514
+
515
+ ```typescript
516
+ // src/base/services/UserService.ts
210
517
  @injectable()
211
- export class ExecutorController extends StoreInterface<ExecutorState> {
212
- constructor() {
213
- super(() => ({
214
- helloState: '',
215
- tasks: []
216
- }));
217
- }
518
+ export class UserService extends StoreInterface<UserState> {
519
+ // ... 其他代码
218
520
 
219
- // 选择器
521
+ // ✅ 定义选择器
220
522
  selector = {
221
- helloState: (state: ExecutorState) => state.helloState,
222
- tasks: (state: ExecutorState) => state.tasks
523
+ user: (state: UserState) => state.user,
524
+ loading: (state: UserState) => state.loading,
525
+ error: (state: UserState) => state.error,
526
+ isLoggedIn: (state: UserState) => state.user !== null
223
527
  };
224
528
  }
529
+
530
+ // 使用
531
+ function UserProfile() {
532
+ const userService = useIOC('UserServiceInterface');
533
+
534
+ // ✅ 使用预定义的选择器
535
+ const user = useStore(userService, userService.selector.user);
536
+ const isLoggedIn = useStore(userService, userService.selector.isLoggedIn);
537
+
538
+ return <div>{isLoggedIn ? user?.name : 'Please login'}</div>;
539
+ }
225
540
  ```
226
541
 
227
- ### 2. 在组件中使用
542
+ ### 6. 实际项目示例
228
543
 
229
- 使用 `useStore` Hook 访问状态:
544
+ #### 示例 1:UserService
230
545
 
231
- ```tsx
232
- function MyComponent() {
233
- // 获取完整状态
234
- const state = useStore(IOC(ExecutorController));
546
+ ```typescript
547
+ // src/base/services/UserService.ts
548
+ @injectable()
549
+ export class UserService extends UserAuthServiceInterface {
550
+ constructor(
551
+ @inject(UserApi) userApi: UserApi,
552
+ @inject(IOCIdentifier.AppConfig) appConfig: AppConfig,
553
+ @inject(IOCIdentifier.LocalStorageEncrypt) storage: Storage
554
+ ) {
555
+ super(userApi, {
556
+ userStorage: {
557
+ key: appConfig.userInfoStorageKey,
558
+ storage: storage
559
+ },
560
+ credentialStorage: {
561
+ key: appConfig.userTokenStorageKey,
562
+ storage: storage
563
+ }
564
+ });
565
+ }
235
566
 
236
- // 使用选择器获取特定状态
237
- const helloState = useStore(
238
- IOC(ExecutorController),
239
- (controller) => controller.selector.helloState
240
- );
567
+ // ✅ UserService 继承的基类包含 store
568
+ override get store(): UserAuthStore<UserApiState> {
569
+ return super.store as UserAuthStore<UserApiState>;
570
+ }
571
+
572
+ override async logout(): Promise<void> {
573
+ await super.logout();
574
+ // ✅ store 会自动通知 UI
575
+ this.routerService.gotoLogin();
576
+ }
577
+ }
578
+
579
+ // 使用
580
+ function Layout() {
581
+ const userService = useIOC(IOCIdentifier.UserServiceInterface);
582
+
583
+ // ✅ 订阅 userService.store
584
+ useStore(userService.store);
585
+
586
+ if (userService.isAuthenticated()) {
587
+ return <Navigate to="/" replace />;
588
+ }
589
+
590
+ return <Outlet />;
591
+ }
592
+ ```
593
+
594
+ #### 示例 2:RouteService
595
+
596
+ ```typescript
597
+ // src/base/services/RouteService.ts
598
+ export class RouteService extends StoreInterface<RouterServiceState> {
599
+ constructor(
600
+ protected uiBridge: UIBridgeInterface<NavigateFunction>,
601
+ protected i18nService: I18nServiceInterface,
602
+ protected options: RouterServiceOptions
603
+ ) {
604
+ super(
605
+ () => new RouterServiceState(options.routes, !!options.hasLocalRoutes)
606
+ );
607
+ }
608
+
609
+ // ✅ 通过 emit 发布路由变化
610
+ override changeRoutes(routes: RouteConfigValue[]): void {
611
+ this.emit(this.cloneState({ routes }));
612
+ }
613
+
614
+ override goto(path: string, options?: NavigateOptions): void {
615
+ const composedPath = this.composePath(path);
616
+ this.uiBridge.getUIBridge()(composedPath, options);
617
+ }
618
+ }
619
+
620
+ // 使用
621
+ function AppRouterProvider() {
622
+ const routerService = useIOC(IOCIdentifier.RouteServiceInterface);
623
+
624
+ // ✅ 订阅 routes 变化
625
+ const routes = useStore(routerService, (state) => state.routes);
626
+
627
+ const router = createBrowserRouter(routes);
628
+
629
+ return <RouterProvider router={router} />;
630
+ }
631
+ ```
632
+
633
+ #### 示例 3:I18nService
634
+
635
+ ```typescript
636
+ // src/base/services/I18nService.ts
637
+ export class I18nService extends StoreInterface<I18nServiceState> {
638
+ constructor(protected pathname: string) {
639
+ super(() => new I18nServiceState(i18n.language as I18nServiceLocale));
640
+ }
641
+
642
+ selector = {
643
+ loading: (state: I18nServiceState) => state.loading,
644
+ language: (state: I18nServiceState) => state.language
645
+ };
646
+
647
+ override async changeLanguage(lng: string): Promise<void> {
648
+ // ✅ 发布加载状态
649
+ this.emit(this.cloneState({ loading: true }));
650
+
651
+ await i18n.changeLanguage(lng);
652
+
653
+ // ✅ 发布完成状态
654
+ this.emit({
655
+ language: lng as I18nServiceLocale,
656
+ loading: false
657
+ });
658
+ }
659
+ }
660
+
661
+ // 使用
662
+ function LanguageSwitcher() {
663
+ const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
664
+
665
+ // ✅ 只订阅 loading 状态
666
+ const loading = useStore(i18nService, i18nService.selector.loading);
241
667
 
242
668
  return (
243
- <div>
244
- <h1>{helloState}</h1>
245
- </div>
669
+ <Select
670
+ value={i18n.language}
671
+ loading={loading}
672
+ onChange={(lng) => i18nService.changeLanguage(lng)}
673
+ />
246
674
  );
247
675
  }
248
676
  ```
249
677
 
250
- ### 3. 更新状态
678
+ ---
679
+
680
+ ## 🧪 测试
681
+
682
+ ### 核心优势:Store 可以独立测试,UI 可以 mock Store
251
683
 
252
- 通过 controller 方法更新状态:
684
+ #### 1. 测试 Service 和 Store(逻辑测试)
253
685
 
254
686
  ```typescript
687
+ // __tests__/src/base/services/UserService.test.ts
688
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
689
+ import { UserService } from '@/base/services/UserService';
690
+
691
+ describe('UserService (逻辑测试)', () => {
692
+ let userService: UserService;
693
+ let mockApi: any;
694
+
695
+ beforeEach(() => {
696
+ mockApi = {
697
+ login: vi.fn(),
698
+ getUserInfo: vi.fn()
699
+ };
700
+
701
+ userService = new UserService(mockApi, mockConfig, mockStorage);
702
+ });
703
+
704
+ it('should update store state when login success', async () => {
705
+ // ✅ 测试状态变化
706
+ mockApi.login.mockResolvedValue({
707
+ user: { name: 'John', id: 1 },
708
+ token: 'test-token'
709
+ });
710
+
711
+ // 订阅状态变化
712
+ const states: any[] = [];
713
+ userService.subscribe((state) => {
714
+ states.push({ ...state });
715
+ });
716
+
717
+ // 调用登录
718
+ await userService.login('user', 'pass');
719
+
720
+ // ✅ 验证状态变化序列
721
+ expect(states).toHaveLength(2);
722
+
723
+ // 第一次 emit:loading = true
724
+ expect(states[0]).toEqual({
725
+ user: null,
726
+ loading: true,
727
+ error: null
728
+ });
729
+
730
+ // 第二次 emit:loading = false, user = John
731
+ expect(states[1]).toEqual({
732
+ user: { name: 'John', id: 1 },
733
+ loading: false,
734
+ error: null
735
+ });
736
+ });
737
+
738
+ it('should update store state when login fails', async () => {
739
+ mockApi.login.mockRejectedValue(new Error('Invalid credentials'));
740
+
741
+ const states: any[] = [];
742
+ userService.subscribe((state) => states.push({ ...state }));
743
+
744
+ await expect(userService.login('user', 'wrong')).rejects.toThrow();
745
+
746
+ // ✅ 验证错误状态
747
+ expect(states[1]).toEqual({
748
+ user: null,
749
+ loading: false,
750
+ error: expect.any(Error)
751
+ });
752
+ });
753
+
754
+ it('should emit logout state', () => {
755
+ // 先设置用户登录
756
+ userService.emit({
757
+ user: { name: 'John', id: 1 },
758
+ loading: false,
759
+ error: null
760
+ });
761
+
762
+ // 登出
763
+ userService.logout();
764
+
765
+ // ✅ 验证状态被重置
766
+ expect(userService.getState()).toEqual({
767
+ user: null,
768
+ loading: false,
769
+ error: null
770
+ });
771
+ });
772
+ });
773
+
774
+ // ✅✅✅ 优势:
775
+ // 1. 不需要渲染 UI
776
+ // 2. 可以测试所有状态变化
777
+ // 3. 可以验证 emit 的调用序列
778
+ // 4. 测试运行快速
779
+ ```
780
+
781
+ #### 2. 测试 UI 组件(UI 测试)
782
+
783
+ ```typescript
784
+ // __tests__/src/pages/LoginPage.test.tsx
785
+ import { describe, it, expect, vi } from 'vitest';
786
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
787
+ import { LoginPage } from '@/pages/LoginPage';
788
+ import { IOCProvider } from '@/uikit/contexts/IOCContext';
789
+
790
+ describe('LoginPage (UI 测试)', () => {
791
+ it('should show loading when login', async () => {
792
+ // ✅ Mock Service 和 Store
793
+ const mockStore = {
794
+ user: null,
795
+ loading: false,
796
+ error: null
797
+ };
798
+
799
+ const mockUserService = {
800
+ login: vi.fn().mockImplementation(() => {
801
+ // 模拟状态变化
802
+ mockStore.loading = true;
803
+ return Promise.resolve();
804
+ }),
805
+ subscribe: vi.fn(),
806
+ getState: () => mockStore
807
+ };
808
+
809
+ const mockIOC = (identifier: string) => {
810
+ if (identifier === 'UserServiceInterface') return mockUserService;
811
+ };
812
+
813
+ // ✅ 渲染组件
814
+ const { rerender } = render(
815
+ <IOCProvider value={mockIOC}>
816
+ <LoginPage />
817
+ </IOCProvider>
818
+ );
819
+
820
+ // 点击登录按钮
821
+ const loginButton = screen.getByText('Login');
822
+ fireEvent.click(loginButton);
823
+
824
+ // ✅ 验证 Service 被调用
825
+ expect(mockUserService.login).toHaveBeenCalled();
826
+
827
+ // 模拟状态更新
828
+ mockStore.loading = true;
829
+ rerender(
830
+ <IOCProvider value={mockIOC}>
831
+ <LoginPage />
832
+ </IOCProvider>
833
+ );
834
+
835
+ // ✅ 验证 UI 显示加载状态
836
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
837
+ });
838
+
839
+ it('should show error message when login fails', () => {
840
+ const mockStore = {
841
+ user: null,
842
+ loading: false,
843
+ error: new Error('Invalid credentials')
844
+ };
845
+
846
+ const mockUserService = {
847
+ login: vi.fn(),
848
+ subscribe: vi.fn(),
849
+ getState: () => mockStore
850
+ };
851
+
852
+ const mockIOC = (identifier: string) => {
853
+ if (identifier === 'UserServiceInterface') return mockUserService;
854
+ };
855
+
856
+ render(
857
+ <IOCProvider value={mockIOC}>
858
+ <LoginPage />
859
+ </IOCProvider>
860
+ );
861
+
862
+ // ✅ 验证错误消息显示
863
+ expect(screen.getByText('Error: Invalid credentials')).toBeInTheDocument();
864
+ });
865
+ });
866
+
867
+ // ✅✅✅ 优势:
868
+ // 1. 不需要真实的 Service 实现
869
+ // 2. 可以轻松模拟各种状态
870
+ // 3. UI 测试专注于 UI 逻辑
871
+ ```
872
+
873
+ #### 3. 组合测试(集成测试)
874
+
875
+ ```typescript
876
+ // __tests__/src/integration/UserLogin.test.tsx
877
+ import { describe, it, expect } from 'vitest';
878
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
879
+ import { LoginPage } from '@/pages/LoginPage';
880
+ import { UserService } from '@/base/services/UserService';
881
+ import { IOCProvider } from '@/uikit/contexts/IOCContext';
882
+
883
+ describe('User Login Flow (组合测试)', () => {
884
+ it('should complete login flow', async () => {
885
+ // ✅ 使用真实的 Service 和 Store
886
+ const mockApi = {
887
+ login: vi.fn().mockResolvedValue({
888
+ user: { name: 'John', id: 1 },
889
+ token: 'test-token'
890
+ })
891
+ };
892
+
893
+ const userService = new UserService(mockApi, mockConfig, mockStorage);
894
+
895
+ const mockIOC = (identifier: string) => {
896
+ if (identifier === 'UserServiceInterface') return userService;
897
+ };
898
+
899
+ // ✅ 渲染真实 UI
900
+ render(
901
+ <IOCProvider value={mockIOC}>
902
+ <LoginPage />
903
+ </IOCProvider>
904
+ );
905
+
906
+ // ✅ 模拟用户操作
907
+ const loginButton = screen.getByText('Login');
908
+ fireEvent.click(loginButton);
909
+
910
+ // ✅ 验证加载状态
911
+ await waitFor(() => {
912
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
913
+ });
914
+
915
+ // ✅ 验证登录成功
916
+ await waitFor(() => {
917
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
918
+ expect(userService.getState().user).toEqual({
919
+ name: 'John',
920
+ id: 1
921
+ });
922
+ });
923
+
924
+ // ✅ 验证 API 被调用
925
+ expect(mockApi.login).toHaveBeenCalled();
926
+ });
927
+ });
928
+
929
+ // ✅✅✅ 优势:
930
+ // 1. 测试真实的用户流程
931
+ // 2. 验证 Service 和 UI 的集成
932
+ // 3. 发现集成问题
933
+ ```
934
+
935
+ ### 测试策略总结
936
+
937
+ ```
938
+ ┌────────────────────────────────────────┐
939
+ │ 测试金字塔 │
940
+ │ │
941
+ │ △ UI 测试 (10%) │
942
+ │ ╱ ╲ │
943
+ │ ╱ ╲ 组合测试 (20%) │
944
+ │ ╱ ╲ │
945
+ │ ╱───────╲ │
946
+ │ ╱ ╲ Store + Service 测试 (70%) │
947
+ │╱═══════════╲ │
948
+ │ │
949
+ │ Store 测试:测试状态变化逻辑 │
950
+ │ 组合测试:测试 Service + UI 集成 │
951
+ │ UI 测试:测试 UI 交互 │
952
+ └────────────────────────────────────────┘
953
+ ```
954
+
955
+ ---
956
+
957
+ ## 💎 最佳实践
958
+
959
+ ### 1. ✅ Service 继承 StoreInterface
960
+
961
+ ```typescript
962
+ // ✅ 好的做法:Service 继承 StoreInterface
255
963
  @injectable()
256
- class ExecutorController extends StoreInterface<ExecutorState> {
257
- // ... 构造函数等其他代码
964
+ export class UserService extends StoreInterface<UserState> {
965
+ constructor() {
966
+ super(() => ({
967
+ user: null,
968
+ loading: false
969
+ }));
970
+ }
258
971
 
259
- updateHelloState(newState: string) {
260
- this.emit({ ...this.state, helloState: newState });
972
+ async login(username: string, password: string) {
973
+ this.emit({ ...this.state, loading: true });
974
+ // ...
261
975
  }
976
+ }
977
+
978
+ // ❌ 不好的做法:Service 不继承 StoreInterface
979
+ @injectable()
980
+ export class UserService {
981
+ private user: UserInfo | null = null;
982
+
983
+ // 问题:UI 无法订阅状态
984
+ }
985
+ ```
986
+
987
+ ### 2. ✅ 使用 emit 发布状态
988
+
989
+ ```typescript
990
+ // ✅ 好的做法:通过 emit 发布状态
991
+ async login(username: string, password: string) {
992
+ this.emit({ ...this.state, loading: true });
993
+
994
+ const response = await this.api.login({ username, password });
995
+
996
+ this.emit({
997
+ user: response.user,
998
+ loading: false
999
+ });
1000
+ }
1001
+
1002
+ // ❌ 不好的做法:直接修改 state
1003
+ async login(username: string, password: string) {
1004
+ this.state.loading = true; // ❌ 不会通知订阅者
1005
+
1006
+ const response = await this.api.login({ username, password });
1007
+
1008
+ this.state.user = response.user; // ❌ 不会通知订阅者
1009
+ }
1010
+ ```
1011
+
1012
+ ### 3. ✅ 使用 cloneState 简化更新
1013
+
1014
+ ```typescript
1015
+ // ✅ 好的做法:使用 cloneState
1016
+ setUser(user: UserInfo) {
1017
+ this.emit(this.cloneState({ user }));
1018
+ }
1019
+
1020
+ setLoading(loading: boolean) {
1021
+ this.emit(this.cloneState({ loading }));
1022
+ }
1023
+
1024
+ // ⚠️ 也可以:手动展开
1025
+ setUser(user: UserInfo) {
1026
+ this.emit({ ...this.state, user });
1027
+ }
1028
+ ```
1029
+
1030
+ ### 4. ✅ 定义选择器
1031
+
1032
+ ```typescript
1033
+ // ✅ 好的做法:定义选择器
1034
+ @injectable()
1035
+ export class UserService extends StoreInterface<UserState> {
1036
+ selector = {
1037
+ user: (state: UserState) => state.user,
1038
+ loading: (state: UserState) => state.loading,
1039
+ isLoggedIn: (state: UserState) => state.user !== null
1040
+ };
1041
+ }
1042
+
1043
+ // 使用
1044
+ const isLoggedIn = useStore(userService, userService.selector.isLoggedIn);
1045
+
1046
+ // ❌ 不好的做法:内联选择器
1047
+ const isLoggedIn = useStore(userService, (state) => state.user !== null);
1048
+ // 问题:每次渲染都创建新函数
1049
+ ```
262
1050
 
263
- async fetchTasks() {
264
- const tasks = await api.getTasks();
265
- this.emit({ ...this.state, tasks });
1051
+ ### 5. ✅ 使用选择器优化性能
1052
+
1053
+ ```typescript
1054
+ // ✅ 好的做法:只订阅需要的状态
1055
+ function UserName() {
1056
+ const userService = useIOC('UserServiceInterface');
1057
+
1058
+ // 只订阅 user,loading 变化不会触发重新渲染
1059
+ const user = useStore(userService, (state) => state.user);
1060
+
1061
+ return <span>{user?.name}</span>;
1062
+ }
1063
+
1064
+ // ❌ 不好的做法:订阅完整状态
1065
+ function UserName() {
1066
+ const userService = useIOC('UserServiceInterface');
1067
+
1068
+ // loading 变化也会触发重新渲染
1069
+ const { user, loading } = useStore(userService);
1070
+
1071
+ return <span>{user?.name}</span>;
1072
+ }
1073
+ ```
1074
+
1075
+ ### 6. ✅ 状态保持不可变
1076
+
1077
+ ```typescript
1078
+ // ✅ 好的做法:创建新对象
1079
+ updateUser(changes: Partial<UserInfo>) {
1080
+ this.emit({
1081
+ ...this.state,
1082
+ user: {
1083
+ ...this.state.user,
1084
+ ...changes
1085
+ }
1086
+ });
1087
+ }
1088
+
1089
+ // ❌ 不好的做法:直接修改对象
1090
+ updateUser(changes: Partial<UserInfo>) {
1091
+ this.state.user.name = changes.name; // ❌ 直接修改
1092
+ this.emit(this.state); // ❌ 引用相同,可能不触发更新
1093
+ }
1094
+ ```
1095
+
1096
+ ### 7. ✅ 合理划分状态
1097
+
1098
+ ```typescript
1099
+ // ✅ 好的做法:每个 Service 管理自己的状态
1100
+ class UserService extends StoreInterface<UserState> {
1101
+ // 只管理用户相关状态
1102
+ }
1103
+
1104
+ class ThemeService extends StoreInterface<ThemeState> {
1105
+ // 只管理主题相关状态
1106
+ }
1107
+
1108
+ class I18nService extends StoreInterface<I18nState> {
1109
+ // 只管理国际化相关状态
1110
+ }
1111
+
1112
+ // ❌ 不好的做法:全局大 Store
1113
+ class GlobalStore extends StoreInterface<GlobalState> {
1114
+ // 包含所有状态:用户、主题、国际化等
1115
+ // 问题:任何状态变化都会影响所有订阅者
1116
+ }
1117
+ ```
1118
+
1119
+ ---
1120
+
1121
+ ## ❓ 常见问题
1122
+
1123
+ ### Q1: 为什么不用 Redux?
1124
+
1125
+ **A:**
1126
+
1127
+ | 特性 | Redux | Store (SliceStore) |
1128
+ | ------------------- | ------------------------------------ | ------------------------- |
1129
+ | **复杂度** | ❌ 高(Action, Reducer, Middleware) | ✅ 低(emit + subscribe) |
1130
+ | **学习曲线** | ❌ 陡峭 | ✅ 平缓 |
1131
+ | **TypeScript 支持** | ⚠️ 需要额外配置 | ✅ 原生支持 |
1132
+ | **IOC 集成** | ⚠️ 需要额外工作 | ✅ 天然集成 |
1133
+ | **性能** | ✅ 好 | ✅ 好 |
1134
+ | **适用场景** | 大型应用 | 中小型应用 |
1135
+
1136
+ **我们的选择:**
1137
+
1138
+ - 项目已经使用 IOC,不需要 Redux 的全局状态管理
1139
+ - 每个 Service 管理自己的状态,更清晰
1140
+ - SliceStore 足够简单和强大
1141
+
1142
+ ### Q2: Store 和 React Context 有什么区别?
1143
+
1144
+ **A:**
1145
+
1146
+ | 特性 | React Context | Store |
1147
+ | ------------------- | ------------------------- | ------------------------- |
1148
+ | **作用域** | 组件树 | 全局(通过 IOC) |
1149
+ | **性能** | ⚠️ 任何值变化都会重新渲染 | ✅ 只有订阅的值变化才渲染 |
1150
+ | **选择器** | ❌ 无 | ✅ 有 |
1151
+ | **与 Service 集成** | ⚠️ 需要手动 | ✅ 天然集成 |
1152
+
1153
+ **建议:**
1154
+
1155
+ - 使用 Store 管理应用状态(Service 状态)
1156
+ - 使用 Context 管理 UI 状态(如模态框、临时表单数据)
1157
+
1158
+ ### Q3: 如何避免重复渲染?
1159
+
1160
+ **A:** 使用选择器
1161
+
1162
+ ```typescript
1163
+ // ❌ 问题:订阅完整状态
1164
+ const { user, loading, error } = useStore(userService);
1165
+ // loading 变化会导致组件重新渲染
1166
+
1167
+ // ✅ 解决:只订阅需要的状态
1168
+ const user = useStore(userService, (state) => state.user);
1169
+ // 只有 user 变化才会重新渲染
1170
+ ```
1171
+
1172
+ ### Q4: 可以在 Service 外部调用 emit 吗?
1173
+
1174
+ **A:** 不建议
1175
+
1176
+ ```typescript
1177
+ // ❌ 不好的做法
1178
+ function SomeComponent() {
1179
+ const userService = useIOC('UserServiceInterface');
1180
+
1181
+ // ❌ 直接调用 emit
1182
+ userService.emit({ user: newUser, loading: false });
1183
+ }
1184
+
1185
+ // ✅ 好的做法:通过 Service 方法
1186
+ function SomeComponent() {
1187
+ const userService = useIOC('UserServiceInterface');
1188
+
1189
+ // ✅ 调用 Service 方法
1190
+ userService.setUser(newUser);
1191
+ }
1192
+
1193
+ // Service 中
1194
+ @injectable()
1195
+ export class UserService extends StoreInterface<UserState> {
1196
+ setUser(user: UserInfo) {
1197
+ this.emit(this.cloneState({ user }));
266
1198
  }
1199
+ }
1200
+ ```
1201
+
1202
+ **原因:**
1203
+
1204
+ - 保持封装性
1205
+ - 方便测试
1206
+ - 业务逻辑集中在 Service
1207
+
1208
+ ### Q5: Store 状态更新不生效?
1209
+
1210
+ **A:** 检查以下几点:
1211
+
1212
+ ```typescript
1213
+ // ❌ 常见错误 1:直接修改 state
1214
+ this.state.loading = true; // 不会触发更新
1215
+
1216
+ // ✅ 正确:使用 emit
1217
+ this.emit({ ...this.state, loading: true });
1218
+
1219
+ // ❌ 常见错误 2:没有创建新对象
1220
+ const state = this.state;
1221
+ state.loading = true;
1222
+ this.emit(state); // 引用相同,可能不触发更新
1223
+
1224
+ // ✅ 正确:创建新对象
1225
+ this.emit({ ...this.state, loading: true });
1226
+
1227
+ // ❌ 常见错误 3:忘记订阅
1228
+ function MyComponent() {
1229
+ const userService = useIOC('UserServiceInterface');
1230
+ // 没有调用 useStore,无法接收更新
1231
+
1232
+ return <div>{userService.getState().user?.name}</div>;
1233
+ }
1234
+
1235
+ // ✅ 正确:使用 useStore 订阅
1236
+ function MyComponent() {
1237
+ const userService = useIOC('UserServiceInterface');
1238
+ const user = useStore(userService, (state) => state.user);
1239
+
1240
+ return <div>{user?.name}</div>;
1241
+ }
1242
+ ```
1243
+
1244
+ ### Q6: 如何在 Service 之间共享状态?
267
1245
 
268
- // 使用 cloneState 进行状态更新
269
- updateWithClone(newState: Partial<ExecutorState>) {
270
- this.emit(this.cloneState(newState));
1246
+ **A:** 通过 IOC 注入
1247
+
1248
+ ```typescript
1249
+ // Service A
1250
+ @injectable()
1251
+ export class UserService extends StoreInterface<UserState> {
1252
+ // ...
1253
+ }
1254
+
1255
+ // Service B 依赖 Service A
1256
+ @injectable()
1257
+ export class ProfileService {
1258
+ constructor(
1259
+ @inject('UserServiceInterface')
1260
+ private userService: UserService
1261
+ ) {}
1262
+
1263
+ async updateProfile(data: ProfileData) {
1264
+ // ✅ 访问 UserService 的状态
1265
+ const user = this.userService.getState().user;
1266
+
1267
+ // ✅ 也可以订阅 UserService 的状态
1268
+ this.userService.subscribe((state) => {
1269
+ console.log('User state changed:', state);
1270
+ });
271
1271
  }
272
1272
  }
273
1273
  ```
274
1274
 
275
- ## 最佳实践
1275
+ ---
276
1276
 
277
- 1. **状态组织**
278
- - 按功能模块划分状态
279
- - 避免状态冗余
280
- - 保持状态扁平化
1277
+ ## 📚 相关文档
281
1278
 
282
- 2. **性能优化**
283
- - 使用选择器获取状态,避免不必要的重渲染
284
- - 合理拆分组件,避免大组件订阅过多状态
285
- - 使用 `cloneState` 方法确保状态更新的不可变性
1279
+ - [项目架构设计](./index.md) - 了解整体架构
1280
+ - [IOC 容器](./ioc.md) - 依赖注入和 UI 分离
1281
+ - [Bootstrap 启动器](./bootstrap.md) - 应用启动和初始化
1282
+ - [测试指南](./test-guide.md) - 详细的测试策略
286
1283
 
287
- 3. **类型安全**
288
- - 为所有状态定义接口
289
- - 使用 TypeScript 的类型推导
290
- - 避免使用 any 类型
1284
+ ---
291
1285
 
292
- 4. **与启动器集成**
293
- - 在 Bootstrap 阶段初始化 store
294
- - 通过 IOC 容器管理 store 实例
295
- - 使用插件系统扩展功能
1286
+ ## 🎉 总结
296
1287
 
297
- ## 常见问题
1288
+ Store 状态管理的核心价值:
298
1289
 
299
- ### 1. 状态更新不生效
1290
+ 1. **解决通信问题** 📡 - 应用层通知 UI 层,同时保持分离
1291
+ 2. **发布订阅模式** 🔔 - Service emit,UI useStore
1292
+ 3. **自动更新 UI** ⚡ - 状态变化时,UI 自动重新渲染
1293
+ 4. **保持解耦** 🔗 - Service 不知道有哪些 UI 在监听
1294
+ 5. **易于测试** 🧪 - Store 可以独立测试
1295
+ 6. **性能优化** 🚀 - 选择器只订阅需要的状态
1296
+ 7. **类型安全** 🔒 - TypeScript 完整支持
300
1297
 
301
- 检查以下几点:
1298
+ **记住核心模式:**
302
1299
 
303
- - 确保正确使用 `emit` 方法更新状态
304
- - 使用 `cloneState` 方法确保状态不可变性
305
- - 检查组件是否正确订阅了状态
1300
+ ```typescript
1301
+ // 1. Service 继承 StoreInterface
1302
+ class MyService extends StoreInterface<MyState> {
1303
+ // 2. 通过 emit 发布状态
1304
+ doSomething() {
1305
+ this.emit({ ...this.state, data: newData });
1306
+ }
1307
+ }
306
1308
 
307
- ### 2. 组件重复渲染
1309
+ // 3. UI 通过 useStore 订阅状态
1310
+ function MyComponent() {
1311
+ const myService = useIOC('MyServiceInterface');
1312
+ const data = useStore(myService, (state) => state.data);
308
1313
 
309
- 可能的解决方案:
1314
+ return <div>{data}</div>;
1315
+ }
1316
+ ```
310
1317
 
311
- - 使用选择器只订阅需要的状态
312
- - 检查依赖项是否正确设置
313
- - 考虑使用 React.memo 优化组件
1318
+ **核心原则:**
314
1319
 
315
- ### 3. TypeScript 类型报错
1320
+ - Service 通过 emit 发布状态
1321
+ - ✅ UI 通过 useStore 订阅状态
1322
+ - ✅ 使用选择器优化性能
1323
+ - ✅ 状态保持不可变
1324
+ - ✅ 每个 Service 管理自己的状态
316
1325
 
317
- 常见解决方法:
1326
+ ---
318
1327
 
319
- - 确保正确继承 StoreInterface
320
- - 检查泛型参数是否正确
321
- - 确保状态类型实现了 StoreStateInterface
1328
+ **问题反馈:**
1329
+ 如果你对 Store 状态管理有任何疑问或建议,请在团队频道中讨论或提交 Issue。