一、Redux Toolkit 是什么?
Redux Toolkit(RTK)是 Redux 官方推荐的标准写法
不是新框架,而是:
Redux + 最佳实践 + 默认配置 + 少写代码
📌 官方态度很明确:
👉 新项目用 RTK,旧项目可以渐进迁移
二、为什么要用 Redux Toolkit?
传统 Redux 的 4 个痛点
1. action type 一堆字符串
2. reducer switch/case 冗长
3. 不可变更新容易写错
4. 异步逻辑分散(thunk / saga)
RTK 帮你一次性解决
问题-----------------------------Redux-----------------------------RTK
action--------------------------手写 type-----------------------自动生成
reducer-------------------------switch-------------------------createSlice
不可变--------------------------手动---------------------------内置 Immer
异步-------------------------saga / thunk---------------createAsyncThunk
store-------------------------createStore-------------------configureStore
三、RTK 的核心概念(一定要理解)
一个 slice = 一个业务模块
一个 slice 包含:
• 初始状态
• reducer
• action(自动生成)
四、最小可运行示例(计数器)
1️⃣ 安装
npm install @reduxjs/toolkit react-redux
2️⃣ 创建 slice(重点)
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter', // 相当于 prefix
initialState: {
count: 0,
},
reducers: {
increment(state) {
state.count += 1;
},
decrement(state) {
state.count -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
3️⃣ 创建 store
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
4️⃣ 注入到 RN App
// App.js
import { Provider } from 'react-redux';
import { store } from './store';
export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
5️⃣ 组件中使用
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
return (
<View>
<Text>计数:{count}</Text>
<Button title="+" onPress={() => dispatch(increment())} />
<Button title="-" onPress={() => dispatch(decrement())} />
</View>
);
}
五、RTK 异步:createAsyncThunk(非常重要)
1️⃣ 定义异步 thunk
// userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUserInfo = createAsyncThunk(
'user/fetchUserInfo',
async (userId) => {
const res = await api.getUserInfo(userId);
return res;
}
);
2️⃣ 处理异步状态
const userSlice = createSlice({
name: 'user',
initialState: {
info: null,
loading: false,
error: null,
},
reducers: {},
extraReducers: builder => {
builder
.addCase(fetchUserInfo.pending, state => {
state.loading = true;
})
.addCase(fetchUserInfo.fulfilled, (state, action) => {
state.loading = false;
state.info = action.payload;
})
.addCase(fetchUserInfo.rejected, (state, action) => {
state.loading = false;
state.error = action.error;
});
},
});
export default userSlice.reducer;
3️⃣ 组件中触发
dispatch(fetchUserInfo(123));
文件层次
store/
├─ index.js
├─ userSlice.js
├─ inviteSlice.js
├─ chatSlice.js
📖📖📖📖📖📖📖
⬇️⬇️⬇️⬇️⬇️⬇️⬇️
例子🌰:
一、目录结构(先整体有感知)
src/
├─ store/
│ ├─ index.js // 注册所有 reducer(关键)
│ ├─ userSlice.js
│ └─ configSlice.js // 👈 新增
├─ App.js
└─ UserScreen.js
二、userSlice.js(业务模块)
👉 只做一件事:定义状态 + 定义怎么改
// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// (可选)异步 action
export const fetchUserInfo = createAsyncThunk(
'user/fetchUserInfo',
async () => {
// 模拟接口
return new Promise(resolve => {
setTimeout(() => {
resolve({
id: 1,
name: '张三',
age: 18,
});
}, 1000);
});
}
);
const userSlice = createSlice({
name: 'user', // 👈 相当于 prefix
initialState: {
info: null,
loading: false,
},
reducers: {
setUserInfo(state, action) {
state.info = action.payload;
},
clearUserInfo(state) {
state.info = null;
},
},
extraReducers: builder => {
builder
.addCase(fetchUserInfo.pending, state => {
state.loading = true;
})
.addCase(fetchUserInfo.fulfilled, (state, action) => {
state.loading = false;
state.info = action.payload;
})
.addCase(fetchUserInfo.rejected, state => {
state.loading = false;
});
},
});
export const { setUserInfo, clearUserInfo } = userSlice.actions;
export default userSlice.reducer; // 👈 只导出 reducer
三、store/index.js(注册 slice 的地方 ⭐⭐⭐)
👉 这是 userSlice 真正“生效”的地方
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
import configReducer from './configSlice';
export const store = configureStore({
reducer: {
user: userReducer, // 用户模块
config: configReducer, // 👈 新增配置模块
},
});
注册完成后,Redux state 结构就是:
{
user: {
info: null,
loading: false
}
}
四、App.js(注入 store)
👉 App 不创建状态,只是把 store 提供给 React
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import UserScreen from './UserScreen';
export default function App() {
return (
<Provider store={store}>
<UserScreen />
</Provider>
);
}
五、UserScreen.js(组件中使用 Redux)
👉 useSelector 取数据,useDispatch 改数据
// src/UserScreen.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import {
setUserInfo,
clearUserInfo,
fetchUserInfo,
} from './store/userSlice';
export default function UserScreen() {
const dispatch = useDispatch();
const userInfo = useSelector(state => state.user.info);
const loading = useSelector(state => state.user.loading);
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 10 }}>
用户信息:
</Text>
{loading && <Text>加载中...</Text>}
{userInfo ? (
<Text>{JSON.stringify(userInfo, null, 2)}</Text>
) : (
<Text>暂无用户信息</Text>
)}
<Button
title="设置用户信息(同步)"
onPress={() =>
dispatch(
setUserInfo({ id: 2, name: '李四', age: 20 })
)
}
/>
<Button
title="获取用户信息(异步)"
onPress={() => dispatch(fetchUserInfo())}
/>
<Button
title="清空用户信息"
onPress={() => dispatch(clearUserInfo())}
/>
</View>
);
}
新增configSlice.js(配置模块)
👉 常见用途:
• 全局配置
• 主题 / 语言
• feature 开关
• 环境信息
// src/store/configSlice.js
import { createSlice } from '@reduxjs/toolkit';
const configSlice = createSlice({
name: 'config', // 👈 prefix
initialState: {
theme: 'light',
language: 'zh',
},
reducers: {
setTheme(state, action) {
state.theme = action.payload; // 'light' | 'dark'
},
setLanguage(state, action) {
state.language = action.payload; // 'zh' | 'en'
},
},
});
export const { setTheme, setLanguage } = configSlice.actions;
export default configSlice.reducer;
新增后Redux State 结构现在长这样
{
user: {
info: null,
loading: false,
},
config: {
theme: 'light',
language: 'zh',
},
}
在组件中使用 configReducer
// src/UserScreen.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import {
setUserInfo,
clearUserInfo,
fetchUserInfo,
} from './store/userSlice';
import { setTheme, setLanguage } from './store/configSlice';
export default function UserScreen() {
const dispatch = useDispatch();
const userInfo = useSelector(state => state.user.info);
const loading = useSelector(state => state.user.loading);
const theme = useSelector(state => state.config.theme);
const language = useSelector(state => state.config.language);
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18 }}>当前主题:{theme}</Text>
<Text style={{ fontSize: 18 }}>当前语言:{language}</Text>
<Button
title="切换主题"
onPress={() =>
dispatch(setTheme(theme === 'light' ? 'dark' : 'light'))
}
/>
<Button
title="切换语言"
onPress={() =>
dispatch(setLanguage(language === 'zh' ? 'en' : 'zh'))
}
/>
<View style={{ marginVertical: 20 }} />
<Button
title="获取用户信息(异步)"
onPress={() => dispatch(fetchUserInfo())}
/>
<Button
title="清空用户信息"
onPress={() => dispatch(clearUserInfo())}
/>
{loading && <Text>加载中...</Text>}
{userInfo && (
<Text>{JSON.stringify(userInfo, null, 2)}</Text>
)}
</View>
);
}
你可能遇到的问题
你可能遇到的问题
你可能遇到的问题
extraReducers: builder => {
builder
.addCase(fetchUserInfo.pending, state => {
state.loading = true;
})
.addCase(fetchUserInfo.fulfilled, (state, action) => {
state.loading = false;
state.info = action.payload;
})
.addCase(fetchUserInfo.rejected, state => {
state.loading = false;
});
}这段是什么意思
⚠️ 异步 action(Thunk)本质上会生成三种 action:
fetchUserInfo.pending // 请求开始
fetchUserInfo.fulfilled // 请求成功
fetchUserInfo.rejected // 请求失败
拆解:
1️⃣ builder.addCase(fetchUserInfo.pending, state => { ... })
• 当 fetchUserInfo() 被 dispatch 时
• Thunk 会自动 dispatch fetchUserInfo.pending
• 这里我们把 state.loading = true,表示“正在加载中”
⸻
2️⃣ builder.addCase(fetchUserInfo.fulfilled, (state, action) => { ... })
• 异步请求成功时自动 dispatch fetchUserInfo.fulfilled
• action.payload 会自动包含返回的数据(Promise resolve 的结果)
• 我们把:
• loading 置为 false
• info 更新为请求结果
⸻
3️⃣ builder.addCase(fetchUserInfo.rejected, state => { ... })
• 请求失败时自动 dispatch fetchUserInfo.rejected
• 我们把:
• loading 置为 false
• 可以在这里处理 error,如果需要可以加 state.error = action.error
⸻
完整流程理解(执行顺序)
组件 dispatch(fetchUserInfo())
↓
store 自动 dispatch(fetchUserInfo.pending)
↓
extraReducers → state.loading = true
↓
异步请求完成
↓
如果成功 → dispatch(fetchUserInfo.fulfilled)
→ extraReducers → state.info = 数据, state.loading = false
如果失败 → dispatch(fetchUserInfo.rejected)
→ extraReducers → state.loading = false