【更新中】【React】基础版React + Redux实现教程,自定义redux库和react-redux库
本项目是一个在react中,使用 redux 管理状态的基础版实现教程,用简单的案例练习redux的使用,旨在帮助学习 redux 的状态管理机制,包括 store
、action
、reducer
、dispatch
等核心概念。
项目地址:https://github.com/YvetW/redux-mini-demo
注:项目中包含多个版本的代码,有助于理解redux,运行前请先选择需要的版本,修改目录名为src。
版本:
- React v19
- Redux v5
- React-Redux v9
- TypeScript
- Vite
1. 初始化项目
使用 Vite
创建 React+TypeScript 项目:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
主要目录结构
my-app/
├── src/
│ ├── redux/
│ │ ├── action-types.ts # 定义 Action Types
│ │ ├── actions.ts # 定义 Actions
│ │ ├── reducers.ts # 定义 Reducers
│ │ ├── store.ts # 创建 Store
│ ├── App.tsx # 组件主入口
│ ├── main.tsx # 入口文件,渲染 App
│ ├── index.css # 样式文件
├── package.json # 依赖管理
├── tsconfig.json # TypeScript 配置
└── vite.config.ts # Vite 配置
2. 在 React 组件中使用 Redux
本版本不使用 react-redux
或 @reduxjs/toolkit
,而是直接使用 Redux 的原生 createStore
API(已过时,但有助于理解 Redux 的核心概念)。
2.1 创建 Action Types (action-types.ts
)
- 在 Redux 中,所有的
action
都应有一个唯一的type
,定义type
常量。 - 例如:
INCREMENT
、DECREMENT
、ADD_MESSAGE
。
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_MESSAGE = 'add_message'
2.2 创建 Reducers (reducers.ts
)
- Redux 需要使用
reducers
函数处理state
变化。 - 创建两个
reducer
:count
和messages
。 - 根据不同
action
类型更新state
,返回新的state
。
import {combineReducers} from 'redux';
import {ADD_MESSAGE, DECREMENT, INCREMENT} from './action-types.ts';
import {Action} from './actions.ts';
// 管理count
const initCount: number = 0;
function count(state: number = initCount, action: Action): number {
console.log('count', state, action);
switch (action.type) {
case INCREMENT:
return state + action.data;
case DECREMENT:
return state - action.data;
default:
return state;
}
}
// 管理messages
const initMessages: string[] = [];
function messages(state: string[] = initMessages, action: Action): string[] {
console.log('messages', state, action);
switch (action.type) {
case ADD_MESSAGE:
return [action.data, ...state]
default:
return state;
}
}
export default combineReducers({count, messages});
// 整体state状态结构: {count: 2, messages: ['xx', 'xxx']}
2.3 创建 Actions (actions.ts
)
- Actions 是 Redux store 唯一的数据来源。
- 创建
increment
、decrement
和addMessage
action。
import {INCREMENT, DECREMENT, ADD_MESSAGE} from './action-types.ts';
// 定义 CountAction 的类型
interface CountAction {
type: typeof INCREMENT | typeof DECREMENT;
data: number;
}
// 定义 MessageAction 的类型
interface MessageAction {
type: typeof ADD_MESSAGE; // 只有 ADD_MESSAGE 类型
data: string; // data 是一个字符串
}
// 联合类型
export type Action = CountAction | MessageAction;
export const increment = (number: number): CountAction => ({type: INCREMENT, data: number});
export const decrement = (number: number): CountAction => ({type: DECREMENT, data: number});
export const add_message = (message: string): MessageAction => ({type: ADD_MESSAGE, data: message});
2.4 创建 Store (store.ts
)
- 通过
createStore(rootReducer)
创建 Reduxstore
,集中管理应用状态。
import { createStore } from 'redux';
import reducers from './reducers'; // 包含多个reducer的reducer
export default createStore(reducers)
2.5 在 App.tsx
中使用 store
- 通过
props.store.getState()
获取state
,控制组件渲染。 - 通过
props.store.dispatch(action)
触发状态更新。
import {useState} from 'react';
import {Store} from 'redux';
import {Action, increment, decrement, add_message} from './redux/actions.ts';
// 为 App 组件定义一个 Props 类型,这个类型包含 store
interface AppProps {
store: Store<{
count: number;
messages: string[];
}, Action>;
}
function App({store}: AppProps) {
const [selectedValue, setSelectedValue] = useState<number>(1);
const [message, setMessage] = useState<string>('');
const count = store.getState().count;
const messages = store.getState().messages;
function handleIncrement() {
store.dispatch(increment(selectedValue));
}
function handleDecrement() {
store.dispatch(decrement(selectedValue));
}
// 奇数增加
function handleIncrementIfOdd() {
// setCount 是异步的,如果 Redux 状态更新了,count 可能不会立即反映出来,直接从 store.getState() 读取最新的 count
if (store.getState().count % 2 === 1) {
store.dispatch(increment(selectedValue));
}
}
// 异步增加
function handleIncrementIfAsync() {
setTimeout(() => {
store.dispatch(increment(selectedValue));
}, 1000);
}
function handleAdd() {
if (message.trim()) {
store.dispatch(add_message(message));
setMessage('');
}
}
return (
<div className="App">
<div className="count">
click <span>{count}</span> times
</div>
<select
id="number" value={selectedValue}
// <option> 的 value 默认是字符串类型(即使它是一个数字),这里强制转换为数字
onChange={event => setSelectedValue(Number(event.target.value))}
>
{[1, 2, 3, 4, 5].map((item) => (
<option value={item} key={item}>{item}</option>
))}
</select>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
<button onClick={handleIncrementIfOdd}>increment if odd</button>
<button onClick={handleIncrementIfAsync}>increment async</button>
<hr />
<input
type="text"
value={message}
onChange={event => setMessage(event.target.value)}
/>
<button onClick={handleAdd}>add text</button>
<ul>
{
messages.map((msg, index) => (
<li key={index}>{msg}</li>
))
}
</ul>
</div>
);
}
export default App;
2.6 在 main.tsx
中订阅 Store 并触发渲染
createRoot()
挂载App
组件。- 通过
store.subscribe()
监听state
变化,每次dispatch
触发state
变更时重新渲染App
。 - React 18 及以上使用
createRoot()
进行渲染,注意避免重复createRoot()
调用。
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import './index.scss';
import App from './App';
import store from './redux/store.ts';
const root = createRoot(document.getElementById('root')!); // 只调用一次 createRoot()
// root.render() 负责后续更新
function render() {
root.render(
<StrictMode>
<App store={store} />
</StrictMode>,
);
}
// 初次渲染
render();
// 订阅 store,state 变化时触发 render
store.subscribe(render);
3. 自定义 redux 库
自己定义一个简易的 redux 库,主要针对 createStore 和 combineReducers 的核心概念。
简单来说,createStore
用来创建存储和管理应用状态的对象,而 combineReducers
用来将多个子 reducer 合并成一个单一的 reducer。
注:完整版请看项目代码。
3.1 createStore
- 作用:用于创建一个 Redux Store,负责管理应用的状态。
- 接收的参数:
reducer
:根 reducer,决定如何根据action
更新state
。- 可选的
preloadedState
:初始化时的状态。
- 返回的
store
对象包含:dispatch
:分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listenergetState
:得到内部管理的state对象subscribe
:监听state
的变化,并在状态变化时触发回调。
export function createStore<S, A extends Action>(rootReducer: Reducer<S, A>): Store<S> {
// 内部state,第一次调用reducer得到初始状态并保存
let state: S;
// 内部保存n个listener的数组
const listeners: (() => void)[] = [];
// 初始调用reducer得到初始state值
state = rootReducer(undefined, {type: '@mini-redux'} as A);
// 得到内部管理的state对象
function getState() {
return state;
}
// 分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listener
function dispatch(action: Action) {
// 调用reducer,得到一个新的state,保存
state = rootReducer(state, action as A);
// 调用listeners中所有的监视回调函数
listeners.forEach(listener => listener());
}
// 订阅state变化,产生新的状态,也就是dispatch时才执行
function subscribe(listener: () => void) {
listeners.push(listener); // 将listener加入到订阅列表
// 返回一个取消订阅的函数(main.ts 通常不需要手动取消,只执行一次,store.subscribe(render) 只执行一次,不会不断创建新的 listener,所以 不会造成内存泄漏。)
return () => {
const index = listeners.indexOf(listener);
if (index >= 0) {
listeners.splice(index, 1);
}
};
}
// 返回一个store对象
return {getState, dispatch, subscribe};
}
3.2 combineReducers
- 作用:将多个子 reducer 组合成一个根 reducer。
- 接收的参数:
reducers
:一个对象,其中包含多个子 reducer,每个子 reducer 负责管理state
的一部分。
- 这个函数返回一个新的 reducer,能够根据
action
调用每个子 reducer,更新对应的state
部分。
// 接收一个包含多个reducer函数的对象,返回一个新的reducer函数
export function combineReducers<S, A extends Action>(
reducers: { [K in keyof S]: Reducer<S[K], A> }
) {
return function (state: Partial<S> = {} as S, action: A): S { // 这个 rootReducer 函数会传给 createStore()
return Object.keys(reducers).reduce((newState, key) => {
// 确保类型安全:key 是 S 类型的有效键
newState[key as keyof S] = reducers[key as keyof S](state[key as keyof S], action)
return newState
}, {} as S)
};
}
4. react-redux
react-redux
是一个官方推荐的 React 库,它将 Redux 状态管理与 React 组件结合起来,帮助你更方便地在 React 应用中使用 Redux。它主要提供了两个核心的 API:Provider
和 connect
。我们可以通过这些 API 将 Redux 的功能与 React 组件连接,轻松地将状态存储、分发操作等功能集成到 React 中。
1. Provider
Provider
是一个 React 组件,它的作用是将 Redux store 传递给整个应用。它让组件树中的所有组件都能访问到 Redux store,因此,Provider
通常包裹在应用的最外层。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; // 导入 Provider
import store from './store'; // 导入 store
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
store
是你通过createStore
创建的 Redux store。Provider
会将 Redux store 传递给组件树中的所有组件。
2. connect
connect
是一个高阶组件,它让你能够将 Redux 的 state
和 dispatch
方法连接到 React 组件中,进而让组件能够访问 Redux store 中的状态,并向 store 发送 action。
connect
语法
import { connect } from 'react-redux';
connect
函数接受两个参数:
- mapStateToProps:一个函数,用来将 Redux store 的状态映射到组件的
props
上。 - mapDispatchToProps:一个函数,用来将 Redux 的
dispatch
方法映射到组件的props
上,使得组件能够派发 action。
使用 connect
import React from 'react';
import { connect } from 'react-redux'; // 导入 connect
// 一个简单的 React 组件
const Counter = ({ count, increment }) => {
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
// mapStateToProps:将 Redux store 中的状态映射为组件的 props
const mapStateToProps = (state) => ({
count: state.count,
});
// mapDispatchToProps:将 Redux 的 dispatch 方法映射为组件的 props
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
});
// 使用 connect 连接 Redux 状态和组件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
3. 核心概念
-
mapStateToProps
:从 Redux 的store
中提取你需要的部分数据,并将它们映射到组件的props
中。这样,组件就能通过props
访问 Redux 状态。 -
mapDispatchToProps
:将 Redux 的dispatch
方法映射到组件的props
,让组件能够触发 Redux 中的action
。你可以将dispatch
与action
包装在一起,简化代码。 -
connect
:是一个高阶组件(HOC),通过将mapStateToProps
和mapDispatchToProps
传递给它,将 Redux 和 React 组件连接起来。通过connect
,React 组件就能接收到 Redux 的状态和分发action
的能力。
4. 如何工作
- 当应用的状态变化时,
mapStateToProps
会重新运行,确保组件的props
始终是最新的。 mapDispatchToProps
将会将dispatch
和action
绑定到组件的props
中,让组件能够触发action
,从而更新状态。
5. useSelector
和 useDispatch
Hook(React-Redux v7.1+)
在 React-Redux v7.1 及以上版本中,你还可以使用 React Hooks 来代替 connect
,使得代码更加简洁:
useSelector
:让你从 Redux store 中获取状态数据。useDispatch
:让你获取dispatch
方法并用于派发action
。
示例:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
const Counter = () => {
const count = useSelector((state) => state.count); // 从 Redux 中获取 count 状态
const dispatch = useDispatch(); // 获取 dispatch 方法
const increment = () => {
dispatch({ type: 'INCREMENT' }); // 派发 action 更新 count
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
结论
本项目通过最基础的 Redux 实现,帮助理解 Redux 的核心概念,包括 store
、action
、reducer
、dispatch
,同时避免 react-redux
的复杂性,适合初学者学习 Redux 的工作原理。
如果要在实际项目中使用 Redux,建议使用 @reduxjs/toolkit
和 react-redux
,可以大幅减少 Redux 代码量,提高开发效率。
后续会继续编写使用 @reduxjs/toolkit
和 react-redux
的教程,欢迎点赞收藏关注!感恩比心~