当前位置: 首页 > 工具软件 > To-Do-List > 使用案例 >

React to-do-list官方推荐示例详细学习

司浩壤
2023-12-01

用react做to-do-list是上手react的一个基本练习。就像玩游戏一样,游戏有一以贯之的逻辑,越早把握基础逻辑的玩家就越能轻松驾驭游戏。这个官方示例对于我来说学习价值极高,试以现在的眼光深入学习一下。
源码:https://download.csdn.net/download/Rance_King/12365080
示例
文件结构拆分:
这个示例中,TodoApp包含了TodoList和TodoInput组件,TodoList包含TodoItem组件

这个拆分方法其实向我们展示了一种react的惯用结构,就是app是最外层,专门处理大量逻辑和状态管理的一个管理层,然后在App之后向下细分,则分出各种实体的组件。在App这个层面的方法和内容都是抽象的,作者还特意把state中的数据结构摆在设计出来的app正上面,非常容易能够对应地看出作者想要展现出来的逻辑。
todos可以想象需要以array的方式进行设计,以适应增加的todolist,而每一条todolist都会需要id,title,和一个表示完成的flag。而App实际上可以管理更多的逻辑,这样的话,todos就作为状态中的一个主要的key被分出来了,然后再以array的形式去再组织。
这个数据结构和组件的抽象形式越早成型,代码整体也就越早成型,不然写到后面则必须不断调整,直到像这个示例一样能够清晰地控制数据为止。在App中返回的整体架构要极度抽象,不要去写具体的实现方法,而是写清楚“我要做什么”。
这可以类比一种金字塔型的管理的思维,站在顶端的领导只提出要求,不管具体做法,只是指出方向和调配资源。

  state = {
    todos: [
      {
        id: new Date().getTime(),
        title: 'My Todo',
        complete: false
      }
    ]
  }

  render() {
    const { todos } = this.state;
    return (
      <div className="TodoApp">
        <TodoInput onTodoItemAdded={this.handleTodoItemAdded} />
        <TodoList
          todos={todos}
          onTodoItemToggled={this.handleTodoItemToggled}
          onTodoItemRemoved={this.handleTodoItemRemoved} />
      </div>
    );
  }

id必须唯一,所以在这里用了时间戳来作为唯一的id,这是一个常用思路。另外,title作者填入了一个初始值,留空是不利于展示的。

随后处理输入框,示例中全部都用了class的写法,function的写法可以等到代码重构的时候,在初期学习的时候,重要的事情是将逻辑写通顺,stateless的function写法可以完全等到最后熟悉时再来重构。
这里可能可以得到一个经验上的写法,就是在最开始写的时候,一律按照class来写,如果临时想要补充this.state就临时补,到最后再进行重构,将state抽上去,并且class改写,用hook重构。

class TodoInput extends Component {
    handleKeyPress = e => {
        const key = e.key;
        const value = e.target.value;
        if(key === 'Enter'){
            e.target.value = '';
            this.props.onTodoItemAdd(value);
        }
    }

    render(){
        return <input type="text" onKeyPress={this.handleKeyPress} />
    }
}

随后处理TodoList:

class TodoList extends Component {
    render() {
        const { todos, onTodoItemToggle, onTodoItemRemove } = this.props;
        return <div>
            {
                todos.length > 0
                ? todos.map(todo =>
                    <TodoItem
                        key={todo.id}
                        onTodoItemToggle={onTodoItemToggle}
                        onTodoItemRemove={onTodoItemRemove}
                        {...todo}
                    />
                )
                : <h5>No todo yet.</h5>
            }
        </div>
    }
}

两个方法和todo中的数据作为prop继续往下传,在这一层处理的主要是条件渲染的问题。注意{…todo}这个写法,这是在一个组件的上层把取出来的所有数据作为props往下传,其效果等于写了id={todo.id}, title={todo.title}, complete={todo.complete}
这个传值的写法也极度典型,值得记下来用。

随后写到这个应用的最底层,也就是TodoItem层,这一层展示了最终实现出来的每个list点的要完成的内容

class TodoItem extends Component {

    handleTodoTitleClick = () => {
        const { onTodoItemToggle, id } = this.props;
        onTodoItemToggle(id);
    }

    handleRemoveButtonClick = () => {
        const { onTodoItemRemove, id } = this.props;
        onTodoItemRemove(id);
    }

    render() {
        const { title, complete } = this.props;
        return (
            <div>
                <div onClick={this.handleTodoTitleClick}
                     style={{ textDecoration: complete ? 'line-through': 'none'}}>
                    {title}
                </div>
                <div onClick={this.handleRemoveButtonClick}>
                    X
                </div>
            </div>
        )
    }
}

然后,发现了实际上这一层的方法在最顶层就提供了。实际上这个方法也可以单独写成helper function,但很明显,react就是想要层层控制,所有最核心的内容都在最顶层就已经订好了。而下面的具体个案只是一个具现出来的个体而已。
全部看完就会发现,每一个数据的最终结局早就在开头订好了,这也是由react设计的本性决定的,因为实现状态管理要this.setState,这个this只能指向它自身这个实体,分散出去的组件不能算,正因为如此,最高层需要掌控整个流程。

handleTodoItemAdd = title => {
        this.setState(prevState => {
            return {
                todos: prevState.todos.concat([{
                    id: new Date().getTime(),
                    title,
                    complete: false
                }])
            }
        })
    }

    handleTodoItemToggle = id => {
        this.setState(prevState => {
            return {
                todos: prevState.todos.map(item => (
                    item.id === id
                    ? {...item, complete: !item.complete}
                    : item
                ))
            }
        })
    }

    handleTodoItemRemove = id => {
        this.setState(prevState => {
            return {
                todos: prevState.todos.filter(item => {
                    return item.id !== id
                })
            }
        })
    }

其实这三个最高层的function写得还非常工整,一眼就可以看出输入和输出的关系,以及逻辑被简单地用js的函数处理掉了。

to-do-list的逻辑是不难实现的,但是这个示例极好地体现了很多react应有的对数据的处理方法和书写的要求。

 类似资料: