即使在调用 setState 之后,React.js 子状态也不会重新渲染?

Posted

技术标签:

【中文标题】即使在调用 setState 之后,React.js 子状态也不会重新渲染?【英文标题】:React.js child state not re-rendering even after calling setState? 【发布时间】:2019-08-09 03:12:23 【问题描述】:

我有一个包含一个子组件的应用程序,我想在 setState 更新父状态下的 bookInput 时重新渲染它。我正在使用 axios 从谷歌的 book api 请求信息。出于某种原因,即使状态正在更新,孩子也不会重新渲染。如果可以的话请帮忙!谢谢!

import React,  Component  from 'react';
import axios from 'axios';

class App extends Component 
  constructor(props) 
    super(props);

    this.state = 
      bookInput: 'ender',
      bookSubmitted: 'initial'
    

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleSubmitEmpty = this.handleSubmitEmpty.bind(this);
  

    handleChange(e) 
    this.setState(bookInput: e.target.value);
    console.log(this.state.bookInput);
    //this.setState(bookSubmitted: false);
  

  handleSubmit(e) 
    e.preventDefault();
    //this.setState(bookSubmitted: true)
    const name = this.state.bookInput;
    this.setState(bookInput: name);
    console.log(this.state);
    this.setState(bookSubmitted: 'userSub');
  

  handleSubmitEmpty(e) 
    alert('please enter an item to search for');
    e.preventDefault();
  

  render() 

    return (

      <div className="App">
        <header className = "App-header">
          <h1>Book Search App</h1>
        </header>

        <form className = "form-style" onSubmit = this.state.bookInput ? this.handleSubmit: this.handleSubmitEmpty>
          <label>
            <input type="text" className = "input-style" 
              value = this.state.bookInput onChange = this.handleChange>
            </input>
          </label>
          <button type="submit">search books</button>
        </form>
        /* <Book bookInput = this.state.bookInput/> */
        /*this.state.bookSubmitted && <Book bookInput = this.state.bookInput/>*/
        
          (this.state.bookSubmitted === 'initial' || this.state.bookSubmitted === 'userSub') && 
          <Book bookInput = this.state.bookInput/>
         
      </div>
    );
  


export default App;


class Book extends Component 
  constructor(props) 
    super(props);

    this.state = 
      //bookInput2: "ender",
      bookTitles: [],
      bookExample: '',
      isLoading: false
    

    this.bookClick = this.bookClick.bind(this);
  

  bookClick(book) 
    console.log(book);
    console.log(book.volumeInfo.infoLink);
    const bookURL = book.volumeInfo.infoLink;
    window.open(bookURL);
  

  componentDidMount() 
    //this.setState( isLoading: true );
    this.setState(isLoading: true);
    axios.get(`https://www.googleapis.com/books/v1/volumes?q=$this.props.bookInput`)
      .then((response) => 
        const bookExample1 = response.data.items;
        console.log(bookExample1);
        this.setState(bookTitles: bookExample1, isLoading: false);
      )
      .catch((error) => 
        console.error('ERROR!', error);
        this.setState(isLoading: false);
      );
  

  render() 
    return (
      <div>
         this.state.bookTitles ? (
          <div>
            <h2>book list</h2>
            <ul className = 'list-style'>
              this.state.isLoading && 
                (<div>
                  loading book list
                </div>)
              
              this.state.bookTitles.map(book => (
                <li key=book.id>
                  <span className = 'book-details book-title' onClick = () => this.bookClick(book)> book.volumeInfo.title</span>
                  <br/>
                  book.volumeInfo.imageLinks && 
                    <img src = book.volumeInfo.imageLinks.thumbnail/>
                  
                   book.volumeInfo.description && 
                    <span className = 'book-details'>book.volumeInfo.description</span>
                  
                  <br/>
                  <span className = 'book-details'>Categories book.volumeInfo.categories</span>
                </li>
              ))
            </ul>
        </div>) :
        (<p>sorry, that search did not return anything</p>)
      </div>
    );
  

【问题讨论】:

请删减你的代码,只保留相关的部分。 【参考方案1】:

您可能正在寻找类似的东西吗? https://stackblitz.com/edit/react-snoqkt?file=index.js

上面的代码可以进一步简化和组织,但它给了你一些想法。

代码的主要变化。

    将 Api 调用从 componentDidMount 生命周期事件更改为名为 getInitialdata 的新方法,该方法在 handleSubmit 中调用。

    getInitialdata(name)
     axios.get(`https://www.googleapis.com/books/v1/volumes?q=$name`)
      .then((response) => 
        const bookExample1 = response.data.items;
        console.log(bookExample1);
        this.setState(bookTitles: bookExample1, isLoading: false, bookSubmitted: 'userSub');
      )
      .catch((error) => 
        console.error('ERROR!', error);
        this.setState(isLoading: false, bookSubmitted: 'userSub');
      );
    

    改变了 Child 组件的使用方式。

    <Book bookTitles=this.state.bookTitles isLoading=this.state.isLoading/>
    

您的代码的问题是您正在组件的 didMount 方法中进行 API 调用。只有在安装组件时才会调用此生命周期事件。不是更新的时候。 当您在文本框中输入一些输入并单击“搜索书籍”时,componentDidMount 事件不会触发。这就是 API 调用从第二次开始没有发生的原因。

https://reactjs.org/docs/react-component.html#componentdidmount 上有关生命周期事件的更多信息

【讨论】:

好的,我更新了它,现在可以使用了!我只是将加载保留在父组件中-您认为这重要吗?在这里:codesandbox.io/s/wyjovm13l7 您可以在父组件中添加加载,因为您在那里进行 api 调用。据我所知,这不重要。我的一个建议是,可以创建一个新的加载组件,以便在需要时可以重复使用。【参考方案2】:

我已获取您的代码并将其推断为this sandbox。正如您所说,您的父组件状态正在更新,但问题是子组件不会更改 its 状态。

状态更改总是会触发 React 中的重新渲染。唯一的问题是,您的子组件正在管理它自己的状态,而这并没有直接改变。相反,它只是一次又一次地接收新的道具,但没有对它们做任何事情。

如果您查看 &lt;Book /&gt; 组件的代码,您只会修改其在 componentDidMount 上的状态,这只会发生一次。如果您想以编程方式使其更新,您可以执行以下两项操作之一。

    从子组件中移除状态,使其完全依赖于 props,使其与父组件保持同步 使用 componentDidUpdate 生命周期方法 (docs) 选择何时更改子节点的状态(这将触发重新渲染)

【讨论】:

感谢您向我展示沙盒,非常方便!我让它像这样工作:codesandbox.io/s/wyjovm13l7 我想通过你的第一个建议,因为我不确定如何正确使用 componentDidUpdate。

以上是关于即使在调用 setState 之后,React.js 子状态也不会重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 setState 之后调用 getDerivedStateFromProps?

在等待之后调用 setState 时,状态立即可用

在 dispose() 之后调用 setState()

我的有状态小部件不会更新 ui,即使我正在调用 setState 并将正确的值传递给小部件类

react的setState使用中遇到的问题

怎么可能在 API 调用和 setState 之后,同一个对象的某些元素可以访问,而其他元素不能访问?