React组件问题与状态,setState和渲染

问题描述:

我正在构建一个基于React的项目用于学习目的。我被困在制作一个表格组件,它呈现自己,然后向mbaas后端发送一个Ajax请求,以获取所有书籍条目并将每个条目填充到新行中。这是我到目前为止所做的。请原谅大块的代码,但因为我还没有完全理解的方法,状态和渲染(之间的相互作用),这里是整个类:React组件问题与状态,setState和渲染

class BooksTable extends Component { 
    constructor(props) { 
     super(props); 
     this.state = { 
      books: [] 
     }; 

     this.updateState = this.updateState.bind(this); 
     this.initBooks = this.initBooks.bind(this); 
    } 

    componentDidMount() { 
     let method = `GET`; 
     let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}`; 
     let headers = { 
      "Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`, 
      "Content-Type": `application/json` 
     }; 

     let request = {method, url, headers}; 
     $.ajax(request) 
      .then(this.initBooks) 
      .catch(() => renderError(`Unable to connect. Try again later.`)); 
    } 

    deleteBook(id) { 
     let method = `DELETE`; 
     let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}/${id}`; 
     let headers = { 
      "Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`, 
      "Content-Type": `application/json` 
     }; 

     let request = {method, url, headers}; 
     $.ajax(request) 
      .then(() => this.updateState(id)) 
      .catch(() => renderError(`Unable to delete, something went wrong.`)); 
    } 

    updateState(id) { 
     for (let entry of this.state.books.length) { 
      if (entry.id === id) { 
       // Pretty sure this will not work, but that is what I've figured out so far. 
       this.state.books.splice(entry); 
      } 
     } 
    } 

    initBooks(response) { 
     console.log(`#1:${this.state.books}); 
     console.log(`#2:${this}); 
     for (let entry of response) { 
      this.setState({ 
       books: this.state.books.concat([{ 
        id: entry._id, 
        name: entry.name, 
        author: entry.author, 
        description: entry.description, 
        price: Number(entry.name), 
        publisher: entry.publisher 
       }]) 
      },() => { 
       console.log(`#3${this.state.books}`); 
       console.log(`#4${this}`); 
      }); 
     } 
    } 

    render() { 
     return (
      <div id="content"> 
       <h2>Books</h2> 
       <table id="books-list"> 
        <tbody> 
         <tr> 
          <th>Title</th> 
          <th>Author</th> 
          <th>Description</th> 
          <th>Actions</th> 
         </tr> 
         {this.state.books.map(x => 
          <BookRow 
           key={x.id} 
           name={x.name} 
           author={x.author} 
           description={x.description} 
           price={x.price} 
           publisher={x.publisher} />)} 
        </tbody> 
       </table> 
      </div> 
     ); 
    } 
} 

现在BookRow是不是很有趣,只有onClick部分是相关的。它看起来像这样:

<a href="#" onClick={() => this.deleteBook(this.props.id)}>{owner? `[Delete]` : ``}</a> 

如果登录用户不是本书的出版者,则链接不应该是可见的。 onClick调用来自BookTable的方法deleteBook(id)。在成功的ajax上,它应该从state.books(数组)和render中删除这本书。

我对initBooks方法特别困惑。我在填充状态的循环之前添加了日志,并在状态更新时作为回调。日志#1和日志#3的结果相同,日志#2#4的结果相同。另外,如果我扩展日志#2(在setState之前)或日志#4,那么这两个显示状态= [1]。这有什么意义?此外,如果您查看日志#1#3 - 他们打印[]。我可能错过了一些内部组件交互,但我真的不知道是什么。

谢谢。

+0

的setState动作是异步的,所以更改可能不会在this.state反映的时候了。 –

setState不会立即更新状态。所以在for循环的第二次迭代中,你不会得到新的状态。因此,请先制作新的图书清单,然后在新清单准备好后再进行设置。

试试这个:

initBooks(response) { 
    console.log(this.state.books, "new books not set yet") 
    let newBooks = [] 
    for (let entry of response) { 
    newBooks.push({ 
       id: entry._id, 
       name: entry.name, 
       author: entry.author, 
       description: entry.description, 
       price: Number(entry.name), 
       publisher: entry.publisher 
      }) 
    } 
    this.setState({books: [...this.state.books, newBooks]},() => { 
     console.log(this.state.books, "new books set in the state") 
    }) 
} 
+0

感谢您的回答。 @*的灵魂,也非常感谢。我会试一试。问题是,在我目前的测试中,循环只进行一次,因为我只有一个测试记录。在循环中设置状态是愚蠢的,我应该想到这一点,但它仍然感到奇怪。让我试试看,回到你身边。 – Alex

+0

如果循环只进行一次,那么它应该工作。我复制了你的代码并试了一下。它对我很有用 – Swapnil

+0

正如我的想法 - 虽然你的意思是将setState从循环中移出很好,但它并不能帮助我解决问题。运行你的代码 - 它不会打印'在该州设置的新书'。我只收到有关重要财产的警告。我仍然无法解释日志#1#2如何输出不同的结果,因为它们正在记录相同的对象。为什么打印对象显示长度为1的更新状态并且包含从服务器响应接收到的对象。我甚至没有初始化临时阵列持有人呢.. – Alex

试试这个:

initBooks(response = {}) { 
    const books = Object.keys(response); 

    if (books.length > 0) { 
     this.setState((prevState) => { 
      const newBooks = books.reduce((acc, key) => { 
       const entry = response[key]; 

       return [ ...acc, { 
        id: entry._id, 
        name: entry.name, 
        author: entry.author, 
        description: entry.description, 
        price: Number(entry.name), 
        publisher: entry.publisher 
       }]; 
      }, prevState.books); 

      return { books: newBooks }; 
     }); 
    } 
} 

我在这里做什么?

  • setState仅在需要时(即仅当存在来自API响应数据)
  • 避免状态突变,无setState内使用功能((prevState, props) => newState),以确保可靠性原子 更新循环

的考虑点:

  • 不要发生变异的状态
  • 避免setState一个循环中;如果你想同时呼吁setState访问以前的状态,而不是准备新的状态对象,做一个一次性setState

  • ,它是advised做这样的:

    this.setState((prevState, props) => { 
        return { counter: prevState.counter + props.increment }; 
    }); 
    

    setState()不立即变异this.state,但创建一个 待处理状态转换。调用此 方法后访问this.state可能会返回现有值。

+0

好的提示总结。谢啦。 – Alex