一篇文章搞定Redux Toolkit 使用。

一、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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容