React Redux activePage切换报'type' of undefined错误
核心报错根因
你遇到的TypeError: Cannot read property 'type' of undefined,触发逻辑是:reducer执行时拿到的action参数值为undefined,读取action.type时自然触发属性访问异常。这个问题不是单个点的bug,是你使用React-Redux的connect、reducer编写规则时把几个核心API的用法搞反了,叠加缺失reducer的兜底逻辑共同导致的。
逐文件定位的问题与修正
1. loginButton.js 核心API用反
原代码的问题:
- 你把
mapDispatchToProps的入参写成了state,实际上mapDispatchToProps的入参是dispatch方法,返回值里的方法要用来触发状态更新;现在的写法返回的changePage直接取了state.activePage,本质是个字符串值,不是action创建函数。 - 按钮点击时调用
changePage('loginPage'),本质是对字符串做函数调用,执行后返回undefined,这个undefined会被当成action传给dispatch,最后流入reducer时直接触发访问action.type的报错。
修正方式:
直接用React-Redux提供的简写connect写法,把action创建函数作为connect的第二个参数传入,框架会自动帮你把每个action绑定dispatch方法,不需要自己手动写mapDispatch逻辑。
2. reducer/index.js 缺失强制兜底逻辑
原代码的问题:
- switch语句只匹配了
CHANGE_PAGE这一个case,没有设置default分支返回原state。Redux在store初始化时会自动dispatch一个带初始化type的内置action,所有不匹配当前case的action(包括初始化时的内置action、前面传进来的undefined非法action)都会走到函数结尾,返回undefined,直接把整个store的初始state覆盖为空值,从根源上触发后续的属性访问报错。 - 文件里冗余导入了
changePageaction函数,reducer本身不需要调用action创建函数,只需要根据传入的action对象的type、payload更新state即可,这个导入完全多余。
修正方式: - 在switch的最后加default分支,把当前的state原封不动返回,这是Redux对reducer的强制要求。
- 删掉冗余的action导入。
3. App.js 状态取值逻辑错误
原代码的问题:
- 页面渲染判断时读取的是
this.props.changePage,但changePage是用来触发状态更新的action函数,根本不是页面状态值;哪怕你把connect的逻辑接对了,用函数和页面名字符串做相等判断永远不会命中,自然不会正确切换页面。
修正方式: - 通过connect把store里存储的
activePage状态值映射到App组件的props上,用这个值判断要渲染的页面。
4. index.js 组件嵌套结构混乱
原代码的问题:
- App组件被放在BrowserRouter的路由出口外面,嵌套层级不对,导致组件拿不到路由上下文;整体Provider、Router、组件的嵌套顺序有问题。
修正方式: - 调整嵌套顺序:最外层是Provider(注入store),里面包BrowserRouter(注入路由能力),最内层放App组件,保证所有子组件能拿到对应的context。
修正后的可运行代码
1. constants/action-types.js(原代码逻辑正确,无需修改)
export const CHANGE_PAGE = "CHANGE_PAGE";
2. actions/actions.js(原代码逻辑正确,无需修改)
import { CHANGE_PAGE } from "../constants/action-types"; export const changePage = (activePage) => ({ type: CHANGE_PAGE, payload: { activePage, }, });
3. redux/reducer/index.js(修正后)
import { CHANGE_PAGE } from "../constants/action-types"; const initState = { activePage: 'HomePage', } function allReducers(state = initState, action){ switch(action.type){ case CHANGE_PAGE: return { ...state, activePage: action.payload.activePage, }; // 增加兜底返回逻辑 default: return state; } } export default allReducers;
4. components/LoginButton.js(修正后)
import { connect } from 'react-redux'; import { changePage } from '../redux/actions/actions'; function LoginButtonThing (props) { return( <div> <button onClick={() => props.changePage('LoginPage')}>Login Page</button> </div> ) } // 第一个参数传null表示不需要读取store状态,第二个参数直接传入action对象 // react-redux会自动把action函数绑定到dispatch上 export default connect(null, { changePage })(LoginButtonThing);
5. App.js(修正后)
import { connect } from 'react-redux'; import LoginPage from './loginPage'; import HomePage from './homePage'; import LoginButton from './components/LoginButton'; class App extends React.Component { render() { let renderedPage; // 从props读取映射过来的activePage状态值做判断 if (this.props.activePage === "LoginPage") { renderedPage = <LoginPage/>; } else if (this.props.activePage === "HomePage") { renderedPage = <HomePage/>; } else { renderedPage = <HomePage/>; } return ( <div id="App"> <LoginButton /> {renderedPage} </div> ); } } // 把store里的activePage状态映射到组件props const mapStateToProps = (state) => { return { activePage: state.activePage, } } export default connect(mapStateToProps)(App);
6. index.js(修正后)
import React from 'react'; import ReactDOM from 'react-dom'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import allReducers from "./redux/reducer"; import App from './App'; const store = createStore( allReducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); ReactDOM.render( <Provider store={store}> <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> </Provider>, document.getElementById('root') );
核心使用规则提醒
- 记牢connect两个参数的边界:第一个参数是
mapStateToProps,负责把store里的状态映射到组件props,入参是state;第二个参数是mapDispatchToProps,负责把dispatch方法映射到组件props,入参是dispatch,两个的入参、返回值完全不能搞混。 - 写reducer必须遵守纯函数规则:不能修改原state,不匹配action的时候必须返回原state,绝对不能返回undefined,否则整个store会变成空值。
- 只要用connect把action对象传进去,react-redux会自动生成包装好的、绑定了dispatch的action触发函数,不需要手动写dispatch调用。
内容的提问来源于stack exchange,提问作者ArthurS
相关产品推荐
相关产品推荐

