React组件生命周期:筛选后AJAX数据获取的合适方法选择
问题描述
我是ReactJS新手,正在为搜索结果构建筛选系统。当用户选择筛选条件时,我希望根据所选筛选器通过AJAX调用获取新数据。我在筛选器的复选框上设置了事件处理函数,用于更新组件的state,这会触发React重新渲染组件,但此时并没有最新的数据。componentWillUpdate()看似适用于此场景,但它将在React 17版本中被弃用(除非使用UNSAFE_componentWillUpdate())。请问如何在组件重新渲染前获取最新数据?应该选择哪个生命周期方法?
场景示例:用户选择年份2015时,我通过this.setState将date[2]:2015添加到appliedFiltres中,之后希望使用该筛选条件获取数据,最终用新值重新渲染组件。
相关代码
index.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Reactjs - Filtres component</title> </head> <body> <div id="filtres" class="filtres" style="width:250px;"></div> <script type="text/javascript"> var filtres = [{ "date": { "libelle": "Year", "buckets": { "date2018": { "name": "date[0]", "value": "2018", "title": 2018, "libelle": 2018, "nb": 1 }, "date2016": { "name": "date[1]", "value": "2016", "title": 2016, "libelle": 2016, "nb": 54 }, "date2015": { "name": "date[2]", "value": "2015", "title": 2015, "libelle": 2015, "nb": 70 } } } }, { /* some filters */ }]; </script> <script src="dist/filtresComponent.js"></script> </body> </html>
FiltresComponent.js
import React from 'react' import ReactDOM from 'react-dom' import Filtres from './Filtres' ReactDOM.render(<Filtres filtres={filtres} />, document.getElementById('filtres'));
Filtres.js
import React, { Component } from 'react' import './Filtres.css' import Crit from './Crit' class Filtres extends Component { constructor(props) { super(props); this.state = { filtres : this.props.filtres, appliedFiltres : {} } } addFiltre = (filtreName, filtreValue) => { console.log('addFiltre from FiltresComponent !!'); console.log('We add : '); console.log('filtreName :' + filtreName + ' ,value : ' + filtreValue); this.setState((state) => { return {appliedFiltres: Object.assign(state.appliedFiltres, {[filtreName]: filtreValue})} }); console.log(this.state); } // before re-rendering sounds good but will be deprecated on reactjs 17 componentWillUpdate = () => { console.log('componentWillUpdate'); // Fetching before rendering ? } render() { console.log('render Filtres.js'); return ([ this.state.filtres.map((crit, index) => { let libelle = crit[Object.keys(crit)].libelle; let critValues = Object.values(crit[Object.keys(crit)].buckets); return <Crit key={index} libelle={libelle} critValues={critValues} addFiltre={this.addFiltre}/> }) ]) } } export default Filtres
Crit.js
import React, { Component } from 'react' class Crit extends Component { static defaultProps = { open : true } constructor(props) { super(props); this.state = { open : this.props.open } } showHideCrit = (e) => { if(this.state.open){ e.target.nextElementSibling.style.display = 'none'; this.setState({ open: false }); }else{ e.target.nextElementSibling.style.display = 'flex'; this.setState({ open: true }); } } addFiltre = (e) => { console.log('addFiltre from CritComponent !!'); // User chosen filter let filtreName = e.target.name; let filtreValue = e.target.value; // LiftUp this.props.addFiltre(filtreName, filtreValue); } render() { console.log('render Crit.js'); return ([ <div className="crit"> <a className={"js-head_crit " + (this.state.open ? 'open' : '' )+""} onClick={this.showHideCrit}>{this.props.libelle}</a> <div className="crit-values"> { this.props.critValues.map((critValue,index) => { return ( <div key={index} className="crit-value" data-count={critValue.nb}> <input type="checkbox" name={critValue.name} value={critValue.value} onChange={this.addFiltre}/> <label className="crit-libelle"> {critValue.libelle} <span className="crit-nb">{critValue.nb}</span> </label> </div> ) }) } </div> </div> ]) } } export default Crit
解决方案
你说得对,componentWillUpdate确实会被弃用,React官方推荐了两种更安全的方式来处理这种场景,下面给你详细说明:
方案1:使用componentDidUpdate生命周期方法
这个方法会在组件更新完成后立即触发,此时你可以拿到最新的state和props,非常适合用来发起AJAX请求。但要注意必须加条件判断,否则会因为更新state再次触发componentDidUpdate,导致无限循环。
修改你的Filtres.js,替换掉componentWillUpdate:
componentDidUpdate(prevProps, prevState) { // 只有当appliedFiltres发生变化时才发起请求 if (JSON.stringify(prevState.appliedFiltres) !== JSON.stringify(this.state.appliedFiltres)) { console.log('使用最新筛选条件发起请求:', this.state.appliedFiltres); // 这里写你的AJAX请求逻辑,比如用fetch或者axios fetch('/your-api-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.state.appliedFiltres) }) .then(res => res.json()) .then(newData => { // 获取到新数据后更新state,触发组件重新渲染 this.setState({ filtres: newData }); }) .catch(err => console.error('请求失败:', err)); } }
注:这里用
JSON.stringify比较对象是因为对象是引用类型,直接用!==判断的是引用是否相同,不是内容。如果你的筛选条件结构复杂,也可以用lodash的isEqual方法来做深度比较。
方案2:利用setState的回调函数
setState方法接受第二个参数,是一个回调函数,它会在state更新完成、组件即将重新渲染时执行。在这里你可以直接拿到最新的state值,不需要依赖生命周期方法。
修改你的addFiltre方法:
addFiltre = (filtreName, filtreValue) => { console.log('addFiltre from FiltresComponent !!'); console.log('We add : '); console.log('filtreName :' + filtreName + ' ,value : ' + filtreValue); this.setState((state) => { // 注意:不要直接修改原state对象,用展开运算符创建新对象,符合React immutable原则 return {appliedFiltres: {...state.appliedFiltres, [filtreName]: filtreValue}} }, () => { // setState完成后的回调,这里能拿到最新的state console.log('最新筛选条件:', this.state.appliedFiltres); // 发起AJAX请求 fetch('/your-api-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.state.appliedFiltres) }) .then(res => res.json()) .then(newData => { this.setState({ filtres: newData }); }) .catch(err => console.error('请求失败:', err)); }); }
注:之前你用
Object.assign(state.appliedFiltres, ...)会直接修改原state对象,这违反了React的不可变数据原则,容易导致难以排查的bug,所以换成展开运算符{...state.appliedFiltres, ...}创建新对象更安全。
额外建议
- 处理取消筛选:当前你的代码只处理了添加筛选条件的情况,当用户取消勾选复选框时,需要从
appliedFiltres中移除对应的项,否则筛选条件会一直累加。 - 加载状态:发起AJAX请求时,可以添加一个
loading状态,在界面上显示加载中提示,提升用户体验。 - 防抖处理:如果用户频繁点击筛选条件,可以加个防抖函数,避免短时间内发起过多请求。
内容的提问来源于stack exchange,提问作者akio

