React-Redux
1、安装
npm i redux react-redux @reduxjs/toolkit
2、基础使用方式(无 Toolkit)
(1)核心Api
- createStore:创建数据仓库;
- store.dispatch():用于派发action,执行修改动作;
- store.getState():用于获取store里面的数据;
- store.subscribe():订阅store的修改,只要store发生改变,回调函数就会被执行;
(2)store的创建和使用
创建仓库:createStore()
// src/store/index.js
import { createStore } from "redux";
import reducer from "./reducer";const store = createStore(reducer);export default store;
通过reducer提供数据源,reducer是一个纯函数
// src/store/reducer.js
const defaultState = { count: 0
}const reducer = (state = defaultState, action) => {switch (action.type) {case "REDUCE":state.count--;break;case "ADD":state.count++;break;default: break;}return state;
}export default reducer;
通过store.getState().keys来获取仓库中的数据
//组件内部引入仓库
import store from "./store/index";//获取数据
<h2>{store.getState().count}</h2>
(3)数据的修改和订阅
在组件内部定义要执行的动作action,并触发
reduceCount() {let action = {type: "REDUCE",mydata: 10}store.dispatch(action)
}<button onClick={this.reduceCount.bind(this)}>-</button>
接收动作并修改数据:通过dispatch触发动作后,会将action传递到reducer函数的第二个参数中
const reducer = (state = defaultState, action) => {switch (action.type) {case "REDUCE":state.count--;break;default: break;}return state;
}
组件中及时响应更新的数据:订阅
componentDidMount() {store.subscribe(this.updatePage.bind(this))
}
updatePage() {this.setState(store.getState())
}
3、使用 Redux Toolkit
目录结构:
(1)创建Slice
// store/modules/counterStore.js
import { createSlice } from '@reduxjs/toolkit'const counterSlice = createSlice({name: 'counter',// 初始化stateinitialState: {count: 0},// 修改状态的方法 同步方法 支持直接修改reducers: {increment: (state) => {state.count++},decrement: (state) => {state.count--}}
})export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
(2)配置store
// store/inedx.js
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from './modules/counterStore.js'export default configureStore({reducer: {counter: counterReducer}
})
(3)为react注入store
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './store';
import { Provider } from 'react-redux';const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>
);
(4)组件中使用(Hooks 写法)
// App.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store/modules/counterStore';function App() {const { count } = useSelector(state => state.counter)const dispatch = useDispatch()return (<div className="App"><button onClick={() => dispatch(decrement())}>-</button>{ count }<button onClick={() => dispatch(increment())}>+</button></div>);
}export default App;
(5)提交action时传参
// App.js
import { increment, decrement, addToNum } from './store/modules/counterStore';<button onClick={() => dispatch(addToNum(20))}>+20</button>// store/modules/counterStore.js
addToNum: (state, action) => {state.count += action.payload
}
4、异步状态操作
上面的操作都是同步更新数据,如果要实现异步操作,可以使用 createAsyncThunk:
// store/modules/channelStore.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios'; // 定义异步 Action
export const fetchChannelList = createAsyncThunk('channel/fetchChannelList', // [切片名称]/[动作名称]async (url, thunkAPI) => {const res = await axios.get(url)return res.data.data.channels}
);const channelSlice = createSlice({name: 'channel',initialState: {channelList: [],loading: false,error: null},reducers: {},extraReducers: (builder) => { // 处理异步操作的结果builder.addCase(fetchChannelList.pending, (state) => { // 请求进行中state.loading = truestate.error = null}).addCase(fetchChannelList.fulfilled, (state, action) => { // 请求成功state.loading = falsestate.channelList = action.payload}).addCase(fetchChannelList.rejected, (state, action) => { // 请求失败state.loading = falsestate.error = action.error.message})}
})export default channelSlice.reducer
// App.js
import { useSelector, useDispatch } from 'react-redux';
import { fetchChannelList } from './store/modules/channelStore';
import { useEffect } from 'react';
function App() {const { channelList, loading, error } = useSelector(state => state.channel)const dispatch = useDispatch()// 使用useEffect触发异步请求函数useEffect(() => {dispatch(fetchChannelList('http://geek.itheima.net/v1_0/channels'))}, [dispatch])return (<div className="App">{ loading && <div>加载中...</div>}{ error && <div>Error:{ error }</div>}<ul>{ channelList.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>);
}export default App;