使用 react 和 react-hooks 编写 TODOList

在这里插入图片描述

运行
npx create-react-app todolist
等待安装
npm start
开启3000端口
修改目录

在这里插入图片描述

index.js

入口文件

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList.js';
// import App from './App.js';

ReactDOM.render(
  <React.StrictMode>
    <TodoList />
  </React.StrictMode>,
  document.getElementById('root')
);

TodoList.js
import React, { Component, Fragment } from 'react';
import TodoItem from './TodoItem';
import axios from 'axios'
import './style.css';

class TodoList extends Component {

  // constructor 初始化的时候会被调用的生命周期函数钩子
  constructor(props) {
    super(props);
    // 当组件的state或者props里面的数据发生变换的时候,render函数就会重新执行
    
    // 存放数据的地方
    this.state = {
      inputValue: '',
      list: []
    }
    // 改变函数中 this 的指向。不然使用的时候,函数中 this 的指向是 undefined
    // 在 constructor 中改变是出于性能方面的考虑
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleBtnClick = this.handleBtnClick.bind(this)
    this.handleItemDelete = this.handleItemDelete.bind(this)
  }

  // 组件即将被挂载到页面的时刻即将执行
  componentWillMount() {
    // console.log('componentWillMount')
  }

  render() {
    // 当父组件的render函数执行的时候。子组件的render组件都将被重新运行
    console.log('parent render')
    return (
    	// react 渲染的时候,只允许有一个根节点,所以可以使用 Fragment,这样渲染到界面的时候,不会产生对于的标签
      <Fragment>
        <div>
          // 支持渲染 HTML 标签
          <label htmlFor="insertArea">输入内容</label>
          // class 在 react 中是代表一个类,所以使用 className 来代替
          // value={this.state.inputValue} 花括号里面的是变量,constructor 中的 state 的变量
          // onChange={this.handleInputChange} 绑定方法 handleInputChange
          // ref={(input) => { this.input = input }} react 是虚拟 DOM, 但是有时候需要操作 DOM
          <input
            id="insertArea"
            className="input"
            value={this.state.inputValue}
            onChange={this.handleInputChange}
            ref={(input) => { this.input = input }}
          />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul ref={(ul) => this.ul = ul}>
          {this.getTodoItem()}
        </ul>
      </Fragment>
    )
  }

  // 页面被挂载之后
  // componentDidMount() {
  //   // console.log('componentDidMount')
  // }

  // 请求数据
  componentDidMount() {
    // console.log('componentDidMount')
    axios.get('/api/todoList').then((res) => {
      console.log(res)
      this.setState(() => {
        return {
          list: [...res.data]
        }
      })
    })
  }

  // 只会在页面挂载的时候被调用,之后数据更新也不会被调用

  // 组件被更新之前,会自动执行
  // 询问组件是否需要更新,true 需要更新, false 不需要更新
  shouldComponentUpdate() {
    // console.log('shouldComponentUpdate')
    return true
  }

  // 组件被更新之前,会自动执行, shouldComponentUpdate 之后执行,
  // 是否执行,取决于 shouldComponentUpdate 的值, true 执行, false 不执行
  componentWillUpdate() {
    // console.log('componentWillUpdate')
  }

  componentDidUpdate() {
    // console.log('componentDidUpdate')
  }

  // 循环渲染数据
  getTodoItem() {
    return this.state.list.map((item, index) => {
      return (
      // TodoItem 自定义组件
        <TodoItem
          key={index}
          content={item}
          index={index}
          deleteItem={this.handleItemDelete}
        />
      )
    })
  }

  handleInputChange(e) {
    console.log(e.target.value)
    // const value = e.target.value
    
    // react 不允许直接改变值,需要借助一个变量存储
    const value = this.input.value
    // 改变 state 里面的值
    this.setState(() => {
      return {
        inputValue: value
      }
    })
  }

  handleBtnClick() {
  // prevState 改变之前的数据
    this.setState((prevState) => {
      return {
        //  将之前未改变的数据,与添加的数值整合到一个数组之中
        list: [...prevState.list, prevState.inputValue],
        inputValue: ''
      }
    },
      () => {
        console.log(this.ul.querySelectorAll('div').length)
      }
    )
  }

  handleItemDelete(index) {
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1)
      return {
        list
      }
    })
  }
}

export default TodoList
TodoItem.js
import React, { Component } from 'react';
import PropsTypes from 'prop-types'

class TodoItem extends Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  render() {
    console.log('child render')
    // TodoItem 是 TodoList 的子组件,接收了来自父组件的 content 的传值
    const { content } = this.props
    return (
      <div onClick={this.handleClick}>
        {content}
      </div>
    )
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.content !== this.props.content) {
      return true;
    } else {
      return false;
    }
  }

  // 1.一个组件要从父组件接受参数
  // 2.如果这个组件第一次存在于父组件中,不会被执行
  // 3.如果这个组件已经存在于父组件中,才会执行
  componentWillReceiveProps() {
    // console.log('child componentWillReceiveProps')
  }

  // 组件即将从页面中移除的时候会被执行
  componentWillUnmount() {
    // console.log('child componentWillUnmount')
  }

  handleClick() {
    const { deleteItem, index } = this.props
    deleteItem(index)
  }
}

// 对父组件传入的数据做校验
TodoItem.propsTypes = {
  // 如果父组件没有传入参数,校验不会生效,此时也不会报错,
  // isRequired 规定该参数必须传
  // test: PropsTypes.string.isRequired,
  // content 的类型可以是其中的一个
  content: PropsTypes.oneOfType([PropsTypes.number, PropsTypes.string]),
  deleteItem: PropsTypes.func,
  index: PropsTypes.number
}

TodoItem.defaultProps = {
  test: 'hello world'
}

export default TodoItem;
对 TodoList.js 做动画处理
import React, { Component, Fragment } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import TodoItem from './TodoItem';
import './style.css';

class TodoList extends Component {

  constructor(props) {
    .....
  }
....
  render() {
    .....
  }

......
  getTodoItem() {
    return (
      <Fragment>
        <TransitionGroup> {
        // 一组动画
          this.state.list.map((item, index) => {
            return (
            // 给每一个加上动画
              <CSSTransition
              // 时间 1s
                timeout={1000}
                // 动画的名字
                classNames="fade"
                unmountOnExit
                onEntered={(el) => { el.style.color = 'blue' }}
                // 初始化的时候也添加动画
                appear={true}
              >
                <TodoItem
                  key={index}
                  content={item}
                  index={index}
                  deleteItem={this.handleItemDelete}
                />
              </CSSTransition>
            )
          })
        }
        </TransitionGroup>
      </Fragment>
    )
  }
.....
export default TodoList
style.css
// 出场动画
.fade-enter, .fade-appear {
    opacity: 0;
}

.fade-enter-active, .fade-appear-active {
    opacity: 1;
    transition: opacity 1s ease-in;
}

.fade-enter-done {
    opacity: 1;
    /* color: aquamarine; */
}
// 退出动画
.fade-exit {
    opacity: 1;
}

.fade-exit-active {
    opacity: 0;
    transition: opacity 1s ease-out;
}

.fade-exit-done {
    opacity: 0;
}

使用 react-hooks 来改写 todoList

还是使用 刚刚的项目结构,将 App.js 改为 App.jsx ,index.js 里面的引入做出相同的修改

import React, { useEffect, useRef, useState, memo, useMemo, useCallback } from 'react';
import './App.css'

// 使用时间戳作为 id
let idSeq = Date.now()

function Control(props) {
  const { addTodo } = props
  const inputRef = useRef()

  const onSubmit = (e) => {
    e.preventDefault();
    const newText = inputRef.current.value.trim()

    if (newText.length === 0) {
      return;
    }

    addTodo({
      id: ++idSeq,
      text: newText,
      complete: false
    })

    inputRef.current.value = ''
  }

  return (
    <div className="control">
      <h1>TODOS</h1>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          className="new-todo"
          placeholder="todos"
          ref={inputRef}
        />
      </form>
    </div>
  )
}

function TodoItem(props) {
  const {
    todo: {
      id,
      text,
      complete
    },
    toggleTodo,
    removeTodo
  } = props

  const onChange = () => {
    toggleTodo(id)
  }

  const onRemove = () => {
    removeTodo(id)
  }

  return (
    <li className="todo-item">
      <input
        type="checkbox"
        onChange={onChange}
        checked={complete}
      />
      <label className={complete ? 'complete' : ''}>{text}</label>
      <button onClick={onRemove}>&#xd7;</button>
    </li>
  )
}

function Todos(props) {
  const { todos, toggleTodo, removeTodo } = props;
  return (
    <ul>
      {
        todos.map(todo => {
          return (
            <TodoItem
              key={todo.id}
              todo={todo}
              toggleTodo={toggleTodo}
              removeTodo={removeTodo}
            />
          )
        })
      }
    </ul>
  )
}

const KEY = 'todo'

function TodoList() {

	// 定义 todos 状态 
  const [todos, setTodos] = useState([]);
	
  // addTodo 方法
  const addTodo = (todo) => {
    setTodos(todos => [...todos, todo])
  }

 // remove 方法
  const removeTodo = useCallback((id) => {
    setTodos(todos => todos.filter(todo => {
      return todo.id !== id
    }))
  }, [])

  const toggleTodo = useCallback((id) => {
    setTodos(todos => todos.map(todo => {
      return todo.id === id ? {
        ...todo,
        complete: !todo.complete
      } : todo
    }))
  }, [])

  useEffect(() => {
    const todos = JSON.parse(localStorage.getItem(KEY))
    setTodos(todos)
  }, [])

  useEffect(() => {
    localStorage.setItem(KEY, JSON.stringify(todos))
  }, [todos])

  return (
    <div className="todo-list">
      <Control addTodo={addTodo} />
      <Todos removeTodo={removeTodo} toggleTodo={toggleTodo} todos={todos} />
    </div>
  )
}

export default TodoList;

App.css

.todo-list {
  width: 550px;
  margin: 130px auto;
  background: #ccc;
  box-shadow: 0 0 10px rgba(0, 0, 0, .5);
}

.control h1 {
  width: 100%;
  font-size: 100px;
  text-align: center;
  margin: 0;
  color: rgba(175, 47, 47, 0.15);
}

.control .new-todo {
  padding: 16px 16px 16px 60px;
  border: 0;
  outline: none;
  font-size: 24px;
  box-sizing: border-box;
  width: 100%;
  line-height: 1.4rem;
  box-shadow: rgba(73, 59, 59, 0.15);
}

.todos {
  margin: 0;
  padding: 0;
  list-style: none;
}

.todo-item {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 24px;
  display: flex;
  align-items: center;
}

.todo-item input {
  display: block;
  width: 20px;
  height: 20px;
  margin: 0 20px;
}

.todo-item label {
  flex: 1;
  padding: 15px 15px 15px 0;
  line-height: 1.2;
  display: block;
}

.todo-item label.complete {
  text-decoration: line-through;
}

.todo-item button {
  border: 0;
  outline: 0;
  display: block;
  width: 40px;
  text-align: center;
  font-size: 24px;
  color: red;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值