React-Redux如何连接Express+MySQL后端获取数据?
Great question—this is one of the most common sticking points when you’re first tying Redux to a backend API. Let’s clear this up with concrete examples and the standard best practices used in the React/Redux ecosystem.
First: Never fetch data inside a reducer
Redux reducers are pure functions—they should only take the current state and an action, then return a new state. They can’t contain side effects like API calls, database queries, or fetch() requests. Doing so breaks Redux’s core principles and will lead to unpredictable state updates.
The standard solution: Async action creators with Redux Thunk
The best practice here is exactly what you hinted at: handle API calls in action creators, using a middleware like Redux Thunk to support asynchronous logic in actions. Redux Thunk lets you write action creators that return functions instead of plain objects, so you can delay dispatching actions until the API call completes.
Let’s walk through a full example matching your setup:
1. Backend: Express/MySQL users.js (your existing setup)
First, confirm your backend route is working to return users:
// backend/routes/users.js const express = require('express'); const router = express.Router(); const mysql = require('mysql'); // Configure DB connection const db = mysql.createConnection({ host: 'localhost', user: 'your_db_user', password: 'your_db_password', database: 'your_db_name' }); // Get all users router.get('/', (req, res) => { db.query('SELECT * FROM users', (err, results) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(results); }); }); module.exports = router;
2. Redux Setup: Install dependencies
First, add the tools you’ll need:
npm install redux-thunk axios
redux-thunk: The middleware to handle async actionsaxios: A popular library for making API requests (you can use nativefetchtoo)
3. Configure Redux Store with Thunk
Update your store to use the Thunk middleware:
// store.js import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import userReducer from './reducers/userReducer'; const rootReducer = combineReducers({ users: userReducer // Your user state will live under `state.users` }); // Apply Thunk middleware to support async actions const store = createStore(rootReducer, applyMiddleware(thunk)); export default store;
4. Write Async Action Creator
Create an action creator to handle fetching users, including loading and error states:
// actions/userActions.js import axios from 'axios'; // Action types (best practice to avoid typos) export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'; export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'; // Async action creator export const fetchUsers = () => { return (dispatch) => { // Dispatch a "loading" action first dispatch({ type: FETCH_USERS_REQUEST }); // Make API call axios.get('/users') .then(response => { // Dispatch success action with user data dispatch({ type: FETCH_USERS_SUCCESS, payload: response.data }); }) .catch(error => { // Dispatch failure action with error message dispatch({ type: FETCH_USERS_FAILURE, payload: error.message }); }); }; };
5. Update Reducer to Handle Actions
Modify your user reducer to respond to the async actions and update state:
// reducers/userReducer.js import { FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS, FETCH_USERS_FAILURE } from '../actions/userActions'; const initialState = { loading: false, users: [], // Start empty, as you had before error: '' }; const userReducer = (state = initialState, action) => { switch (action.type) { case FETCH_USERS_REQUEST: return { ...state, loading: true }; case FETCH_USERS_SUCCESS: return { loading: false, users: action.payload, // Populate users from API response error: '' }; case FETCH_USERS_FAILURE: return { loading: false, users: [], error: action.payload }; default: return state; } }; export default userReducer;
6. Dispatch the Action in Your Component
In your component, dispatch the fetchUsers action when the component mounts. Here’s how to do it for both class and function components:
Class Component:
// components/UserList.js import React, { Component } from 'react'; import { connect } from 'react-redux'; import { fetchUsers } from '../actions/userActions'; class UserList extends Component { componentDidMount() { // Dispatch the async action when the component loads this.props.fetchUsers(); } render() { const { loading, users, error } = this.props; return ( <div> <h2>User List</h2> {loading && <p>Loading users...</p>} {error && <p className="error">Error: {error}</p>} <ul> {users.map(user => ( <li key={user.id}>{user.name} - {user.email}</li> ))} </ul> </div> ); } } // Map Redux state to component props const mapStateToProps = (state) => { return { loading: state.users.loading, users: state.users.users, error: state.users.error }; }; // Map dispatch to component props const mapDispatchToProps = (dispatch) => { return { fetchUsers: () => dispatch(fetchUsers()) }; }; export default connect(mapStateToProps, mapDispatchToProps)(UserList);
Function Component (with Hooks):
// components/UserList.js import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchUsers } from '../actions/userActions'; const UserList = () => { const dispatch = useDispatch(); const { loading, users, error } = useSelector(state => state.users); useEffect(() => { // Dispatch the action on component mount dispatch(fetchUsers()); }, [dispatch]); return ( <div> <h2>User List</h2> {loading && <p>Loading users...</p>} {error && <p className="error">Error: {error}</p>} <ul> {users.map(user => ( <li key={user.id}>{user.name} - {user.email}</li> ))} </ul> </div> ); }; export default UserList;
Key Takeaways
- Keep reducers pure: No API calls, side effects, or async logic here—they only update state based on actions.
- Async logic lives in action creators: Use middleware like Redux Thunk to handle delayed action dispatching.
- Track request states: Include loading/error states in your reducer to give users feedback while data loads.
- Trigger actions in component lifecycle: Dispatch the async action when your component mounts (or when a user interacts with it, like clicking a button).
内容的提问来源于stack exchange,提问作者Ryne

