React跨组件调用函数报错:调用Panel的activePanel提示组件未挂载
我在Products组件里尝试调用Panel组件的activePanel方法来激活面板,结果触发了以下警告:
Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Panel component.
我试过实例化新的Panel对象来调用这个方法,但还是出现同样的错误。相关代码如下:
Panel组件代码
import React from "react"; // import {render} from "react-dom"; import AddInventory from "components/AddInventory"; class Panel extends React.Component{ constructor(props) { super(props); this.state = { activeIndex: '' } } componentDidMount() { this.activePanel(); } closePanel=()=>{ this.setState({ activeIndex : false }) } activePanel = ()=>{ this.setState({ activeIndex : true }) } render(){ return( <div> {/*<button className={"button is-primary add-btn"} onClick={this.activePanel}>add</button>*/} <div className={this.state.activeIndex ? 'panel-wrapper active':'panel-wrapper'}> <div className={"over-layer"}> <div className={"panel"}> <div className={"head"}> <span onClick={this.closePanel} className={"close"}>x</span> <AddInventory></AddInventory> </div> </div> </div> </div> </div> ) } } export default Panel;
Products组件代码
import React from "react"; import ToolBox from "components/ToolBox"; import Product from "components/Product"; import axios from 'components/axios' import {CSSTransition , TransitionGroup} from 'react-transition-group' import Panel from "components/Panel"; class Products extends React.Component{ product =[]; source =[]; state ={ product : [{ id:'1', name:'Air Jordan1', tags:'45 colours', image:'images/1.jpg', price:'21000', status:'available' }, { id:'2', name:'Nike Pual George PG 3', tags:'45 colours', image:'images/2.jpg', price:'11000', status:'available' }, { id:'3', name:'Jordan Why Not Zer0.2', tags:'10 colours', image:'images/3.jpg', price:'15000', status:'unavailable' }, ] } componentDidMount() { // fetch('http://localhost:3003/products').then(response => response.json()).then( data=>{ // console.log(data) // this.setState({ // product : data // }) // }) axios.get('/products').then(response => { this.setState( { product : response.data, source : response.data }) }) } search = text=>{ //1.get a new array from product let _product = [...this.state.source] //2.filter the array let res = _product.filter((element)=>{ return element.name.toLowerCase().includes(text.toLowerCase()) }) //set state this.setState({ product : res }) } add = ()=>{ let panel = new Panel(this.props) panel.activePanel() } // add =()=>{ // panel.setState({ // activeIndex : true // }) // } render() { return( <div> <ToolBox search={this.search}/> <div className={'products'}> <div className="columns is-multiline is-desktop"> <TransitionGroup component={null}> { this.state.product.map(p=>{ return ( <CSSTransition timeout={400} classNames="product-fade" key={p.id} > <div className="column is-3" key={p.id}> <Product product={p}/> </div> </CSSTransition> ) }) }</TransitionGroup> </div> <button className={"button is-primary add-btn"} onClick={this.add}></button> </div> </div> ) } } export default Products;
错误根源
你手动new Panel(this.props)创建的组件实例根本没有被React挂载到DOM中,React组件的生命周期由React统一管理,手动实例化的组件不会经过挂载流程,调用它的setState自然会触发未挂载警告。而且这种方式本身就不符合React的单向数据流模式——父组件应该通过props传递状态控制子组件,而非直接调用子组件方法。
正确实现:状态提升+受控组件
将面板的激活状态提升到父组件Products中,通过props传递给Panel,同时传递关闭面板的回调方法,让Panel成为受控组件:
修改后的Panel组件
import React from "react"; import AddInventory from "components/AddInventory"; class Panel extends React.Component{ render(){ const { isActive, onClose } = this.props; return( <div> <div className={isActive ? 'panel-wrapper active':'panel-wrapper'}> <div className="over-layer"> <div className="panel"> <div className="head"> <span onClick={onClose} className="close">x</span> <AddInventory></AddInventory> </div> </div> </div> </div> </div> ) } } export default Panel;
修改后的Products组件
import React from "react"; import ToolBox from "components/ToolBox"; import Product from "components/Product"; import axios from 'components/axios' import {CSSTransition , TransitionGroup} from 'react-transition-group' import Panel from "components/Panel"; class Products extends React.Component{ state ={ product : [{ id:'1', name:'Air Jordan1', tags:'45 colours', image:'images/1.jpg', price:'21000', status:'available' }, { id:'2', name:'Nike Pual George PG 3', tags:'45 colours', image:'images/2.jpg', price:'11000', status:'available' }, { id:'3', name:'Jordan Why Not Zer0.2', tags:'10 colours', image:'images/3.jpg', price:'15000', status:'unavailable' }, ], source : [], isPanelActive: false // 添加控制面板显示的状态 } componentDidMount() { axios.get('/products').then(response => { this.setState( { product : response.data, source : response.data }) }) } search = text=>{ let _product = [...this.state.source] let res = _product.filter((element)=>{ return element.name.toLowerCase().includes(text.toLowerCase()) }) this.setState({ product : res }) } openPanel = ()=>{ this.setState({ isPanelActive: true }) } closePanel = ()=>{ this.setState({ isPanelActive: false }) } render() { return( <div> <ToolBox search={this.search}/> <div className={'products'}> <div className="columns is-multiline is-desktop"> <TransitionGroup component={null}> { this.state.product.map(p=>{ return ( <CSSTransition timeout={400} classNames="product-fade" key={p.id} > <div className="column is-3" key={p.id}> <Product product={p}/> </div> </CSSTransition> ) }) }</TransitionGroup> </div> <button className="button is-primary add-btn" onClick={this.openPanel}></button> {/* 渲染Panel并传递状态和回调 */} <Panel isActive={this.state.isPanelActive} onClose={this.closePanel} /> </div> </div> ) } } export default Products;
备选方案:使用ref(不推荐)
如果必须保留Panel内部状态,可以通过ref获取已挂载的组件实例来调用方法,但这种方式会破坏单向数据流,仅在特殊场景使用:
- 在Products中创建ref:
panelRef = React.createRef() - 渲染Panel时绑定ref:
<Panel ref={this.panelRef} /> - 打开面板时调用:
this.panelRef.current.activePanel()
内容的提问来源于stack exchange,提问作者Na Jacky

