express-react-views下setState与componentDidMount更新异常问题
核心问题
你遇到的问题是两个层面的错误叠加导致的:
- 渲染引擎机制问题:你用的
express-react-views是纯静态服务端渲染引擎,服务端渲染阶段只会执行组件构造函数、render方法,完全不会触发componentDidMount这类仅客户端运行的生命周期钩子,服务端生成完静态HTML就直接返回给浏览器了,根本不会等待你写在生命周期里的异步请求完成,也不会在状态更新后重新执行render。 - 代码逻辑错误:就算放到纯客户端React环境下,你现有代码也无法正常渲染,存在以下硬伤:
- 遍历数据时错把类方法
this.getData(本身是函数)当成了数据源,Array.isArray(this.getData)永远返回false,自然不会渲染列表内容,你存到this.state.FinalData里的数据从来没被render读取过。 setState的第二个参数要求传回调函数引用,你写的是立即执行的自执行异步函数,等于回调逻辑在setState调用时就立刻跑了,只是闭包拿到了接口返回值所以打印看起来正常,实际上和setState是否完成更新没有关系。- render函数里绝对不能直接调用会触发
setState的异步方法,你在render里调用this.getData()会触发无限重渲染循环,属于React基础写法错误。 componentDidMount里第一次调用getData时传入的FinalData变量根本没有定义,会直接抛出引用错误;且重复调用了两次getData发起重复请求。- 接口返回的列表数据存在返回值的
a字段下,你没有读取对应字段,初始状态FinalData设为空对象也不符合列表遍历的预期。
- 遍历数据时错把类方法
修复方案
根据你的渲染场景二选一即可:
方案1:继续使用express-react-views做纯静态SSR
这种场景下不要在组件内部写任何异步拉数逻辑,所有数据必须在路由层提前查询完成,再通过props传入组件:
- 路由层修改:
// 对应路由文件 const express = require('express'); const router = express.Router(); const getPipeLine = require('../../service/Contructor.js'); router.get('/active-loans', async (req, res, next) => { try { const display = new getPipeLine(); const finalData = await display; // 提前拿完所有数据,作为props传给待渲染组件 res.render('ActiveLoans', { FinalData: finalData }); } catch (err) { next(err) } }); module.exports = router;
- 组件层简化,去掉所有无效的生命周期、拉数方法,直接从props读数据渲染:
const React = require('react'); const Head = require('../components/Head.jsx'); const Nav = require('../components/Nav.jsx'); const Footer = require('../components/Footer.jsx'); class ActiveLoans extends React.Component { render() { // 从props取路由传入的数据,拿a字段下的贷款列表,做兜底处理 const loanList = this.props.FinalData?.a || []; return ( <React.Fragment> <Head /> <Nav /> {loanList.map((item) => ( <div key={item.LoanNumber}> <table> <thead> <tr> <th>Loan Number</th> <th>Loan Status</th> <th>Loan Type</th> <th>Submit Date</th> <th>Last Review</th> </tr> </thead> <tbody> <tr> <td>{item.LoanNumber}</td> <td>{item.LoanStatus}</td> <td>{item.LoanType}</td> <td>{item.SubmitDate}</td> <td>{item.LastReview}</td> </tr> </tbody> </table> </div> ))} <Footer /> </React.Fragment> ) } } module.exports = ActiveLoans;
方案2:需要客户端动态拉数/交互
这种场景不要用express-react-views渲染当前页面,改成前后端分离模式:客户端加载React打包后的JS bundle,由客户端完成渲染、拉数逻辑,此时componentDidMount才会正常触发。客户端组件正确写法参考:
const React = require('react'); const Head = require('../components/Head.jsx'); const Nav = require('../components/Nav.jsx'); const Footer = require('../components/Footer.jsx'); const getPipeLine = require('../../service/Contructor.js'); class ActiveLoans extends React.Component { constructor(props) { super(props) this.state = { loanList: [], loading: true }; } async componentDidMount() { try { const display = new getPipeLine(); const resData = await display; this.setState({ loanList: resData.a || [], loading: false }, () => { console.log('状态更新完成', this.state.loanList) }) } catch (err) { console.log(err.message) this.setState({loading: false}) } } render() { const { loanList, loading } = this.state; if (loading) return <div>数据加载中...</div> return ( <React.Fragment> <Head /> <Nav /> {loanList.map((item) => ( <div key={item.LoanNumber}> <table> <thead> <tr> <th>Loan Number</th> <th>Loan Status</th> <th>Loan Type</th> <th>Submit Date</th> <th>Last Review</th> </tr> </thead> <tbody> <tr> <td>{item.LoanNumber}</td> <td>{item.LoanStatus}</td> <td>{item.LoanType}</td> <td>{item.SubmitDate}</td> <td>{item.LastReview}</td> </tr> </tbody> </table> </div> ))} <Footer /> </React.Fragment> ) } } module.exports = ActiveLoans;
注意:你给出的目标数据示例本身结构不完整,
a字段对应的对象缺少闭合括号,实际使用时注意确认接口返回的最终结构。
内容的提问来源于stack exchange,提问作者user123
相关产品推荐
相关产品推荐

