-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
State Management Intro
LobeChat differs from traditional CRUD web applications in that it involves a large amount of rich interactive capabilities. Therefore, it is crucial to design a data flow architecture that is easy to develop and maintain. This document will introduce the best practices for data flow management in LobeChat.
Concept | Explanation |
---|---|
store | The store contains the application's state and actions. It allows access to and modification of the state during application rendering. |
state | State refers to the data of the application, storing the current state of the application. Any change in the state will trigger a re-rendering to reflect the new state. |
action | An action is an operation function that describes the interactive events occurring in the application. Actions are typically triggered by user interactions, network requests, or timers. Actions can be synchronous or asynchronous. |
reducer | A reducer is a pure function that takes the current state and action as parameters and returns a new state. It is used to update the application's state based on the action type. A reducer is a pure function with no side effects, therefore it is always a synchronous function. |
selector | A selector is a function used to retrieve specific data from the application's state. It takes the application's state as a parameter and returns computed or transformed data. Selectors can combine parts of the state or multiple states to generate derived data. Selectors are commonly used to map the application's state to a component's props for the component's use. |
slice | A slice is a concept used to express a part of the data model state. It specifies a state slice and its related state, action, reducer, and selector. Using slices, a large store can be divided into smaller, maintainable subtypes. |
The structure of the Store can vary greatly depending on the complexity:
-
Low Complexity: Generally includes 2 to 5 states and 3 to 4 actions. In this case, the structure usually consists of a
store.ts
and aninitialState.ts
.
DataFill/store
├── index.ts
└── initialState.ts
-
Moderate Complexity: Typically involves 5 to 15 states and 5 to 10 actions, with the possibility of selectors for derived states and reducers to simplify data changes. The structure usually includes a
store.ts
, aninitialState.ts
, and aselectors.ts
/reducer.ts
.
IconPicker/store
├── index.ts
├── initialState.ts
├── selectors.ts
└── store.ts
SortableList/store
├── index.ts
├── initialState.ts
├── listDataReducer.ts
└── store.ts
-
Medium Complexity: Involves 15 to 30 states and 10 to 20 actions, often requiring the use of multiple slices to manage different actions. The following code represents the internal data flow of the
SortableTree
component:
SortableTree/store
├── index.ts
├── initialState.ts
├── selectors.ts
├── slices
│ ├── crudSlice.ts
│ ├── dndSlice.ts
│ └── selectionSlice.ts
├── store.ts
└── treeDataReducer.ts
- High Complexity: Involves over 30 states and 20 actions, requiring modular cohesion using slices. Each slice declares its own initState, actions, reducers, and selectors.
The directory structure of the previous version of SessionStore for LobeChat, with high complexity, implements a large amount of business logic. However, with the modularization of slices and the fractal architecture, it is easy to find the corresponding modules, making it easy to maintain and iterate on new features.
LobeChat SessionStore
├── index.ts
├── initialState.ts
├── selectors.ts
├── slices
│ ├── agentConfig
│ │ ├── action.ts
│ │ ├── index.ts
│ │ ├── initialState.ts
│ │ └── selectors.ts
│ ├── chat
│ │ ├── actions
│ │ │ ├── index.ts
│ │ │ ├── message.ts
│ │ │ └── topic.ts
│ │ ├── index.ts
│ │ ├── initialState.ts
│ │ ├── reducers
│ │ │ ├── message.ts
│ │ │ └── topic.ts
│ │ ├── selectors
│ │ │ ├── chat.ts
│ │ │ ├── index.ts
│ │ │ ├── token.ts
│ │ │ ├── topic.ts
│ │ │ └── utils.ts
│ │ └── utils.ts
│ └── session
│ ├── action.ts
│ ├── index.ts
│ ├── initialState.ts
│ ├── reducers
│ │ └── session.ts
│ └── selectors
│ ├── export.ts
│ ├── index.ts
│ └── list.ts
└── store.ts
Based on the provided directory structure of LobeChat SessionStore, we can update the previous document and convert the examples to the implementation of LobeChat's SessionStore. The following is a portion of the updated document:
In the LobeChat application, session management is a complex functional module, so we use the Slice pattern to organize the data flow. Below is the directory structure of LobeChat SessionStore, where each directory and file has its specific purpose:
src/store/session
├── helpers.ts # Helper functions
├── hooks # Custom React hooks
│ ├── index.ts # Export file for hooks
│ ├── useEffectAfterHydrated.ts # Hook for effects after session hydration
│ ├── useOnFinishHydrationSession.ts # Hook for session hydration completion
│ ├── useSessionChatInit.ts # Hook for session chat initialization
│ └── useSessionHydrated.ts # Hook for session hydration status
├── index.ts # Aggregated export file for SessionStore
├── initialState.ts # Aggregated initialState for all slices
├── selectors.ts # Selectors exported from various slices
├── slices # Separated functional modules
│ ├── agent # State and operations related to agents
│ │ ├── action.ts # Action definitions related to agents
│ │ ├── index.ts # Entry file for agent slice
│ │ ├── selectors.test.ts # Tests for agent-related selectors
│ │ └── selectors.ts # Selector definitions related to agents
│ └── session # State and operations related to sessions
│ ├── action.test.ts # Tests for session-related actions
│ ├── action.ts # Action definitions related to sessions
│ ├── helpers.ts # Helper functions related to sessions
│ ├── initialState.ts # Initial state for session slice
│ └── selectors # Session-related selectors and their tests
│ ├── export.ts # Aggregated export for session selectors
│ ├── index.ts # Entry file for session selectors
│ ├── list.test.ts # Tests for list selectors
│ └── list.ts # Definitions for list-related selectors
└── store.ts # Creation and usage of SessionStore
In LobeChat, the SessionStore is designed as the core module for managing session state and logic. It consists of multiple Slices, with each Slice managing a relevant portion of state and logic. Below is a simplified example of the SessionStore implementation:
import { PersistOptions, devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { shallow } from 'zustand/shallow';
import { devtools } from 'zustand/middleware';
import { createWithEqualityFn } from 'zustand/traditional';
import { SessionStoreState, initialState } from './initialState';
import { AgentAction, createAgentSlice } from './slices/agent/action';
import { SessionAction, createSessionSlice } from './slices/session/action';
// =============== Aggregate createStoreFn ============ //
export type SessionStore = SessionAction & AgentAction & SessionStoreState;
const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (...parameters) => ({
...initialState,
...createAgentSlice(...parameters),
...createSessionSlice(...parameters),
});
// =============== Implement useStore ============ //
export const useSessionStore = createWithEqualityFn<SessionStore>()(
persist(
subscribeWithSelector(
devtools(createStore, {
name: 'LobeChat_Session' + (isDev ? '_DEV' : ''),
}),
),
persistOptions,
),
shallow,
);
In this store.ts
file, we create a useSessionStore
hook that uses the zustand
library to create a global state manager. We merge the initialState and the state and actions of each Slice to create a complete SessionStore.
import { StateCreator } from 'zustand';
import { SessionStore } from '@/store/session';
export interface SessionActions {
/**
* A custom hook that uses SWR to fetch sessions data.
*/
useFetchSessions: () => SWRResponse<any>;
}
export const createSessionSlice: StateCreator<
SessionStore,
[['zustand/devtools', never]],
[],
SessionAction
> = (set, get) => ({
useFetchSessions: () => {
// ...logic for initializing sessions
},
// ...implementation of other actions
});
In the action.ts
file, we define a SessionActions
interface to describe session-related actions and implement a useFetchSessions
function to create these actions. Then, we merge these actions with the initial state to form the session-related Slice.
Through this layered and modular approach, we can ensure that LobeChat's SessionStore is clear, maintainable, and easy to extend and test.
This is the 🤯 / 🤖 Lobe Chat wiki. Wiki Home
- Architecture Design | 架构设计
- Code Style and Contribution Guidelines | 代码风格与贡献指南
- Complete Guide to LobeChat Feature Development | LobeChat 功能开发完全指南
- Conversation API Implementation Logic | 会话 API 实现逻辑
- Directory Structure | 目录架构
- Environment Setup Guide | 环境设置指南
- How to Develop a New Feature | 如何开发一个新功能:前端实现
- New Authentication Provider Guide | 新身份验证方式开发指南
- Resources and References | 资源与参考
- Technical Development Getting Started Guide | 技术开发上手指南
- Testing Guide | 测试指南