React父组件状态改变后,子组件未更新

158

我正在尝试创建一个良好的ApiWrapper组件,以填充各种子组件中的数据。根据我所阅读的所有内容,这应该可以实现:https://jsfiddle.net/vinniejames/m1mesp6z/1/

class ApiWrapper extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      response: {
        "title": 'nothing fetched yet'
      }
    };
  }

  componentDidMount() {
    this._makeApiCall(this.props.endpoint);
  }

  _makeApiCall(endpoint) {
    fetch(endpoint).then(function(response) {
      this.setState({
        response: response
      });
    }.bind(this))
  }

  render() {
    return <Child data = {
      this.state.response
    }
    />;
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: props.data
    };
  }

  render() {
    console.log(this.state.data, 'new data');
    return ( < span > {
      this.state.data.title
    } < /span>);
  };
}

var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;

ReactDOM.render(
  element,
  document.getElementById('container')
);

但因某种原因,当父组件状态改变时,子组件似乎没有更新。

我有什么遗漏吗?

4个回答

257

你的代码有两个问题。

你的子组件初始状态是从props中设置的。

this.state = {
  data: props.data
};
引用自这个SO答案

将初始状态作为prop传递给组件是一种反模式,因为getInitialState方法(在我们的情况下是构造函数)仅在第一次渲染组件时调用,从不再次调用。这意味着,如果您重新渲染该组件并传递一个prop不同值,则该组件不会做出相应的反应,因为该组件将保留其第一次渲染的状态。这非常容易出错。

因此,如果无法避免这种情况,理想的解决方案是使用方法componentWillReceiveProps来监听新的prop

将下面的代码添加到您的子组件中将解决子组件重新渲染的问题。

componentWillReceiveProps(nextProps) {
  this.setState({ data: nextProps.data });  
}
第二个问题与fetch有关。
_makeApiCall(endpoint) {
  fetch(endpoint)
    .then((response) => response.json())   // ----> you missed this part
    .then((response) => this.setState({ response }));
}

这里有一个可工作的示例: https://jsfiddle.net/o8b04mLy/


16
据我所知,没有其他缺点。但在你的情况下,我们可以明确避免在子组件内部拥有状态。父组件可以将数据作为属性传递,当父组件使用其新状态重新渲染时,子组件也会进行重新渲染(带有新属性)。实际上,在这里保持子组件中的状态是不必要的。纯组件无敌! - yadhu
12
从现在开始阅读的任何人,请查看 static getDerivedStateFromProps(nextProps, prevState) https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops - GoatsWearHats
4
除了 componentWillReceiveProps(),还有其他解决方案吗?因为它现在已经被弃用了。 - LearningMath
4
请查看最新的React文档(https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops),其中提到了替代方法。您可能需要重新考虑您的逻辑。 - yadhu
2
只是想留下一条评论,提醒大家 componentWillReceiveProps 将在 17 版本中被删除。他们建议使用 getDerivedStateFromProps 作为替代方案。希望能帮到大家! - Diogo Santo
显示剩余8条评论

11
如果以上解决方案仍未解决您的问题,我建议您检查一下如何改变状态。如果您没有返回一个新对象,React有时会认为新的先前状态和更改后的状态没有区别。在改变状态时始终传递一个新对象是一个好习惯,因为看到新对象,React肯定会重新渲染所有需要访问该更改状态的组件。
例如:在这里,我将更改状态中对象数组的一个属性,注意如何将所有数据传递给新对象。此外,下面的代码可能看起来有点陌生,它是Redux reducer函数,但不用担心,它只是一种改变状态的方法。
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
        return cartItems.map(item => {
            if(item.id===existingItem.id){
                ++item.quantity;        
            }
            // I can simply return item but instead I spread the item and return a new object
            return {...item} 
        })
    } 

请确保你用一个新的对象来改变状态,即使你只是对状态做了微小的更改,也要将其展开到一个新对象中并返回,这将在所有适当的位置触发渲染。 希望这可以帮到您。如果我有错误,请告诉我 :)


2

你需要改变一些东西。

fetch 得到响应时,它不是一个 json。我正在寻找如何获取这个 json,并发现了这个 链接

另一方面,你需要考虑到 constructor 函数只被调用一次。

所以,你需要改变在 <Child> 组件中检索数据的方式。

这里,我留下了一个示例代码:https://jsfiddle.net/emq1ztqj/

希望对你有所帮助。


谢谢您。虽然看了您给出的例子,但似乎 Child 从未更新过。对于如何改变 Child 接收数据的方式,您有什么建议吗? - Vinnie James
2
你确定吗?我看到 <Child> 组件从 https://jsonplaceholder.typicode.com/posts/1 检索新数据并重新渲染。 - saeta
1
是的,我现在在OSX上看到它工作了。iOS没有在StackExchange应用程序浏览器中触发重新渲染。 - Vinnie James

1

被接受的答案和componentWillReceiveProps

被接受的答案中的componentWillReceiveProps已过时,将在React 17中从React中删除。 React文档:UNSAFE_componentWillReceiveProps()

在React中使用派生状态逻辑

正如React文档指出的那样,使用派生状态(意思是:组件反映了发生在其props中的更改)可能会使您的组件更难以考虑,并且可能是一种反模式。 React文档:您可能不需要派生状态

当前解决方案:getDerivedStateFromProps

如果您选择使用派生状态,则当前解决方案是使用getDerivedStateFromProps方法,就像@DiogoSanto所说的那样。

getDerivedStateFromProps在调用render方法之前被调用,既在初始安装上,也在后续更新上。它应该返回一个对象来更新状态,或者返回null来不更新任何内容。 React文档:static getDerivedStateFromProps()

如何使用componentWillReceiveProps

此方法无法访问实例属性。它所做的就是描述React如何从给定的props计算新状态。每当更改props时,React都会调用此方法,并将此方法返回的对象用作新状态。

class Child extends React.Component {
    constructor() {
        super(props);
        // nothing changed, assign the state for the 
        // first time to teach its initial shape. 
        // (it will work without this, but will throw 
        // a warning)
        this.state = {
            data: props.data
        };
    }

    componentWillReceiveProps(props) {
        // return the new state as object, do not call .setState()
        return { 
            data: props.data
        };
    }

    render() {
        // nothing changed, will be called after 
        // componentWillReceiveProps returned the new state, 
        // each time props are updated.
        return ( 
            <span>{this.state.data.title}</span>
        );
    }
}

注意

  • 根据父组件的变化重新渲染组件可能会让用户感到困扰,因为这将导致用户在该组件上输入的内容丢失。
  • 派生状态逻辑会使组件更难理解和思考。请明智地使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接