You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Redux Toolkit实现用户注册与身份验证功能?

解决方案

1. 修正并完善User Slice

首先,你的slice存在笔误(counterSlice应改为userSlice),且注册、验证均为异步操作,需使用Redux Toolkit的createAsyncThunk处理异步逻辑,配合extraReducers管理请求的不同状态(pending/fulfilled/rejected)。

更新后的slice代码:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 注册用户异步逻辑
export const registerUser = createAsyncThunk(
  'user/register',
  async (userData, { rejectWithValue }) => {
    try {
      const config = {
        headers: { 'Content-Type': 'application/json' }
      };
      const res = await axios.post('http://localhost:3000/api/users', userData, config);
      localStorage.setItem('token', res.data.token);
      return res.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

// 验证token获取用户信息异步逻辑
export const loadUser = createAsyncThunk(
  'user/loadUser',
  async (_, { rejectWithValue }) => {
    try {
      const token = localStorage.getItem('token');
      if (!token) return rejectWithValue('无有效token');
      
      const config = {
        headers: { 'Authorization': `Bearer ${token}` }
      };
      const res = await axios.get('http://localhost:3000/api/auth', config);
      return res.data;
    } catch (error) {
      localStorage.removeItem('token');
      return rejectWithValue(error.response.data);
    }
  }
);

const initialState = {
  token: localStorage.getItem('token'),
  isAuthenticated: null,
  loading: true,
  user: null,
  error: null // 新增错误状态用于展示请求失败信息
};

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    // 登出清除状态
    logout: (state) => {
      localStorage.removeItem('token');
      state.token = null;
      state.isAuthenticated = false;
      state.loading = false;
      state.user = null;
      state.error = null;
    },
    // 清除错误信息
    clearErrors: (state) => {
      state.error = null;
    }
  },
  extraReducers: (builder) => {
    // 处理注册请求各阶段状态
    builder
      .addCase(registerUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(registerUser.fulfilled, (state, action) => {
        state.loading = false;
        state.isAuthenticated = true;
        state.token = action.payload.token;
        state.user = action.payload.user; // 假设API返回包含用户信息
        state.error = null;
      })
      .addCase(registerUser.rejected, (state, action) => {
        state.loading = false;
        state.isAuthenticated = false;
        state.token = null;
        state.user = null;
        state.error = action.payload;
      })
      // 处理token验证请求各阶段状态
      .addCase(loadUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(loadUser.fulfilled, (state, action) => {
        state.loading = false;
        state.isAuthenticated = true;
        state.user = action.payload;
        state.error = null;
      })
      .addCase(loadUser.rejected, (state) => {
        localStorage.removeItem('token');
        state.loading = false;
        state.isAuthenticated = false;
        state.token = null;
        state.user = null;
      });
  }
});

export const { logout, clearErrors } = userSlice.actions;
export default userSlice.reducer;

2. 更新Register组件

移除原有的useState管理认证逻辑,改用Redux的useDispatch和useSelector处理状态。表单输入状态可保留在组件内(属于UI局部状态,无需全局共享),仅将用户认证相关状态交由Redux管理。

更新后的组件代码:

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { registerUser, clearErrors } from './userSlice';

// Material UI
import { Grid, Container, Box, Button, FormControl, InputLabel, Input, FormHelperText, Alert } from '@mui/material';

function Register() {
    const [formData, setFormData] = useState({
        name: '',
        email: '',
        password: '',
        password2: ''
    });

    const { name, email, password, password2 } = formData;
    const dispatch = useDispatch();
    const { loading, error } = useSelector(state => state.user);

    const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });

    const onSubmit = (e) => {
        e.preventDefault();
        if (password !== password2) {
            console.log("密码不匹配");
            return;
        }
        dispatch(registerUser({ name, email, password }));
    };

    // 组件卸载时清除错误信息
    useEffect(() => {
        return () => dispatch(clearErrors());
    }, [dispatch]);

    return (
        <Container>
            <Box p={4}>
                {/* 展示注册错误信息 */}
                {error && <Alert severity="error" sx={{ mb: 2 }}>{error.message || '注册失败'}</Alert>}
                <Grid container>
                    <form onSubmit={onSubmit}>
                        <Grid item md={12}>
                            <FormControl>
                                <InputLabel htmlFor="name">姓名</InputLabel>
                                <Input
                                    type='text'
                                    id='name'
                                    name='name'
                                    value={name}
                                    onChange={onChange}
                                    disabled={loading}
                                />
                                <FormHelperText>请输入全名</FormHelperText>
                            </FormControl>
                        </Grid>
                        <Grid item md={12}>
                            <FormControl>
                                <InputLabel htmlFor="email">邮箱</InputLabel>
                                <Input
                                    id='email'
                                    type='email'
                                    name='email'
                                    value={email}
                                    onChange={onChange}
                                    disabled={loading}
                                />
                                <FormHelperText>请输入邮箱</FormHelperText>
                            </FormControl>
                        </Grid>
                        <Grid item md={12}>
                            <FormControl>
                                <InputLabel htmlFor="password">密码</InputLabel>
                                <Input
                                    id="password"
                                    type='password'
                                    name='password'
                                    value={password}
                                    onChange={onChange}
                                    disabled={loading}
                                />
                                <FormHelperText>请输入密码(至少6位)</FormHelperText>
                            </FormControl>
                        </Grid>
                        <Grid item md={12}>
                            <FormControl>
                                <InputLabel htmlFor="password2">确认密码</InputLabel>
                                <Input
                                    id="password2"
                                    type='password'
                                    name='password2'
                                    value={password2}
                                    onChange={onChange}
                                    disabled={loading}
                                />
                                <FormHelperText>请再次输入密码</FormHelperText>
                            </FormControl>
                        </Grid>
                        <Grid item md={12}>
                            <Button type='submit' variant="contained" color="primary" disabled={loading}>
                                {loading ? '注册中...' : '注册'}
                            </Button>
                        </Grid>
                    </form>
                </Grid>
            </Box>
        </Container>
    );
}

export default Register;

3. 关键说明

  • 异步操作必须通过createAsyncThunk实现,因为Redux的reducers是纯函数,无法直接处理异步逻辑。extraReducers用于监听异步请求的不同阶段,更新全局状态。
  • 表单输入状态保留在组件内是合理的,仅将需要全局共享的认证状态(token、登录状态、用户信息等)交由Redux管理。
  • loadUser可在App组件初始化时调用,用于验证本地存储的token,保持用户登录状态。
  • 新增的错误状态可在组件中展示具体的失败原因,提升用户体验。

内容的提问来源于stack exchange,提问作者tukitooo1998

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 16:49:54