@reduxjs/toolkit状态管理

610 阅读15分钟

@reduxjs/toolkit

@reduxjs/toolkitredux的进阶版,旨在简化状态管理的开发过程。它不仅包含了 redux的核心功能,还集成了一些最佳实践和工具,使得开发变得更加高效和便捷。

@reduxjs/toolkitredux的对比:

  1. 简化配置:redux需要手动设置 store,以及包括使用combineReducers进行仓库模块化管理。redux需要手动配置中间件,例如 redux-thunkredux-saga@reduxjs/toolkit只需几行代码即可创建store并自动配置store常用的中间件。

  2. 更简单的reducersredux需要手动定义action来完成数据的修改,且reducers通常需要返回新的状态对象。@reduxjs/toolkit可以同时定义初始状态、和 actions,而且可以在 reducers中直接修改状态对象,无需返回新对象。

  3. 异步逻辑处理:redux需要使用中间件如 redux-thunkredux-saga 来处理异步逻辑。编写异步逻辑时需要手动管理 action 类型和 dispatch 流程。@reduxjs/toolkit提供了可以轻松创建异步 thunk。自动处理异步请求的生命周期,提供了标准化的 action 类型(如 pendingfulfilledrejected)。

@reduxjs/toolkit使用

@reduxjs/toolkit基于redux,创建出来的仓库还是redux仓库,只不过创建方式简化了。

@reduxjs/toolkit使用步骤:

image.png

安装 @reduxjs/toolkit

npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit

基本使用

创建 Store仓库

// store/index.js 使用@reduxjs/toolkit创建仓库
import { createSlice,configureStore } from '@reduxjs/toolkit';
// 创建切片
let msgSlice = createSlice({
    // 必须给切片一个名称,name会作为action type的前缀
    name:'msgSlice',
    // initialState定义初始值
    initialState: {
        singer: "G.E.M.",
        album: ['G.E.M.', '18', 'My Secret', 'Xposed', '新的心跳', '摩天动物园', '启示录']
    },
    // reducers定义修改数据的行为
    reducers:{
        changeSinger(state,action){
            // state接受修改前的上一个仓库状态的数据
            // action接受调用方法传入的参数
            state.singer = action.payload;
        },
        resetSinger(state){
            state.singer = "G.E.M.";
        }
    }
})
// 整合切片创建仓库,传入一个对象,reducer属性需要取出切片的reducer属性,不能直接给切片
let store = configureStore({
    reducer:{
        msgReducer:msgSlice.reducer
    }
})
// 暴露仓库
export default store;

在这个例子中,创建了一个名为 msgSlice 的切片,并定义了reducer函数来处理不同的操作。这些函数可以直接修改状态对象,而不需要返回一个新的对象。

react-redux将@reduxjs/toolkit创建的仓库和React进行关联

react-redux和和React组件进行关联

// main.jsx
import { createRoot } from 'react-dom/client' // 用于渲染页面的
import React from 'react' // 用于创建组件的
import { Provider } from 'react-redux'; // 引入react-redux的Provider组件,然后用这个组件包裹App组件完成react-redux的全局注册
import App from './App.jsx'
import store from './store/index.js';
createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App></App>
    </Provider>
)

react-redux将@reduxjs/toolkit创建的仓库在React组件中使用

// App.jsx
import React from 'react';
import { useSelector, useDispatch, useStore } from 'react-redux'
export default function App(props) {
  const store = useStore();
  // 输出仓库状态
  console.log(store.getState(), 'state')
  const singer = useSelector((state) => state.msgReducer.singer);
  const album = useSelector((state) => state.msgReducer.album);
  const dispatch = useDispatch();
  // dispatch action修改仓库数据
  const change = () => {
    // msgSlice就是切片的名称
    dispatch({ type: "msgSlice/changeSinger", payload: "G.E.M. 邓紫棋" });
  };
  const reset = () => {
    dispatch({ type: "msgSlice/resetSinger" });
  };
  return (
    <div>
      <h1>My App</h1>
      <p>歌手:{singer}</p>
      <p>专辑:</p>
      {
        album.map((item, index) => {
          return <p key={index}>{item}</p>
        })
      }
      <button onClick={change}>change</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

仓库状态输出结果如下:

image.png

2.gif

一般@reduxjs/toolkit触发dispatch修改仓库数据不会像上面一样使用,而是将切片的actions属性暴露出去,切片的actions属性里面包含了切片定义的reducers方法,这样在dispatch修改仓库数据时就不用传入typepayload属性了,可以直接传入要修改的数据,它会作为action对象的payload属性值。

// src/store/index.js
import { createSlice, configureStore } from '@reduxjs/toolkit';
// 创建切片
let msgSlice = createSlice({
    // 必须给切片一个名称,在修改数据时会用到
    name: 'msgSlice',
    // initialState定义初始值
    initialState: {
        singer: "G.E.M.",
        album: ['G.E.M.', '18', 'My Secret', 'Xposed', '新的心跳', '摩天动物园', '启示录']
    },
    // reducers定义修改数据的行为
    reducers: {
        changeSinger(state, action) {
            console.log(action,'传入的action对象')
            // state接受修改前的上一个仓库状态的数据
            // action接受调用方法传入的参数,这里只能是payload接受传入的值
            state.singer = action.payload;
        },
        resetSinger(state) {
            state.singer = "G.E.M.";
        }
    }
})
// 整合切片创建仓库,传入一个对象,reducer属性需要取出切片的reducer属性,不能直接给切片
let store = configureStore({
    reducer: {
        msgReducer: msgSlice.reducer
    }
})
console.log(msgSlice.actions,'切片的actions属性');
// 暴露切片的actions属性用于修改仓库数据
export let { changeSinger, resetSinger } = msgSlice.actions;
// 暴露仓库
export default store;
// src/App.jsx
import React from 'react';
import { useSelector, useStore, useDispatch } from 'react-redux'
import { changeSinger, resetSinger } from './store/index.js'
export default function App(props) {
  const store = useStore();
  // 输出仓库状态
  console.log(store.getState(), 'state')
  const singer = useSelector((state) => state.msgReducer.singer);
  const album = useSelector((state) => state.msgReducer.album);
  const dispatch = useDispatch();
  // dispatch action修改仓库数据
  const change = () => {
    // dispatch传入的action对象就不再是普通的js对象,而是切片暴露的actions属性中的修改方法
    // 这个传入的参数就是要修改的数据,它会作为action对象的payload属性来修改数据
    dispatch(changeSinger("G.E.M. 邓紫棋"));
  };
  const reset = () => {
    dispatch(resetSinger());
  };
  return (
    <div>
      <h1>My App</h1>
      <p>歌手:{singer}</p>
      <p>专辑:</p>
      {
        album.map((item, index) => {
          return <p key={index}>{item}</p>
        })
      }
      <button onClick={change}>change</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

切片的actions属性:

image.png

5.gif

@reduxjs/toolkit API介绍

createSlice

createSlice 用于创建包含切片初始状态、切片reducers 和 切片actions 的对象,它简化了 reducer函数的编写过程。

// 创建切片
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
    // 切片的名称,用于生成唯一的 action 类型
    name: 'counter',
    // 初始状态对象
    initialState: {
        value: 0,
    },
    // reducers对象包含多个 reducer 函数,每个 reducer 函数定义了如何根据特定的 action 更新状态
    reducers: {
        increment: (state) => {
            state.value += 1;
        },
        decrement: (state) => {
            state.value -= 1;
        },
        incrementByAmount: (state, action) => {
            state.value += action.payload;
        },
    },
});
// counterSlice.actions 是一个对象,包含所有由 createSlice 自动生成的一个返回 action 对象的函数
// 通过解构,具名导出reducers对象中的reducer 函数
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// counterSlice.reducer 是由 createSlice 自动生成的 reducer 函数。
// 将这个 reducer 导出,以便在创建 Redux store 时使用。
export default counterSlice.reducer;

createAction

createAction用于创建一个返回 action 对象的函数,这个 action 对象包含一个 type 属性和 payload属性。将创建 action 对象的逻辑封装在一个函数中,这样避免在多个地方手动创建相同的 action 对象,通过函数名明确表示要派发的 action 类型,使代码更具可读性

手动创建 action 对象

// 手动创建 action 对象
const action = {
  type: ADD_TODO,
  payload: {
    text: 'Buy milk',
    completed: false,
  },
};

// 派发 action
store.dispatch(action);

createAction 接受两个参数:

  • type: 字符串,表示 action 的类型。在下面这个例子中,类型是 todos/add
  • prepare: 可选的准备函数,用于准备 action对象 的 payload属性。这个函数接受一个参数,并返回一个对象,该对象必须包含 payload 属性。

返回的 action 对象: createAction 返回的这个函数会生成一个标准的 Redux action 对象。这个对象包含两个属性:

  • type: 由 createAction 的第一个参数指定。
  • payload: 由 prepare 函数返回的对象中的 payload 属性决定
// 定义返回action对象的函数
import { createAction } from '@reduxjs/toolkit';
// 由 createAction 返回的创建action对象的函数
const addTodo = createAction('todos/add', (text) => ({
  payload: {
    text,
    completed: false,
  },
}));
// 调用返回action对象的函数,并传入一个字符串作为参数。
const todoAdded = addTodo('Buy milk');
// todoAdded是调用 addTodo 函数后返回的 action 对象
console.log(todoAdded); // { type: 'todos/add', payload: { text: 'Buy milk', completed: false } 
// 派发 action
store.dispatch(action);
}

createReducer

createReducer用于创建一个 reducer 函数,可以更方便地处理多个 action 类型

reducer函数是一个纯函数,用于根据当前的状态和接收到的 action对象来计算新的状态。它是 Redux 应用的核心部分,负责管理应用的状态变化。

注意点:

  1. 纯函数:reducer函数必须是纯函数,即它的输出仅依赖于输入参数,并且没有副作用(如网络请求、直接修改外部变量等)。
  2. 参数:
    • state:当前的状态。通常在第一次调用时,state 会使用默认值 定义的initialState作为初始状态。
    • action:一个包含 type 属性的对象,可能还包含其他属性(如 payload)。 返回值:
  3. reducer函数必须返回一个新的状态对象。

手动编写reducer函数

const initialState = {
    count: 0,
};

function counterReducer(state = initialState, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { ...state, count: state.count + 1 };
        case 'DECREMENT':
            return { ...state, count: state.count - 1 };
        default:
            return {...state };
    }
}

createReducer(initialState, builder):接受两个参数

  • initialState:reducer 的初始状态。
  • builder:一个函数,用于构建 reducer。这个函数接收一个 builder 对象,通过调用 builderaddCase方法来定义如何处理不同的 action

builder.addCase(actionCreator, reducerFunction)addCase 方法用于定义如何处理特定的 action,接受两个参数

  • actionCreator:一个返回 action 对象的函数,用于生成特定类型的 action 对象。
  • reducerFunction:一个函数,用于更新状态。这个函数接收两个参数:
    • state:当前的状态。
    • action:被 dispatch 的 action 对象
import { createReducer } from '@reduxjs/toolkit';
// 导入addTodo 和 toggleTodo,这两个函数用于生成特定类型的 action 对象
import { addTodo, toggleTodo } from './actions.js';
// initialState变量定义了reducer 的初始状态
const initialState = {
  todos: [],
};
// 创建 Reducer
const todoReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(addTodo, (state, action) => {
      state.todos.push(action.payload);
    })
    .addCase(toggleTodo, (state, action) => {
      const todo = state.todos.find((t) => t.id === action.payload.id);
      if (todo) {
        todo.completed = !todo.completed;
      }
    });
});

export default todoReducer;

在上面这个例子中,处理addTodo action是当 addTodo actiondispatch 时,state.todos.push(action.payload) 会被执行,将新的 todo 添加到 todos 数组中。处理 toggleTodo action是当 toggleTodo actiondispatch 时,const todo = state.todos.find((t) => t.id === action.payload.id) 会查找匹配的 todo元素。如果找到了匹配的 todo元素,执行todo.completed = !todo.completed,切换该 todo元素 的 completed 属性。

createAsyncThunk

createAsyncThunk用于创建一个异步返回 action 对象的函数,自动处理异步操作的生命周期。

createAsyncThunk(type,payloadCreator):接受两个参数:

  • type:字符串,表示 action 的类型。在下面的例子中,类型是 users/fetchById
  • payloadCreator:一个异步函数,用于执行异步操作并返回结果。这个函数接受一个参数,并返回异步操作中获取的数据。异步操作触发的时候会有三种状态,pending(进行中)、fulfilled(成功)、rejected(失败)

创建切片时:extraReducers是一个函数,用于处理额外的action 类型,也就是让切片处理在别处定义的返回 action 对象的函数,包括由 createAsyncThunk 或其他切片生成的返回 action 对象的函数。extraReducers函数接受builder对象提供了 addCase 方法,用于定义如何处理特定的 action

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
// 这里只异步请求数据,不会修改切片状态
const fetchUserById = createAsyncThunk('users/fetchById', async (userId) => {
  const response = await axios.get(`https://api.example.com/users/${userId}`);
  return response.data;
});
// 创建切片
const userSlice = createSlice({
  // 切片名
  name: 'user',
  // 定义状态初始值
  initialState: {
    status: 'idle',
    user: null,
    error: null,
  },
  // 使用 extraReducers 定义如何处理异步操作的不同阶段pending、fulfilled、rejected,然后才做状态的修改
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.status = 'idle';
        state.user = action.payload;
      })
      .addCase(fetchUserById.rejected, (state, action) => {
        state.status = 'idle';
        state.error = action.error.message;
      });
  },
});
// 导出 fetchUserById 异步返回action对象的函数
export { fetchUserById };
// 导出 user这个切片的reducer函数,用于管理user切片的状态
export default userSlice.reducer;
  1. fetchUserById.pending:表示当异步操作开始时,设置 status'loading'
  2. fetchUserById.fulfilled:表示当异步操作成功完成时,设置 status'idle',并将获取到的用户数据存储在 user 属性中。
  3. fetchUserById.rejected:表示当异步操作失败时,设置 status'idle',并将错误信息存储在 error 属性中。

在React组件中使用这个异步返回action对象的函数

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUserById } from './features/user/userSlice';

function UserProfile({ userId }) {
    const dispatch = useDispatch();
    const { status, user, error } = useSelector((state) => state.user);

    useEffect(() => {
        if (userId) {
            dispatch(fetchUserById(userId));
        }
    }, [dispatch, userId]);

    if (status === 'loading') {
        return <div>Loading...</div>;
    }

    if (error) {
        return <div>Error: {error}</div>;
    }

    if (!user) {
        return <div>User not found</div>;
    }

    return (
        <div>
            <h1>{user.name}</h1>
            <p>Email: {user.email}</p>
        </div>
    );
}

export default UserProfile;

在这个组件中,fetchUserById 触发异步修改仓库数据的方法,被用来派发获取用户数据的请求。组件根据 statususererror 的值来显示不同的内容。

configureStore

configureStore用于创建 Redux store仓库,并自动配置常用的中间件(如 redux-thunk)。

import { configureStore } from '@reduxjs/toolkit';
// 从 counter/counterSlice 文件中导入reducer,用于管理计数器的状态
import counterReducer from './counter/counterSlice';
// 从 user/userSlice 文件中导入 reducer,用于管理用户的状态
import userReducer from './user/userSlice';
// 创建 Store
const store = configureStore({
  // 配置对象中的 reducer 属性,它是一个对象,用于定义应用的状态树。
  // 每个键值对表示一个切片创建时定义的名称及其对应的管理切片的 reducer 函数。
  reducer: {
    counter: counterReducer,
    user: userReducer,
  },
});
// 导出 Store
export default store;

在React应用中使用这个 store

// main.jsx
import { createRoot } from 'react-dom/client' // 用于渲染页面的
import React from 'react' // 用于创建组件的
import { Provider } from 'react-redux'
import store from './store/index.js'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App />
    </Provider>
)

react-redux的Provider 组件将 Redux store 传递给整个应用,使得应用中的所有组件都可以访问 store

createSelector

createSelector用于创建缓存的选择器函数,具有缓存的选择器可以在输入不变的情况下缓存计算结果,避免不必要的重新计算从而提高性能。

createSelector:接受两个参数:

  • 输入选择器数组:第一个参数是一个数组,包含一个或多个基础选择器函数。这些基础选择器函数的返回值将作为第二个参数函数的输入。
  • 结果函数:第二个参数是一个函数,用于根据输入选择器数组的元素计算最终的输出。
import { createSelector } from '@reduxjs/toolkit';
// 基础选择器函数,从 Redux store 的状态对象中提取 todos的状态
// 参数state表示当前的 Redux store 状态
const selectTodos = (state) => state.todos;
// 返回值是过滤后的 todos,只包含 completed 属性为 true 的 todos。
const selectCompletedTodos = createSelector(
  [selectTodos],
  (todos) => todos.filter((todo) => todo.completed)
);
// 返回值是过滤后的 todos ,只包含 completed 属性为 false 的 todos
const selectIncompleteTodos = createSelector(
  [selectTodos],
  (todos) => todos.filter((todo) => !todo.completed)
);

createEntityAdapter

createEntityAdapter用于管理一组具有唯一标识符的实体适配器本质是对象,提供了一系列便捷的方法来操作这些对象。

createEntityAdapter:接受一个配置对象,用于创建实体适配器。配置对象的sortComparer属性是一个可选的比较函数,用于对实体进行排序。

实体适配器的getInitialState方法是用于获取初始状态对象,这个对象包含了实体适配器管理的实体列表以及其他自定义的状态属性。

实体适配器的getSelectors方法会返回一组选择器函数,用于从状态中选择特定的数据。

  • selectById:根据 ID 选择一个特定的数据。
  • selectIds:选择所有数据的 ID 列表。
  • selectEntities:选择所有数据的实体对象。
  • selectAll:选择所有数据的数组。
  • selectTotal:选择所有数据的总数。
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
// 创建实体适配器
const todosAdapter = createEntityAdapter({
  sortComparer: (a, b) => a.text.localeCompare(b.text),
});
// 定义初始状态
// todosAdapter.getInitialState是用于获取初始状态对象。这个对象包含了实体适配器管理的实体列表以及其他自定义的状态属性。
const initialState = todosAdapter.getInitialState({
  status: 'idle',
  error: null,
});
// 创建切片,包含切片名、切片初始状态、以及切片的reducer函数,用于处理特定的 action
const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    todoAdded: todosAdapter.addOne,
    todoRemoved: todosAdapter.removeOne,
    todosLoaded: todosAdapter.setAll,
  },
});
// 导出todosSlice.actions,包含由 createSlice 自动生成的所有返回action对象的函数
export const { todoAdded, todoRemoved, todosLoaded } = todosSlice.actions;
// 导出由实体适配器生成的选择器函数,用于从状态中选择特定的数据
export const { selectById, selectIds, selectEntities, selectAll, selectTotal } = todosAdapter.getSelectors();
// 导出由 createSlice 自动生成的 reducer 函数,用于管理 todos 切片的状态
export default todosSlice.reducer;

在React组件中使用:

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { todoAdded, todosLoaded, selectAll } from './features/todos/todosSlice';

function TodoList() {
    const dispatch = useDispatch();
    const todos = useSelector(selectAll);

    useEffect(() => {
        // 模拟从 API 加载 todos
        const loadTodos = async () => {
            const response = await fetch('https://api.example.com/todos');
            const data = await response.json();
            dispatch(todosLoaded(data));
        };

        loadTodos();
    }, [dispatch]);

    const handleAddTodo = () => {
        dispatch(todoAdded({ id: Date.now(), text: 'New Todo', completed: false }));
    };

    return (
        <div>
            <button onClick={handleAddTodo}>Add Todo</button>
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id}>{todo.text}</li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;

在这个组件中,todoAddedtodosLoaded 这两个方法被用来派发添加新 todo 和加载 todos 的请求。selectAll 选择器用于从 Redux store 中选择所有的 todos。

总结

创建切片总结:

import { createSlice } from '@reduxjs/toolkit';

// createAsyncThunk 创建一个异步action允许执行异步逻辑, 通常用于发出异步请求。
// 异步逻辑触发的时候会有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)
export const getData = createAsyncThunk();

// 创建一个 Slice(切片)
export const counterSlice = createSlice({
  // 命名空间,name会作为action type的前缀
  name: 'counter',
  
  // 初始化状态
  initialState: {},
  
  // 定义reducer更新状态的函数
  // reducer函数也就是组件中dispatch指定的action所使用的函数
  reducers: {
    xxx: (state, action) => {},
  },
  // extraReducers 字段让切片可以处理在别处定义的reducer更新状态的函数, 
  // 包括由 createAsyncThunk或其他切片生成的reducer更新状态的函数。
  extraReducers() {},
});

// 导出action函数
export const { xxx } = counterSlice.actions;

// 导出reducer,创建store
export default counterSlice.reducer;

案例演示:

需要先安装:

npm install @reduxjs/toolkit react-redux

创建不同切片:

// store/numSlice.js
import { createSlice } from '@reduxjs/toolkit'
const numSlice = createSlice({
    name: "num",
    initialState: {
        likeCount:6
    },
    reducers: {
        addLikeCount: (state, action) => {
            state.likeCount++;
        },
        minusLikeCount: (state) => {
            state.likeCount--;
        }
    }
})
export const { addLikeCount, minusLikeCount } = numSlice.actions
export default numSlice.reducer
// store/singerSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 异步加载数据,只异步请求数据
const loadAlbumApi = createAsyncThunk('singer/loadAlbum', async (userName) => {
    // 模拟网络请求,计时器延迟3秒返回数据
    const res = await new Promise((reslove, reject) => {
        setTimeout((...args) => {
            console.log(args, 'args')
            if (args[0] != '') {
                if (args[0] == '邓紫棋') {
                    reslove(['G.E.M.', '18', 'My Secret', 'Xposed', '新的心跳', '摩天动物园', '启示录'])
                }else {
                    reslove([])
                }
            } else {
                reject("未获取到歌手名")
            }
        }, 3000, userName)
    })
    return res
})
const singerSlice = createSlice({
    name: "singer",
    initialState: {
        singer: "G.E.M.",
        status: 'idle',
        error: "",
        album: []
    },
    reducers: {
        changeSinger: (state, action) => {
            state.singer = action.payload;
        },
        resetSinger: (state) => {
            state.singer = "G.E.M."
        }
    },
    // 根据异步获取的数据来修改切片状态
    extraReducers: (builder) => {
        builder.addCase(loadAlbumApi.pending, state => {
            state.status = 'loading';
        })
            .addCase(loadAlbumApi.fulfilled, (state, action) => {
                state.status = 'idle';
                state.error = "未查询到数据";
                state.album = action.payload;
            }).addCase(loadAlbumApi.rejected, (state, action) => {
                state.status = 'idle';
                state.album = [];
                state.error = action.error.message;
            })
    }
})
export { loadAlbumApi }
export const { changeSinger, resetSinger } = singerSlice.actions
export default singerSlice.reducer

创建仓库:

// store/index.js
import { configureStore } from '@reduxjs/toolkit'
import singerReducer from './singerSlice.js'
import numReducer from './numSlice.js'

const store = configureStore({
    reducer:{
        singer:singerReducer,
        num:numReducer
    }
})

export default store

将仓库数据关联到React

// main.jsx
import { createRoot } from 'react-dom/client' // 用于渲染页面的
import React from 'react' // 用于创建组件的
import { Provider } from 'react-redux'
import store from './store/index.js'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App msg='我是一条消息' />
    </Provider>
)

React组件中使用仓库管理:

// App.jsx
import React, { useState } from 'react';
import { useSelector, useStore, useDispatch } from 'react-redux'
import { addLikeCount, minusLikeCount } from './store/numSlice.js'
import { changeSinger, resetSinger, loadAlbumApi } from './store/singerSlice.js'
export default function App(props) {
  const store = useStore();
  // 输出仓库状态
  console.log(store.getState(), 'state')
  let [inputValue, setInputValue] = useState('');
  const changeInputHandle = (e) => {
    setInputValue(e.target.value)
  }
  const singer = useSelector((state) => state.singer.singer);
  const album = useSelector((state) => state.singer.album);
  const status = useSelector(state => state.singer.status);
  const error = useSelector(state => state.singer.error)
  const likCount = useSelector(state => state.num.likeCount)
  const dispatch = useDispatch();
  // dispatch action修改仓库数据
  const change = () => {
    // dispatch传入的action对象就不再是普通的js对象,而是切片暴露的actions属性中的修改方法
    // 这个传入的参数就是要修改的数据,它会作为action对象的payload属性来修改数据
    dispatch(changeSinger("G.E.M. 邓紫棋"));
  };
  const reset = () => {
    dispatch(resetSinger());
  };
  const increment = () => {
    dispatch(addLikeCount());
  }
  const decrement = () => {
    dispatch(minusLikeCount());
  }
  const btnSearchAlbum = () => {
    dispatch(loadAlbumApi(inputValue));
  }
  return (
    <div>
      <h1>My App</h1>
      <p>歌手:{singer}</p>
      <p>点赞数:{likCount}</p>
      <button onClick={increment}>点赞</button>
      <button onClick={decrement}>取消点赞</button>
      <button onClick={change}>修改歌手名</button>
      <button onClick={reset}>重置歌手名</button>
      <br />
      <h6>查询专辑:</h6>
      <input type="text" value={inputValue} onChange={changeInputHandle} placeholder='请输入歌手名' />
      <button onClick={btnSearchAlbum}>获取</button>
      <p>专辑:</p>
      {
        status == 'idle' ? album.length != 0 ? album.map((item, index) => {
          return <p key={index}>{item}</p>
        }) : <div>{error}</div> : <div>{status}</div>
      }
    </div>
  );
}

6.gif

7.gif