Redux Thunk同步数据异常:组件state未即时更新求助
相关代码片段
Action Creator
export function pickup(latlng) { return function(dispatch) { dispatch({ type: PICKUP_STATE,payload:latlng }); }; }
Reducer
import { PICKUP_STATE, PICKUP_ADD, DROPOFF_STATE } from '../actions/types'; export default (state={},action) => { const INITIAL_STATE = { pickup: '', pickupAdd:'' }; switch(action.type) { case PICKUP_STATE: console.log(action.payload) return {...state,pickup:action.payload}; case PICKUP_ADD: return{...state,pickupAdd:action.payload}; case DROPOFF_STATE: return {...state,dropoff:action.payload} default: return state; } }
Map Component
import { connect } from "react-redux"; import * as actions from "../actions" class Map extends React.Component { componentWillReceiveProps(nextprops) { if (nextprops.pickupProps !== undefined) { this.setState({ pick: nextprops.pickupProps }, () => { console.log(this.state.pick); }); } } isPickEmpty(emptyPickState) { this.props.pickup(emptyPickState); // setTimeout(() =>{ console.log('sdkjlfjlksd',this.state.pick) },3000); console.log(this.state.pick); } } const mapStateToProps = (state) => { return { pickupProps:state.BookingData.pickup, pickupAddProps: state.BookingData.pickupAdd } } export default connect(mapStateToProps,actions)(Map);
App.js (Root File)
import React from "react"; import ReactDOM from "react-dom"; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import "normalize.css/normalize.css" import "./styles/styles.scss"; import { Router, Route, IndexRoute, browserHistory } from 'react-router'; import reduxThunk from 'redux-thunk'; import { composeWithDevTools } from 'redux-devtools-extension'; import AppRouter from './routers/AppRouter'; import reducers from './reducers'; import {AUTH_USER} from "./actions/types"; const middleware = [ reduxThunk, ]; const store = createStore(reducers, composeWithDevTools( applyMiddleware(...middleware), )); const token = localStorage.getItem('token'); if(token){ store.dispatch({type:AUTH_USER}); } ReactDOM.render( <Provider store={store}> <AppRouter /> </Provider> , document.getElementById('app'));
问题描述
在Map组件中调用isPickEmpty()方法时,会触发action creator this.props.pickup(false)(已通过redux-devtools确认值已更新为false),但此时直接打印存储在componentWillReceiveProps(nextprops)中的pick state,显示的仍是默认值而非false;
但将打印逻辑放入setTimeout(() =>{console.log('sdkjlfjlksd',this.state.pick) }, 3000);中时,能正确获取到false值
我原以为Redux Thunk是同步工作的,请问为何此处无法同步生效?
更新
我已定位问题:componentWillReceiveProps中调用的setState是异步的,因此在isPickEmpty方法中获取的是旧值。
请问该如何处理setState,或有其他解决方案?
解决方案
你说得对,核心问题就是setState的异步特性——哪怕Redux的状态更新是同步完成的,React的状态更新也不会立即生效,这就是为什么你直接打印拿不到新值,而setTimeout能拿到。下面给你几个可行的解决思路:
1. 直接使用Redux中的状态,而非组件本地state
既然你已经通过mapStateToProps把Redux的pickupProps映射到组件props里了,完全可以跳过组件本地的pick状态,直接用this.props.pickupProps。这样就不用处理setState的异步问题,因为Redux状态更新后,props会同步更新,你在任何地方都能拿到最新值:
isPickEmpty(emptyPickState) { this.props.pickup(emptyPickState); // 注意:这一行同步执行时还是旧值,但组件重新渲染后会拿到新的props } // 在render里直接使用props(渲染时会拿到最新值) render() { return <div>当前Pick状态:{this.props.pickupProps}</div> }
2. 利用Redux store的getState方法获取最新值
如果确实需要在dispatch之后立即拿到Redux中的最新状态,可以直接从store获取:
首先你需要在组件中导入store(确保store是单例导出的):
// 假设你的store是在src/store.js中导出的 import store from '../store'; isPickEmpty(emptyPickState) { this.props.pickup(emptyPickState); // dispatch之后直接从store取最新的Redux状态 const latestPickup = store.getState().BookingData.pickup; console.log(latestPickup); // 这里会立即获取到false }
3. 用setState的回调函数处理后续逻辑
如果必须依赖组件本地state的更新,那一定要把需要用新状态的逻辑放在setState的第二个参数(回调函数)里,这个回调会在状态更新完成后执行:
componentWillReceiveProps(nextprops) { if (nextprops.pickupProps !== undefined) { this.setState({ pick: nextprops.pickupProps }, () => { // 所有需要用到this.state.pick的逻辑都放在这里 console.log('更新后的pick状态:', this.state.pick); }); } }
不过这个方法的局限性是,你没法在isPickEmpty里直接触发这个回调,因为setState是在生命周期方法中执行的,所以更推荐前两种方法。
4. 替换过时的生命周期方法
另外要注意,componentWillReceiveProps已经被React标记为过时(legacy)方法,官方推荐用componentDidUpdate来替代,它会在组件props更新后执行,更符合React的生命周期规范:
componentDidUpdate(prevProps) { // 只有当pickupProps发生变化时才更新本地state if (prevProps.pickupProps !== this.props.pickupProps && this.props.pickupProps !== undefined) { this.setState({ pick: this.props.pickupProps }, () => { console.log('更新后的pick:', this.state.pick); }); } }
关于Redux Thunk的补充说明
你提到以为Redux Thunk是同步工作的——其实你的这个action creator确实是同步的,因为里面没有异步操作(比如API请求),dispatch是立即执行的,Redux store的状态也会同步更新。问题出在React的状态更新是异步的,和Redux Thunk无关。如果你的action里有异步操作(比如fetch数据),那Thunk才会体现它的异步处理能力。
内容的提问来源于stack exchange,提问作者zulqarnain

