我现在面临一个奇怪的问题。 我有一个通过rest api来的任务列表。 我创建了一个自定义卡组件来显示它。
/* eslint-disable import/first */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Grid from 'material-ui/Grid';
import Paper from 'material-ui/Paper';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Button from 'material-ui/Button';
import Card, { CardActions, CardContent } from 'material-ui/Card';
import Typography from 'material-ui/Typography';
import TextField from 'material-ui/TextField';
import { LinearProgress } from 'material-ui/Progress';
import Modal from 'react-responsive-modal';
var Moment = require('moment');
import TaskActions from '../redux/TaskRedux'
import ProgressColumn from './Progress'
import TaskCard from './Card'
import AddTaskCard from './AddTaskCard'
import { connect } from 'react-redux'
import '../styles/main.css';
export class Container extends Component{
constructor(props){
super(props);
this.state = {
fetching: this.props.fetching,
taskName: null,
showError: false,
openEditModal: false,
editTaskName: null,
showUpdateError: false,
toBeUpdatedTask: null,
progressTasks:[],
tasks: []
}
}
componentDidMount(){
this.props.getTasks()
}
componentDidUpdate(prevProps, prevState) {
if(prevProps !== this.props){
this.setState({fetching: this.props.fetching,
tasks: this.props.tasks})
}
}
render(){
let {fetching, progressTasks} = this.state
let {tasks} = this.props
if(tasks)
tasks.map(i => console.log(i.title))
return (
<div className="grid-root">
<Grid container spacing={24}>
<Grid item xs={3} sm={3}>
<Paper className="task-list-view">
<List className="task-list">
{tasks && tasks.map((item,i) => (
<ListItem key={`item-${i}`}>
<TaskCard task={item} key={`item-${i}`}/>
</ListItem>
))}
</List>
<AddTaskCard/>
</Paper>
{fetching ? (<LinearProgress color="secondary" />): ('')}
</Grid>
<Grid item xs={3} sm={3}>
<Paper className="task-list-view">
<ProgressColumn progressTasks={progressTasks}/>
</Paper>
</Grid>
</Grid>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
fetching: state.task.fetching,
tasks: state.task.tasks,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getTasks: () => dispatch(TaskActions.fetchTasks()),
deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
updateTask: (taskId,title) => dispatch(TaskActions.updateTask(taskId,title)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Container)
以下是卡组件:
/* eslint-disable import/first */
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Card, { CardActions, CardContent } from 'material-ui/Card';
import Button from 'material-ui/Button';
import Typography from 'material-ui/Typography';
import TaskActions from '../redux/TaskRedux'
import EditModal from './EditModal'
var Moment = require('moment');
export class TaskCard extends Component{
constructor(props){
super(props)
this.state = {
openEditModal: false,
editTaskName: null,
task: this.props.task
}
}
// handle edit of task name
handleTaskEdit(task){
console.log("edit task", task)
// this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
this.props.editTask(task)
}
// handle start task button click
// Will push the tasks for progressTasks state
handleStartTask(task){
// this.setState({
// progressTasks: [...this.state.progressTasks, task]
// })
}
// handle deletion of the task
handleTaskDelete(taskId){
console.log(taskId)
this.props.deleteTask(taskId)
// this.props.getTasks()
}
render(){
let {task} = this.state
let created = Moment(task.created).format("Do MMM YYYY")
console.log("task", task)
return (
<Card className="task-card" key={task.id}>
<EditModal/>
<CardContent>
<Typography variant="headline" component="h2">
{task.title}
</Typography>
<Typography color="textSecondary">
{created}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, task)}>Edit</Button>
<Button size="small" color="primary" onClick={this.handleStartTask.bind(this, task)}>Start Task</Button>
<Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, task.id)}>Delete</Button>
</CardActions>
</Card>
)
}
}
const mapStateToProps = (state) => {
return {
}
}
const mapDispatchToProps = (dispatch) => {
return {
getTasks: () => dispatch(TaskActions.fetchTasks()),
deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
editTask: (currentTask) => dispatch(TaskActions.editTask(currentTask))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TaskCard)
当我从顶部或底部删除任务时,它会更新正常,但是当我从中间执行时,底部的一个消失,而要删除的一个仍然保持/显示。当你刷新它时,当然一切都很好,但问题是,为什么会发生这种情况?我有一个redux传奇,它在删除操作后再次获取任务,我可以确认道具也能获得正确的数据。
所以,我试着从我的最终调试它。看起来TaskCard有点缓存道具。
在添加了{item.title}
的图片中,正好在其下方是呈现TaskCard的地方,两者都有不同的标题,但{item.title}
是正确的。
基于jmathew,回答,我将ListItem和TaskCard的密钥更新为item.id,因此删除工作,但更新仍然无法正常工作。同样,根据Update 1,它仍然显示错误的标题,但{item.title}是正确的。 所以,代码的那部分现在看起来像:
<List className="task-list">
{tasks && tasks.map((item,i) => (
<ListItem key={item.id}>
{item.title}
<TaskCard item={item} key=
{`item-${item.id}`}/>
</ListItem>
))}
</List>
新的TaskCard组件:
/* eslint-disable import/first */
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Card, { CardActions, CardContent } from 'material-ui/Card';
import Button from 'material-ui/Button';
import Typography from 'material-ui/Typography';
import TaskActions from '../redux/TaskRedux'
import EditModal from './EditModal'
var Moment = require('moment');
export class TaskCard extends Component{
constructor(props){
super(props)
this.state = {
openEditModal: false,
editTaskName: null,
item: this.props.item
}
}
// handle edit of task name
handleTaskEdit(task){
console.log("edit task", task)
// this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
this.props.editTask(task)
}
// handle start task button click
// Will push the tasks for progressTasks state
handleStartTask(task){
// this.setState({
// progressTasks: [...this.state.progressTasks, task]
// })
}
// handle deletion of the task
handleTaskDelete(taskId){
// console.log(taskId)
this.props.deleteTask(taskId)
// this.props.getTasks()
}
componentDidUpdate(prevProps, prevState, snapshot){
console.log("update ", prevProps, prevState)
}
render(){
let {item} = this.state
let created = Moment(item.created).format("Do MMM YYYY")
console.log("item",item)
return (
<Card className="task-card" key={`task-${item.id}`}>
<EditModal/>
<CardContent>
<Typography variant="headline" component="h2">
{item.title}
</Typography>
<Typography color="textSecondary">
{created}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, item)}>Edit</Button>
<Button size="small" color="primary" onClick={this.handleStartTask.bind(this, item)}>Start Task</Button>
<Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, item.id)}>Delete</Button>
</CardActions>
</Card>
)
}
}
const mapStateToProps = (state) => {
return {
}
}
const mapDispatchToProps = (dispatch) => {
return {
getTasks: () => dispatch(TaskActions.fetchTasks()),
deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
editTask: (currentTask) => dispatch(TaskActions.editTask(currentTask))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TaskCard)
正如您在上面的图片中看到的那样,卡片旁边的标题是更新的标题,由编辑按钮触发,但同样的事情不会传递给TaskCard组件。在屏幕截图中,有一个控制台输出,“更新”行显示更改未触发componentDidUpdate。
答案 0 :(得分:2)
问题似乎是子组件中键和使用状态的组合。由于您使用索引作为键,如果从中间删除项目,数据渲染映射的索引会更改,并且使用不同的道具重新呈现卡片,但是,您没有更新taskCard中的状态,因此数据不会#39;改变。设置直接可以从道具派生的状态并不是正确的方式,如果你这样做,你还需要更新状态以响应道具变化
要解决此问题,您需要做的就是从状态
渲染任务 export class TaskCard extends Component{
constructor(props){
super(props)
this.state = {
openEditModal: false,
editTaskName: null,
}
}
// handle edit of task name
handleTaskEdit(task){
console.log("edit task", task)
// this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
this.props.editTask(task)
}
// handle start task button click
// Will push the tasks for progressTasks state
handleStartTask(task){
// this.setState({
// progressTasks: [...this.state.progressTasks, task]
// })
}
// handle deletion of the task
handleTaskDelete(taskId){
console.log(taskId)
this.props.deleteTask(taskId)
// this.props.getTasks()
}
render(){
let {task} = this.props;
let created = Moment(task.created).format("Do MMM YYYY")
console.log("task", task)
return (
<Card className="task-card" key={task.id}>
<EditModal/>
<CardContent>
<Typography variant="headline" component="h2">
{task.title}
</Typography>
<Typography color="textSecondary">
{created}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, task)}>Edit</Button>
<Button size="small" color="primary" onClick={this.handleStartTask.bind(this, task)}>Start Task</Button>
<Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, task.id)}>Delete</Button>
</CardActions>
</Card>
)
}
}
但是,使用唯一的项目ID可以提高性能。
答案 1 :(得分:1)
您的症状与key
ListItem
道具的问题一致。当您需要支持数据的独特内容时,您似乎正在使用数组中的索引。
在调用delete之前,react会看到一个带有键1, 2, 3, 4
的VDOM。然后在3
上点击删除。现在重新渲染被调用,并使用i
重新生成密钥。新密钥为1, 2, 3
。 React将前一个DOM与新的DOM区分开来,看到只有4
丢失并将其删除。
更新问题的解决方案可以通过以下两种方式之一解决:
在渲染中使用道具而不是状态。这是首选
选择因为它更简单。 let {item} = this.state
&gt; let {item} =
this.props
(根据Shubham的回答)。
实施componentDidUpdate。如果您仍想保留物品 状态您需要确保在父组件时更新它 发送你的组件新道具:
可能看起来像这样:
componentDidUpdate(prevProps,prevState) {
if(this.props.item != this.state.item) {
this.setState( { item: this.props.item });
}
}