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

React Redux中setUsers两次触发 导致用户列表重复渲染问题

问题现象

向服务端请求用户列表接口后,页面渲染的用户数量为预期的2倍,排查发现setUsers逻辑被重复触发,无法定位根因。

相关业务代码

friends-reducer.js

const FOLLOW = 'FOLLOW';
const UNFOLLOW = 'UNFOLLOW';
const SET_USERS = 'SET_USERS';

let initialState = {
  users: []
};

const FriendsReducer = (state = initialState, action) => {
  switch (action.type) {
    case FOLLOW:
      return {
        ...state,
        users: state.users.map(u => {
          if (u.id === action.userId) {
            return { ...u, followed: true }
          }
          return u;
        })
      }

    case UNFOLLOW:
      return {
        ...state,
        users: state.users.map(u => {
          if (u.id === action.userId) {
            return { ...u, followed: false }
          }
          return u;
        })
      }

    case SET_USERS:
      return {
        ...state,
        users: [...state.users, ...action.users]
      }

    default:
      return state;
  }
}

export const followAC = (userId) => ({ type: FOLLOW, userId })
export const unfollowAC = (userId) => ({ type: UNFOLLOW, userId })
export const setUsersAC = (users) => ({ type: SET_USERS, users })

export default FriendsReducer;

FriendsConteiner.jsx

import Friends from './Friends';
import { connect } from "react-redux";
import { followAC, setUsersAC, unfollowAC } from '../../Redux/friends-Reduser';
import React from 'react';
import axios from 'axios';

class FriendsConteiner extends React.Component {

  componentDidMount() {
    if (this.props.users.length === 0) {

      axios.get("https://social-network.samuraijs.com/api/1.0/users").then(response => {
        this.props.setUsers(response.data.items)
      });
    }
  }
  render() {
    return <Friends users={this.props.users}
      follow={this.props.follow}
      unfollow={this.props.unfollow}
    />
  }
}

const mapStateToProps = (state) => {
  return {
    users: state.FriendsPage.users
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    follow: (userId) => {
      dispatch(followAC(userId))
    },
    unfollow: (userId) => {
      dispatch(unfollowAC(userId))
    },
    setUsers: (users) => {
      dispatch(setUsersAC(users))
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(FriendsConteiner);

Friends.jsx

import React from 'react';
import User from './User';

const Friends = (props) => {
  return (
    <div className='Friends'>
      <div className="selectedPage">
        <span>1</span>
        <span>2</span>
        <span>3</span>
        <span>4</span>
        <span>5</span>
      </div>
      {
        props.users.map(u => <User
          followed={u.followed}
          id={u.id}
          key={u.id}
          photos={u.photos.smoll}
          fullName={u.name}
          follow={props.follow}
          unfollow={props.unfollow}
        />)
      }
    </div>
  );
}

export default Friends;

User.jsx

import React from 'react';
import userPhoto from '../../assets/images/user.webp';

const User = (props) => {
  return (
    <div className='user' key={props.id}>
      <div className='user__present'>
        <div className='user__img'><img src={props.photos != null ? props.photos : userPhoto} alt='user' /></div>
        <div className='user__followed'>{
          props.followed ?
            <button onClick={() => props.unfollow(props.id)}>unfollow</button>
            : <button onClick={() => props.follow(props.id)}>follow</button>
        }</div>
      </div>
      <div className='user__info'>
        <div className="user__name">{props.fullName}</div>
        <div className="user__status">{props.status}</div>
        <div className='user__location'>
          <div className="user__city">{"props.city"}</div>
          <div className="user__country">{"props.country"}</div>
        </div>
      </div>
    </div>
  );
}

export default User;

根因分析

两个问题叠加导致用户列表重复渲染:

  1. React 18 严格模式开发环境的重复执行机制:如果项目入口开启了<React.StrictMode>,开发环境下类组件的componentDidMount会执行两次,用于检测非预期的副作用。写的if (this.props.users.length === 0)判断无法拦住重复请求——两次componentDidMount是同步执行的,此时异步接口还未返回,store里的users始终是空数组,两次判断都会通过,最终发起两次接口请求、两次触发setUsers的dispatch。
  2. SET_USERS处理逻辑错误:当前reducer中SET_USERS的逻辑是users: [...state.users, ...action.users],属于追加列表而非替换列表。两次dispatch会把同一批用户数据追加两次,最终渲染数量翻倍。

修复方案

  • 第一步:修正reducer中SET_USERS的逻辑,首次加载场景直接替换原有用户列表。如果后续需要支持分页加载,可以单独新增分页追加的action,或者给setUsersAC增加参数区分操作类型:
case SET_USERS:
  return {
    ...state,
    users: action.users // 直接替换,不再追加
  }
  • 第二步:处理严格模式下的重复请求问题,二选一即可:
    • 不需要严格模式的副作用检测能力:直接删除项目入口文件中包裹应用的<React.StrictMode>标签即可。注意严格模式仅在开发环境生效,生产环境打包后不会出现重复执行问题。
    • 保留严格模式:给请求增加状态锁,比如在组件实例上挂载isRequested标记,第一次发起请求后将标记设为true,后续判断标记为true时直接终止请求逻辑;也可以用AbortController取消重复发起的请求。

额外代码问题:Friends.jsx中读取用户头像的字段u.photos.smoll存在拼写错误,正确字段名为small,会导致头像地址读取失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:06:22