React新手上路:用组件化思维搭建Todo List应用

491 阅读6分钟

前言

    React是前端第一开发框架,是一个由 Facebook 开发的用于构建用户界面的 JavaScript 库,主要用于构建单页面应用程序(SPA)。React 的核心理念是将 UI 分解为可重用的组件,每个组件负责渲染和管理 UI 的一部分。这种组件化的方式使得代码更加模块化,易于维护和扩展。

使用React框架创建一个TodoList简陋demo

    React官网并没有为我们过多的介绍它自己的方法的使用,大家如果有兴趣的话可以自行阅读。

1.分析功能需求

image.png

    从图中我们可以看到有一个添加按钮,当我们点击添加按钮后,我们可以把输入框中的数据,显示在页面上,然后添加的数据后面紧跟一个编辑,删除按钮,编辑的功能是,当我们点击编辑时,我们可以修改该数据的值,点击删除就可以删除该数据。这就是我们这个简陋的demo的功能了。 看完之后我们就要开始划分组件了,首先,我们的父组件的作用是显示作为主页面,然后存在三个子组件,分别是表单Form部分,添加数据后List,List中的数据Item,三个子组件。

2.功能实现

    首先我们应该创建一个React项目,我们在VsCode终端创建我们的React项目

//创建React项目文件 执行代码后会问两个问题第一个问题选React 第二个问题选JavaScript
npm init vite
//进入项目文件
cd 文件名
//下载项目依赖
npm i (install)
//执行项目
npm run dev

代码执行后,我们需要处理一些文件在src文件夹下的index.css的样式我们全部删除。对于css的样式,这里可以跟大家提几嘴,一般我们都是直接用

*{
  padding: 0;
  margin: 0;
}

去去除边距的,但是这样会导致页面加载的速度变慢,性能不好,以及使用

#list li{

}

这样写css也会导致代码性能下降,因为css选择器的匹配规则是从右到左的顺序来读取的,所以css选择器会选找到所有的li标签,再去找li中谁的id是#list,这样会大大降低页面加载的效率的。对于页面的性能优化,可以解读为,页面加载,首先是生成DOM树,然后渲染CSSOM树,然后读取script,所以我们一般script放在底部,以免导致页面堵塞,然后生成的DOM树+CSSOM树 = 渲染树,然后再生成布局树,最后再由GPU去绘制页面,形成静态页面。

    打开我们的App.jsx文件,这是我们的父组件

import {Component} from 'react'
class App extends Component{
  constructor(props){
    super(props)//继承父类  将父类的构造函数执行一下
    // 私有数据  申明自己的属性
    this.state = {
      todos:[]
    }
    //重写render()函数,一定要
    render(){
        return(
            <div></div>
        )
    }
  }

    首先我们导入模块Componetn,然后我们使用es6中的模块化能力,使用传统面向对象的能力。在constructor()函数中,我们接收父类中的参数props,并使用super()函数调用。然后我们再私有化我们需要的数据,这里我私有化一个数据todos:[]用来存放我们的数据值。父组件中的第一步我们就完成了。然后我们再src文件夹下创建一个components文件夹,用来存放子组件,在components文件夹下,我们分别创建TodoForm.jsx/css,TodoList.jsx/css,TodoItem.jsx/css 一共六个文件。然后我们把子组件导入父组件中

import TodoForm from './components/TodoForm.jsx'
import TodoList from './components/TodoList.jsx'

然后在父组件中一共存在这几个函数adTodo()添加函数,deleteTodo()删除函数,toggleTodo()状态切换函数,editTodo()编辑函数。

// 修改状态  数据流
  addTodo = (text)=>{
    // Component 上有setState 方法,修改状态,响应式更新
    this.setState({
      todos:[
      //展开用法
        ...this.state.todos,
        {
          text,
          completed:false
        }
      ]
    })
  }

  // 数据编程
  // 数据和界面状态是一一对应的
  deleteTodo = (index)=>{
    const newTodos = [...this.state.todos]
    newTodos.splice(index,1),//删除某一项
    this.setState({
      todos:newTodos
    })
  }

  toggleTodo = (index) =>{
    const newTodos = [...this.state.todos]
    newTodos[index].completed =!newTodos[index].completed
    this.setState({
      todos:newTodos
    })
  }

  // 修改todo
  editTodo = (index,newText)=>{
    const newTodos = [...this.state.todos]
    newTodos[index].text = newText
    this.setState({
      todos:newTodos
    })
  }
  //抽象方法 abstract function
    //每一个组件的界面不同
    // 重写这个方法 
    render(){
      return(
        <div>
          <div>
            <TodoForm addTodo={this.addTodo}/>
            <TodoList
              todos={this.state.todos}
              deleteTodo={this.deleteTodo}
              toggleTodo={this.toggleTodo}
              editTodo={this.editTodo}
            />
          </div>
        </div>
      )
    }
}
// 抛出
export default App;

最后被忘了抛出App;当然在几个子组件中也是需要抛出的不然在父组件中就无法导入.然后我们再通过数据流向由父-》子 子通过调用父props传过来的方法,通知数据修改,这样我们的父组件就基本完成。

2.1TodoFom.jsx

    在TodoForm.jsx文件中我们只需要实现输入框以及添加就好了

image.png

代码的基本写法跟父组件其实是一样的,首先模块化class TodoForm extends Component 然后创建私有变量

constructor(props){
        super(props);
        this.state = {
            inputText: '聚会'
        }
    }

我们重写render()方法,在这里我们可以写Form表单

render(){
       return(
           <form className="todo-form" onSubmit={this.handleSubmit}>
                <input 
                    type="text" 
                    placeholder="请输入待办事项"
                    className="todo-form__input"
                    value={this.state.inputText}
                    onChange={this.handleChange}
                />
                <button type="submit" className="todo-form__button">Add</button>
           </form>
       )
    }

在React中写类名我们需要写className,这里我们给input框添加一个默认值value,是私有化变量this.state.inputText为“聚会”。然后添加一个名为Add的按钮,我们给表单添加函数onSubmit={this.handleSubmit}是当我们点击Add按钮后,把数据添加到List中,但是在这之前,我们会发现input框中的数据是无法修改的,原因是你的value值已经写死了为“聚会”,所以我们需要对其进行数据修改,当我们触发数据框数据修改时,原数据"聚会"就需要被替换成新的数据。

handleChange = (event)=>{
        this.setState({
            inputText: event.target.value
        })
    }

而添加函数,则是我们调用父组件传过来的props,去调用addTodo函数把数据把 this.state.inputText存入父组件中的this.state.最后不要忘了,添加完之后输入框中的数据记得清空

handleSubmit = (event)=>{
        event.preventDefault();

        if(this.state.inputText.trim()){
            this.props.addTodo(this.state.inputText)
            this.setState({
                inputText: ''
            })
        }
    }

但是这种情况,我们添加完数据后,刷新页面,就会导致数据没有被保存。因此我们可以在父组件中把子组件传过来的数据存到浏览器中,但是在React中我们又如何监听数据更新呢。 这里我们可以使用生命周期函数,进行数据存储

constructor(props){
    super(props)//继承父类  将父类的构造函数执行一下
    // 私有数据  申明自己的属性
    const savedTodos = JSON.parse(localStorage.getItem("todos"))||[];
    this.state = {
      todos:[]
    }
  }
  
  ------------------------------------------
  // 生命周期
  componentDidUpdate(){
    // console.log('updata....');
    localStorage.setItem('todos', JSON.stringify(this.state.todos))
  }

这里我们的constructor函数也需要相应的进行修改。

2.2TodoList.jsx

    在TodoList.jsx文件中我们只需要遍历数据即可

import { Component } from "react";
import TodoItem from "./TodoItem";
import './TodoList.css'


class TodoList extends Component {
    render(){
        //解构父组件传过来的数据
        const {todos,deleteTodo,toggleTodo,editTodo} = this.props;
       return(
            <div>
                {
                //遍历父组件传过来的参数
                    todos.map((todo,index)=>{
                        return <TodoItem 
                                key={index} 
                                index={index} 
                                todo={todo}
                                deleteTodo={deleteTodo} 
                                toggleTodo={toggleTodo} 
                                editTodo={editTodo}
                                />;
                    })
                }
            </div>
       )
    }
}

export default TodoList;

2.3TodoItem.jsx

    在TodoItem.jsx文件中我们需要实现Item的删除,以及编辑, 删除无非就是点击删除按钮,触发父组件中的删除函数,这里我们重点介绍编辑。

image.png

首先,当我们点击编辑时,会出现一个新的输入框,然后隐藏原数据项

import { Component } from "react";
import './TodoItem.css'

class TodoItem extends Component{
    constructor(props){
        super(props);
        this.state = {
            isEditing:false,
            editText:this.props.todo.text
        }
    }
    handleEditChange = (event)=>{
        this.setState({          
            editText:event.target.value
        })
    }
    handleEditSave =()=>{
        this.props.editTodo(this.props.index,this.state.editText)
        this.setState({
            isEditing:false
        })
    }
    render(){
        const {todo,deleteTodo,toggleTodo,editTodo,index} = this.props;
        const {text,completed} = todo
        return(
            <li className={`todo-item ${completed ? 'todo-item--completed' : ''}`}>
                {
                   this.state.isEditing?(
                        <div>
                            <input
                                type="text"
                                value={this.state.editText}
                                onChange={this.handleEditChange}
                            />
                            <button onClick={this.handleEditSave}>保存</button>
                        </div>
                    ):(
                <div>
                    <span 
                        className="todo-item__text"
                        onClick={()=>toggleTodo(index)}
                    >
                    {todo.text}
                </span>
                <button className="todo-item__edit-btn" 
                onClick={()=>this.setState({isEditing:true})}>编辑</button>
                <button className="todo-item__delete-btn" 
                onClick={()=>deleteTodo(index)}>Delete</button>
                </div>
                )}
            </li>
        )
    }
}

export default TodoItem;

当我们点击

<button className="todo-item__edit-btn" 
                onClick={()=>this.setState({isEditing:true})}>编辑</button>

编辑按钮时,就会触发函数把this.state中的isEncoding的值修改成true,当isEncoding的值变成true时,三元运算就会执行

this.state.isEditing?(
                        <div>
                            <input
                                type="text"
                                value={this.state.editText}
                                onChange={this.handleEditChange}
                            />
                            <button onClick={this.handleEditSave}>保存</button>
                        </div>
                    ):(
                <div>
                    <span 
                        className="todo-item__text"
                        onClick={()=>toggleTodo(index)}
                    >
                    {todo.text}
                </span>
                <button className="todo-item__edit-btn" 
                onClick={()=>this.setState({isEditing:true})}>编辑</button>
                <button className="todo-item__delete-btn" 
                onClick={()=>deleteTodo(index)}>Delete</button>
                </div>
                )

切换成一个新的输入框。还有一个点是,当我们点击Item时,该Item就会被划掉

image.png 因为在 <li className={todo-item ${completed ? 'todo-item--completed' : ''}}> 上面动态绑定了一个类,当点击时,就会触发函数toggleTodo(index),把completed取反,就添加类了。

这就是我今天为大家带来的一个简陋的todoList,若有不足,恳请各位指出!!!!