利用 React 写一个 TodoList

JavaScript,前端 2018-01-16

写一个 TodoList 小应用

安装 create-react-app 模块

# -g 代表全局安装
npm install -g create-react-app

这个模块可以帮助我们更快的创建 React 应用。如果你的用了淘宝的 npm 镜像,安装过程大概要一两分钟吧。

创建一个 todolist 应用

在你想创建该应用的文件夹里,打开终端。我用的终端是 git bash 输入如下命令行:

# 我们会在终端当前路径下,创建一个文件夹 todolist 这就是一个 React 应用了。
create-react-app todolist
# 家里网络不好,还报错了,我创建了两次才成功。。。

启动该应用

# 进入创建的项目文件
cd todolist
# 启动成功后会打开浏览器访问 localhost:3000
npm start

删掉不必要的文件

其实我们需要的只是模块还会有配置文件,所以我们要把 srcpublic 文件夹里的文件都删了。

./public/index.html

public 文件夹里新建一个 index.html 文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Todo List</title>
</head>
<body>
    <div id="container">
        
    </div>
</body>
</html>

./src/index.css

src 文件夹里新建一个 index.css 文件。

body {
  padding: 50px;
  background-color: #66CCFF;
  font-family: sans-serif;
}
#container {
  display: flex;
  justify-content: center;
}

./src/index.js

src 文件夹里新建一个 index.js 文件。

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

let container = document.querySelector('#container')

ReactDOM.render(
    <div>
        <h1>hello react</h1>
    </div>,
    container
)

看下效果

# 在 todolist 目录下运行
npm start

创建一个 TodoList 组件

src 文件夹里新建一个 components 文件夹专门放组件用的,然后再在该文件夹下创建一个 TodoList.js 文件。

import React, { Component } from 'react'

class TodoList extends Component {
    render() {
        return (
            <div className="todoListMain">
                <div className="header">
                    <form>
                        <input placeholder="添加待办事项"></input>
                        <button type="submit">确定</button>
                    </form>
                </div>
            </div>
        )
    }
}

export default TodoList

然后我觉得 css 文件也用一个专门的文件夹来放置。所以我们需要修改 ./src/index.js 的部分代码:

// index.js
import React from 'react' // 因为暂时没用到组件类,所以把 Component 去掉了。
import ReactDOM from 'react-dom'

import './css/index.css'
import TodoList from "./components/TodoList";

let container = document.querySelector('#container')

ReactDOM.render(
    <div>
        <TodoList />
    </div>,
    container
)

这个时候我们还需要完成什么?嗯,我在分析需求。

  • 添加待办事件
  • 显示待办事件
  • 美化 UI 样式
  • 删除待办事件
  • 添加动画效果

添加待办事件

添加表单提交事件

添加 ReactonSubmit 事件

<form onSubmit={this.addItem}>

当触发 onSubmit 事件会执行 this.addItem 这个 JavaScript 表达式

添加组件的状态

利用 this.state 去存储添加的事件。在构造器里用先调用父类构造器 super() 才能使用 this ,这是 ES6 的语法。

constructor(props) {
    super(props)
    this.state = {
        items: []
    }
}
利用 ref 把提交的文本取到

因为在 React 里,不推荐直接操作 DOM 元素。所以我们要用 ref 。其实是如果通过 JSX 操作的话,比较困难。

<input ref={(a) => this._inputElement = a} placeholder="添加待办事项">
</input>

其实 refreferences (引用) 的缩写。也就是说上面:ref={(a) => this._inputElement = a} 这一句代码的意思是:我们把这个 input 元素的引用赋值给 this._inputElement 这个属性。因为 ref 指向的是一个回调函数,然后当 render() 组件被挂载后(when component is mounted),该回调函数就会立即被调用,该回调函数的参数(param)就是该 DOM 元素的引用。其实 ref 也可以赋值为 this.refs 的一个属性,但官方不推荐使用。

详情可以看:Accessing DOM Elements in React

然后写 this.addItem 这个方法
this.addItem = (e) => {
    let itemArray = this.state.items
    // 判断提交的文本框是否有内容,有的话就在 itemArray 数组里添加一个对象,该对象有两个键值对:text 和 key
    if (this._inputElement.value !== "") {
        itemArray.unshift({
            text: this._inputElement.value,
            key: Date.now()
        })
        // 更新状态
        this.setState({
            items: itemArray
        })
        // 把文本框的文本清空
        this._inputElement.value = ""
    }

    console.log(itemArray)
    // 组织表单提交的默认行为
    e.preventDefault()
}
试一下

打开控制台,在 input 框里输入内容,点击确定,这时候控制台会打印出一个数组。

显示待办事件

我们用一个列表去放这些待办事件,所以新建一个组件 TodoItems

components 文件夹下创建一个 TodoItems.js 文件。

import React, { Component } from 'react'

class TodoItems extends Component {
    
    constructor(props) {
        super(props)
        this.createTasks = this.createTasks.bind(this)
    }
    // 创建待办事件的方法
    createTasks(item) {
        return <li key={item.key}>{item.text}</li>
    }

    render() {
        // 拿到父组件传过来的待办事件数组,然后遍历该数组
        let todoEntries = this.props.entries,
            listItems = todoEntries.map(this.createTasks)
        // 把遍历到的待办事件渲染出来
        return (
            <ul className="theList">
                {listItems}
            </ul>
        )
    }
}
// 导出该组件
export default TodoItems
把 TodoItems 组件导入到 TodoList 组件

修改 ./src/components/TodoList.js 文件

// 导入 TodoItems 组件模块
import TodoItems from './TodoItems'

// 添加了 <TodoItems entries={this.state.items} />
    render() {
        return (
            <div className="todoListMain">
                <div className="header">
                    <form onSubmit={this.addItem}>
                        <input ref={(a) => this._inputElement = a}
                        placeholder="添加待办事项"></input>
                        <button type="submit">确定</button>
                    </form>
                </div>
                <TodoItems entries={this.state.items} />
            </div>
        )
    }

美化 UI 样式

新建 ./src/css/TodoList.css 文件,然后添加如下代码:

.todoListMain .header input {
  padding: 10px;
  font-size: 16px;
  border: 2px solid #FFF;
  width: 165px;
}
.todoListMain .header button {
  padding: 10px;
  font-size: 16px;
  margin: 10px;
  margin-right: 0px;
  background-color: #0066FF;
  color: #FFF;
  border: 2px solid #0066FF;
}
.todoListMain .header button:hover {
  background-color: #003399;
  border: 2px solid #003399;
  cursor: pointer;
}
.todoListMain .theList {
  list-style: none;
  padding-left: 0;
  width: 250px;
} 
.todoListMain .theList li {
  color: #333;
  background-color: rgba(255,255,255,.5);
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 5px;
}

然后添加到 TodoList 组件里,所以修改 ./src/components/TodoList.js 文件:

import './../css/TodoList.css'

删除待办事件

通过点击去删除待办事件

给待办事件添加点击事件 onClick 如下:

// TodoItems.js
createTasks(item) {
    return <li onClick={() => this.delete(item.key)}
    key={item.key}>{item.text}</li>
}
添加 delete 方法
// TodoItems.js
constructor(props) {
    super(props)
    this.createTasks = this.createTasks.bind(this)
    this.delete = this.delete.bind(this)
}

delete(key) {
    this.props.delete(key)
}
delete 方法调用的是父类 props 传过来的方法
// TodoList.js
<TodoItems entries={this.state.items} 
            delete={this.deleteItem}/>

constructor(props) {
    // 显式绑定 deleteItem 方法的 this 指向
    this.deleteItem = this.deleteItem.bind(this)
}
// deleteItem 方法的实现
deleteItem(key) {
    let filteredItems = this.state.items.filter(function (item) {
        return (item.key !== key)
    })

    this.setState({
        items: filteredItems
    })
}
再次修改样式
/* TodoList.css */
.todoListMain .theList li {
  color: #333;
  background-color: rgba(255,255,255,.5);
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 5px;
 
  transition: background-color .2s ease-out;
}
 
.todoListMain .theList li:hover {
  background-color: pink;
  cursor: pointer;
}

添加动画效果

// 导入该模块
import FlipMove from 'react-flip-move'
// 添加 <FlipMove /> 组件
return (
    <ul className="theList">
        <FlipMove duration={250} easing="ease-out">
            {listItems}
        </FlipMove>
    </ul>
)

注意: 如果没有该模块则要安装,使用命令行:npm i -S react-flip-move

参考资料


本文由 阿涛 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论