React调用this.props.history.push('/search')时出现hasChildNodes为空报错
我来帮你排查这个问题,这个报错在React 16.x + React Router 4.x的组合里挺常见的,结合你的代码和环境来看,大概率和组件卸载后仍执行异步操作有关,下面是具体的分析和解决方案:
可能的原因
当你发起fetch请求后,如果用户在请求完成前切换了路由(比如点击其他导航),当前组件会被卸载。但请求响应回来后,.then回调里的this.props.history.push仍然会执行——此时组件已经不在DOM树中,React处理路由跳转时尝试访问已不存在的DOM节点,就会抛出Cannot read property 'hasChildNodes' of null的错误。
另外,React 16.2.0版本对异步操作完成后组件已卸载的场景处理不够完善,这类DOM相关的错误更容易触发。
具体解决方案
1. 给组件添加挂载状态标记,阻止卸载后的异步操作
在组件中新增一个实例变量,标记组件是否处于挂载状态,在异步回调执行前先检查这个标记:
class YourComponent extends React.Component { // 初始化挂载状态为false isMounted = false; componentDidMount() { // 组件挂载后标记为true this.isMounted = true; } componentWillUnmount() { // 组件卸载时标记为false this.isMounted = false; } handleSubmit(event) { event.preventDefault(); fetch(SEARCH_API_ENDPOINT + this.state.searchTerm, { method: 'get', headers: headers }) .then(response => { if (response.ok) { return response.json(); } else { throw new Error('Something went wrong ...'); } }) .then(data => { console.log(data.docs); // 先检查组件是否还挂载,再执行跳转 if (this.isMounted) { this.props.history.push('/search'); } }) .catch(error => { // 同样,只有组件挂载时才执行状态更新和提示 if (this.isMounted) { notify.show('Something is wrong with something!','error'); this.setState({ error: error, isLoading: false }) } }); } // 其他组件代码... }
2. 确保组件能正确获取history对象
虽然你已经能访问this.props.history,但如果组件不是直接被<Route>渲染(比如嵌套在其他组件里),可能会出现路由上下文丢失的情况。可以用withRouter高阶组件包裹你的组件,确保history等路由props能正确传递:
import { withRouter } from 'react-router-dom'; // 你的组件代码... export default withRouter(YourComponent);
3. 检查自定义PrivateRoute的实现
你用到了自定义的<PrivateRoute>,如果它的实现有缺陷(比如没有正确传递路由props),也可能导致这个错误。确保你的PrivateRoute正确把所有props传递给子组件:
const PrivateRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={props => // 这里要把路由相关的props全部传递给Component isAuthenticated() ? <Component {...props} /> : <Redirect to="/login" /> } /> );
4. 用Redirect组件替代history.push(备选方案)
如果上面的方法都无效,可以尝试通过状态控制渲染<Redirect>组件来实现跳转,避免直接调用history.push:
// 先在state里添加跳转标记 constructor(props) { super(props); this.state = { // 其他状态... redirectToSearch: false }; } handleSubmit(event) { event.preventDefault(); fetch(SEARCH_API_ENDPOINT + this.state.searchTerm, { method: 'get', headers: headers }) .then(response => { if (response.ok) { return response.json(); } else { throw new Error('Something went wrong ...'); } }) .then(data => { console.log(data.docs); // 更新状态标记需要跳转 this.setState({ redirectToSearch: true }); }) .catch(error => { notify.show('Something is wrong with something!','error'); this.setState({ error: error, isLoading: false }) }); } render() { // 如果需要跳转,直接渲染Redirect组件 if (this.state.redirectToSearch) { return <Redirect to="/search" />; } // 原来的组件渲染内容 return ( // ...你的表单和其他元素 ); }
内容的提问来源于stack exchange,提问作者nasaa

