“状态管理,是前端开发的巴别塔。”
林小凡盯着屏幕上并排打开的两个文档:
- Redux:
单一不可变状态树
、纯函数reducer
、dispatch(action)
- Vuex:
集中式存储
、mutation同步
、action异步
他揉了揉太阳穴,感觉自己的大脑正在被两种截然不同的哲学撕裂。
“为什么一个简单的‘登录状态’需要这么多概念?!”他对着空气咆哮,手指悬在键盘上,像站在悬崖边犹豫的旅人。
墨尘的消息适时弹出:
“欢迎来到状态管理的深渊——这里埋葬着无数前端工程师的理智。”
第一节:Redux的仪式感
新接手的React项目采用经典Redux架构,目录结构令人望而生畏:
/src
/store
/actions
- userActions.js
/reducers
- userReducer.js
/types
- actionTypes.js
- configureStore.js
一个简单的“用户登录”需要穿越四个文件:
-
定义action类型(actionTypes.js)
javascriptexport const LOGIN_REQUEST = 'USER/LOGIN_REQUEST'; export const LOGIN_SUCCESS = 'USER/LOGIN_SUCCESS';
-
编写action创建函数(userActions.js)
javascriptexport const login = (credentials) => (dispatch) => { dispatch({ type: LOGIN_REQUEST }); return api.login(credentials) .then(user => dispatch({ type: LOGIN_SUCCESS, payload: user })); };
-
处理reducer(userReducer.js)
javascriptconst initialState = { loading: false, data: null }; export default (state = initialState, action) => { switch (action.type) { case LOGIN_REQUEST: return { ...state, loading: true }; case LOGIN_SUCCESS: return { ...state, data: action.payload, loading: false }; default: return state; } };
-
组件中调用
jsxconst mapDispatch = { login }; export default connect(null, mapDispatch)(LoginForm);
“就为了改个登录状态?!”林小凡数了数键盘磨损的键帽,“这代码比银行转账流程还复杂!”
第二节:Vuex的甜蜜陷阱
转战Vue项目时,Vuex看起来像救世主:
javascript
// store.js
export default new Vuex.Store({
state: {
user: null
},
mutations: {
SET_USER(state, user) {
state.user = user; // 直接修改!
}
},
actions: {
async login({ commit }, credentials) {
const user = await api.login(credentials);
commit('SET_USER', user);
}
}
});
// 组件中使用
methods: {
...mapActions(['login'])
}
“这才叫人性化!”林小凡刚赞叹完,就发现项目里出现了:
- 嵌套五层的module
- mutation和action的命名冲突
- 神秘失踪的state更新(后来发现是对象引用问题)
最可怕的是这个:
javascript
// 某个深奥的plugin
store.subscribeAction((action, state) => {
if (action.type === 'login') {
localStorage.setItem('lastLogin', new Date());
}
});
“所以Vuex的简洁只是表象……”他对着store.watch
的文档陷入沉思。
第三节:Redux Toolkit的救赎
当林小凡准备放弃Redux时,@reduxjs/toolkit出现了:
javascript
// 用createSlice同时定义reducer和action
const userSlice = createSlice({
name: 'user',
initialState: { loading: false, data: null },
reducers: {},
extraReducers: (builder) => {
builder.addCase(login.pending, (state) => {
state.loading = true;
});
builder.addCase(login.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
}
});
// 用createAsyncThunk自动生成action
export const login = createAsyncThunk('user/login', (credentials) => {
return api.login(credentials);
});
“等等!”他瞪大眼睛,“Redux现在允许直接修改state了?还有自动生成的action?”
墨尘发来一个笑哭的表情:
“Redux团队终于承认:人类不应该手写action type。”
第四节:Pinia的突袭
就在林小凡刚适应Vuex时,Vue 3推出了Pinia:
javascript
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({
data: null
}),
actions: {
async login(credentials) {
this.data = await api.login(credentials);
}
}
});
// 组件中使用
const store = useUserStore();
store.login();
没有mutation
,没有module
,甚至没有this.$store
!
“所以Vuex被放弃了?!”林小凡三观碎裂。
GitHub上的issue给出了答案:
“Pinia is Vuex 5, but we didn't want to do a major version bump.”
终节:状态管理的本质
深夜,林小凡在项目遗留代码里发现一段2017年的注释:
javascript
/*
* 状态管理的终极方案:
* 1. 把数据存在组件内(简单)
* 2. 发现需要跨组件共享 → 提升到父组件
* 3. 发现层级太深 → 尝试Context/Provide
* 4. 还是太乱 → 上Redux/Vuex
* 5. 最后发现:还不如直接存在localStorage
*/
窗外,月光照在并排打开的四个标签页上:
- Redux
- Vuex
- @reduxjs/toolkit
- Pinia
像四个不同时代的墓碑,又像同一座山峰的四面。
(第十二章 完)
下一章预告:
第十三章:npm与yarn的依赖迷宫
“当你运行npm install
时,永远不知道是下载代码还是引爆地雷。”——某被node_modules折磨的开发者