ReactJS, 待办事项列表, 如何从列表中删除元素

3
我是一名ReactJS新手(更确切地说,我是一名编程新手),正在寻找一些答案,请不要因为显而易见的愚蠢而排除我:) 我在传递props和理解'this'上下文方面遇到了很大的困难。
这是我的混乱,两个简单的Todo应用程序组件。TodoApp:
import React from 'react';
import uuid from 'uuid';
import style from './App.css';
import Title from '../components/Title.js';
import TodoList from '../components/TodoList.js';

class App extends React.Component {
    constructor(props) {
        super(props);
        this.title = "Todo Application"
        this.state = {
            data: [{
                id: 1,
                text: 'clean room'
                }, {
                id: 2,
                text: 'wash the dishes'
                }, {
                id: 3,
                text: 'feed my cat'
            }]
        };
    }

    removeTodo(id) {
        const remainder = this.state.data.filter(item => item.id !== id);
        this.setState({data: remainder});
    }

    render() {
        return (
            <div className={style.TodoApp}>
                <Title title="Todo Application" added={this.state.data.length} />
                <TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
            </div>
        );
    }
}

export default App;

还有待办事项清单:

import React from 'react';

const TodoList = props => (
    <ul>
        {props.data.map((item, index) =>
            <li key={index}>
                {item.text}
                <button value={index} onClick={() => props.remove(index)}>x</button>
            </li>
        )}
    </ul>
);

export default TodoList;

我想问的是如何正确地向子组件(TodoList)传递 props,以使删除按钮起作用?


我没有看到明显的错误,但你是否尝试将“index”作为参数传递给“onClick”回调函数?基本上是(index)而不是(). 当你点击按钮时,你有收到任何错误吗? - IronWaffleMan
你应该学习Redux,或者简单地将props添加到父组件中,在子组件中调用相同的props。 - pirs
@pirs,在这个例子中,绝对不需要Redux,那会是绝对的过度。 - Thomas Hennes
我说Redux,或者从父组件调用props... 有两种解决方案,两种方法可以实现... - pirs
2个回答

1
为了确保this始终指向定义removeTodo方法的App上下文,您可以在构造函数中添加以下内容(在设置初始状态之后):
this.removeTodo = this.removeTodo.bind(this);

否则,你的代码一点也不乱。它甚至出奇地简洁和深思熟虑,尤其是对于一个自称初学者来说更是如此。恭喜!
正如Sag1v在下面的评论中指出的那样,你没有将正确的值传递给你的removeTodo方法。你传递的是正在迭代的项目的索引,而不是其id。
将你的<button>调用更改为以下内容:
<button value={index} onClick={() => props.remove(item.id)}>x</button>

请注意,您也可以通过以下方式实现相同的效果:
<button value={index} onClick={props.remove.bind(null, item.id)}>x</button>

这不会起作用,因为 OP 传递了错误的 id - Sagiv b.g
1
@Sag1v 很好的发现,感谢你指出。回答已更新。 - Thomas Hennes

1
你有两个主要问题:
  1. As Jaxx mentioned you are not binding the handler removeTodo to the class.
    There are couple of ways to do it.

    • Bind it in the constructor:

      this.removeTodo = this.removeTodo.bind(this);

    • Or use the ES2015(ES6) Arrow functions which will use the lexical context for this:

      removeTodo = (id) => {
         const remainder = this.state.data.filter(item => item.id !== id);
         this.setState({data: remainder});
      }
      
  2. The 2nd problem is that inside TodoList onClick handler you are not passing the correct id to the handler, you are passing the index position.

    onClick={() => props.remove(index)}
    

    You should change that to:

    onClick={() => props.remove(item.id)} 
    

    There is another problem with this approach which i'll explain next.

这是一个可工作的示例:

const Title = ({title}) => <h1>{title}</h1>

const TodoList = props => (
    <ul>
        {props.data.map((item, index) =>
            <li key={index}>
                {item.text}
                <button value={index} onClick={() => props.remove(item.id)}>x</button>
            </li>
        )}
    </ul>
);

class App extends React.Component {
    constructor(props) {
        super(props);
        this.title = "Todo Application"
        this.state = {
            data: [{
                id: 1,
                text: 'clean room'
                }, {
                id: 2,
                text: 'wash the dishes'
                }, {
                id: 3,
                text: 'feed my cat'
            }]
        };
    }

    removeTodo = (id) => {
        const remainder = this.state.data.filter(item => item.id !== id);
        this.setState({data: remainder});
    }

    render() {
        return (
            <div>
                <Title title="Todo Application" added={this.state.data.length} />
                <TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
            </div>
        );
    }
}

ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

正如我所说,这种方法存在问题,你正在每次渲染时传递一个新的函数实例,代码行如下:
onClick={() => props.remove(item.id)

这被视为不良实践,因为它可能会中断调和和差异算法
但是我们知道,事件处理程序应该得到一个函数引用,因此你不能像这样传递一个函数调用。
onClick={props.remove(item.id)}

这将传递函数的返回类型(如果有)而不是函数的引用。
因此,传递函数引用的正确方法如下:
onClick={props.remove}

但是对于您的情况来说,这并不好,因为您需要将当前项目的id传回给父级,但恐怕浏览器只会传回event参数。

那么你问什么是替代方案?

创建另一个组件,并控制从组件传入和传出的数据,而不是依赖于浏览器的善意。

这里是另一个工作示例,但这次没有在每个渲染上创建新的函数实例。

const Title = ({title}) => <h1>{title}</h1>

class TodoItem extends React.Component {
  handleClick = () => {
    const{item, onClick} = this.props;
    onClick(item.id);
  }

  render(){
    const {item} = this.props;
    return(
      <li>
        {item.text}
        <button value={item.id} onClick={this.handleClick}>x</button>
      </li>
    );
  }
}

const TodoList = props => (
    <ul>
        {props.data.map((item, index) =>
            <TodoItem key={index} item={item} onClick={props.remove} />
        )}
    </ul>
);

class App extends React.Component {
    constructor(props) {
        super(props);
        this.title = "Todo Application"
        this.state = {
            data: [{
                id: 1,
                text: 'clean room'
                }, {
                id: 2,
                text: 'wash the dishes'
                }, {
                id: 3,
                text: 'feed my cat'
            }]
        };
    }

    removeTodo = (id) => {
        const remainder = this.state.data.filter(item => item.id !== id);
        this.setState({data: remainder});
    }

    render() {
        return (
            <div>
                <Title title="Todo Application" added={this.state.data.length} />
                <TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
            </div>
        );
    }
}

ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>


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