React Hook+Redux+Router应用访问未触发action的页面崩溃如何解决?
问题根因
- 崩溃的核心原因是
Dashboard组件直接读取DataReducer.data.users,当Redux中data还未完成加载、加载失败或初始为空时,customData为undefined,访问users属性直接抛出JS异常 - 现有代码仅处理了数据加载中的加载遮罩,未覆盖加载失败、数据为空的异常场景
- 没有把刷新数据的能力透传给子路由组件,无法在异常场景触发重加载
修复方案
分三步修改即可:
1. 完善Redux Reducer的状态字段
在你的data对应的reducer中新增error字段,用来标记加载失败状态:
// 你的data reducer初始状态 const initialState = { isLoading: false, data: null, // 初始设为null不要设为空对象 error: null // 新增错误状态 } // 处理requestData对应的三个action: // 请求发起时 case 'DATA_REQUEST_PENDING': return { ...state, isLoading: true, error: null } // 请求成功时 case 'DATA_REQUEST_FULFILLED': return { ...state, isLoading: false, data: payload.data } // 请求失败时 case 'DATA_REQUEST_REJECTED': return { ...state, isLoading: false, error: payload.error }
2. 改造App组件,透传刷新能力
通过Context把刷新方法共享给所有子路由组件,不用逐层传props:
// App.js新增代码 import { createContext, useContext } from 'react' // 新建刷新上下文 export const RefreshContext = createContext() function App() { const dispatch = useDispatch(); const DataReducer = useSelector((state) => state.data); const dataLoadingStatus = DataReducer.isLoading; const [refresh, setRefresh] = useState(0); const triggerRefresh = () => { setRefresh(prev => prev + 1) } useEffect(() => { dispatch(requestData()); }, [refresh, dispatch]); return ( <RefreshContext.Provider value={triggerRefresh}> <div id="App"> <LoadingOverlay active={dataLoadingStatus} spinner text='Loading your content...' > <div className="ui inverted segment"> <Header as='h1' textAlign='center'> <Header.Content> Data Dashboard </Header.Content> </Header> </div> <BurgerSidebar /> </LoadingOverlay> </div> </RefreshContext.Provider> ); } // 导出方便调用的hook export const useRefresh = () => useContext(RefreshContext)
3. 改造Dashboard组件,加空值校验和刷新按钮
所有用到Redux异步数据的子组件都要加类似的校验逻辑:
import {React} from 'react' import { Header, Container, Segment, Icon, Divider, Card, Grid, Statistic, Button, Message } from 'semantic-ui-react' import { useSelector } from "react-redux"; import { useRefresh } from '../App' // 导入刚才的刷新hook const Dashboard = () => { const DataReducer = useSelector((state) => state.data); const triggerRefresh = useRefresh() // 前置状态判断:加载中(App全局遮罩已覆盖的话可以省略该行,作为兜底保留) if (DataReducer.isLoading) return null // 加载失败场景 if (DataReducer.error) { return ( <main id="page-wrap"> <Container text> <Message negative> <Message.Header>数据加载失败</Message.Header> <p>请点击下方按钮重试</p> <Button primary onClick={triggerRefresh} icon='refresh' content='刷新重试' /> </Message> </Container> </main> ) } // 数据为空场景 const customData = DataReducer.data; if (!customData || !customData.users) { return ( <main id="page-wrap"> <Container text> <Message warning> <Message.Header>暂无有效数据</Message.Header> <p>请点击下方按钮重试</p> <Button primary onClick={triggerRefresh} icon='refresh' content='刷新重试' /> </Message> </Container> </main> ) } // 数据正常才执行后续逻辑 const totalusers = customData.users.length; return( <main id="page-wrap"> <Container text> <Header as='h2'>Dashboard</Header> <Segment> <Divider horizontal> <Header as='h4'> <Icon name='chart bar' /> Statistics </Header> </Divider> <Grid columns={1} relaxed='very'> <Grid.Column> <Card fluid> <Card.Content> <Card.Header>Total Users</Card.Header> <Statistic> <Statistic.Value>{totalusers}</Statistic.Value> <Statistic.Label>Users of the system</Statistic.Label> </Statistic> </Card.Content> </Card> </Grid.Column> </Grid> </Segment> </Container> </main> ); } export default Dashboard
其他优化建议
- 所有用到Redux中异步加载数据的子组件,都要按照上面的逻辑加前置状态判断,不要直接读取深层属性
- 可以用JS可选链运算符简化空值判断,比如
const totalusers = customData?.users?.length || 0,避免空值报错
内容的提问来源于stack exchange,提问作者user3303852
相关产品推荐
相关产品推荐

