![](/img/trans.png)
[英]react component renders twice with only one setState in componentWillMount
[英]ReactDOM renders only one component
我想创建一个具有评论功能的应用程序。 我正在尝试这样的代码:
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
我正在使用 MySQL。 Axios 用于 HTTP 请求。 而Next.js为框架。
完整代码:
import React from 'react'
import ReactDOM from 'react-dom'
import styles from './comments-list.module.css'
class CommentMessage extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<div>
<b>{this.props.username}</b>
<span>: </span>
<span>{this.props.message}</span>
</div>)
}
}
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
})
.catch(err => {
console.log(err)
})
}
render() {
return (<div ref={this.commentListRef} onLoad={this.loadComments()}>
</div>)
}
}
export default CommentsList
但它只呈现这个:
期待这个:
你的做法很奇怪; 我不知道这是不是故意的。 无论如何,推荐的方法是将评论存储为组件的 state 的一部分,并在收到评论时更新 state。
像这样:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: []
};
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState({
comments: response.data
});
})
.catch(err => {
console.log(err)
})
}
componentDidMount(){
this.loadComments();
}
render() {
return (<div ref={this.commentListRef}>
(this.state.comments.map(comment => (
<CommentMessage username={comment.username} message={comment.message}/>
)))
</div>)
}
}
此外,您的 onLoad 没有按预期工作。 每次组件呈现时它都会调用 loadComments,我什至不知道 onLoad 是否是 div 上的正确事件。
无论如何,如果您绝对想按照自己的方式进行操作,则必须将每个节点安装到其自己的容器中。 正如您现在所拥有的,每条评论都覆盖了commentListRef 的内容。 因此,您必须创建一个新元素 append 到 commentListRef,并将反应组件安装到该元素:
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const element = document.createElement('div');
this.commentListRef.current.appendChild(element);
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, element)
})
})
.catch(err => {
console.log(err)
})
}
ReactDOM.render
只会为给定容器渲染一个组件。 从文档:
首次调用时,内部的任何现有 DOM 元素都会被替换。 后面的调用使用 React 的 DOM diffing 算法进行有效更新。
基本上,当您在循环中调用ReactDOM.render
时,React 将每个给定组件视为对前一个组件的更新,而不是单独渲染每个组件。
最佳实践是在根容器(通常称为<App>
)呈现单个组件。 但是,您似乎已经这样做了,因为这些ReactDOM.render
调用正在另一个组件中发生。 通常,您应该只需要在应用程序中使用ReactDOM.render
一次。
相反,您可以将数据存储在CommentsList
组件的 state 中,然后从父组件的render
方法中返回子组件。
例如:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: [],
}
}
loadComments = () => {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState(prev => ({...prev, comments: response.data}));
})
.catch(err => {
console.log(err)
})
}
render() {
const { comments } = this.state;
return (
<React.Fragment>
{comments.map(e => (
<CommentMessage key={e.id} username={e.username} message={e.message}/>
))}
</React.Fragment>
)
}
}
注意:我还向CommentMessage
组件传递了一个key
,以便为每个孩子提供一个稳定的身份(有关更多信息,请参阅文档)。 不得不猜测,但我假设评论会有一个id
值,如果没有,您可以为评论选择一个不同的唯一值作为键。
此外,我建议将基于类的组件迁移到React Hooks ——一旦你掌握了钩子,使用起来会更容易。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.