【react】react初学6-第一个react应用-待办事项

文章目录


前言

最近跟敲了一个待办事项的应用,强烈建议零基础的朋友也跟着敲一下,案例很初级,不需要搭建后台,但知识点非常全面,包含数据的增删改查、组件之间数据传递、样式等。最终结果如下图所示:

在跟敲的同时,注意感受以下几点:

  1. 拆分组件、实现静态组件,注意:className、style的写法
    2.动态初始化实例列表,如何确定将数据放在哪个组件的state中?
    单个组件使用:放在自身的state中
    某些组件使用:放在他们共同的父组件
    3.关于父子组件之间的通信:
    父组件 给 子组件传递数据,通过props传递
    子组件 给 父组件传递数据:通过props,要求父提前给子传递一个函数
    4.注意defaultChecked和checked 的区别,类似的还有defaultValue 和 value
    5.状态在哪里,操作状态的方法就在哪里

一、项目搭建

控制台创建react项目:create-react-app XX

启动项目:npm start

需要额外下载的库:

npm i nanoid

npm i prop-types

根据原型图拆分成四个组件,项目结构如下:

二、入口文件

index.js

javascript 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import { createRoot } from "react-dom/client";

const root = createRoot(document.getElementById("root"));
root.render(<App />)

index.css

css 复制代码
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

app.jsx

javascript 复制代码
import React, { Component } from 'react'
import './App.css'
import Header from './components/Header'
import Footer from './components/Footer'
import List from './components/List'

// 需要安装的库 npm i nanoid ; npm i prop-types
export default class App extends Component {
  // 状态在哪里,操作状态的方法就在哪个组件中

  state={todos:[
    {id:'001',name:'eating',done:true},
    {id:'002',name:'sleeping',done:true},
    {id:'003',name:'codeing',done:false},
  ]}

  addTodo=(todoObj)=>{
    const {todos}=this.state
    const newTodos=[todoObj,...todos]
    this.setState({todos:newTodos})
  }

  // 用于更新一个todo对象
  updateTodo=(id,done)=>{
    const {todos}=this.state
    
    const newTodos=todos.map((todoObj)=>{
      if(todoObj.id===id)return {...todoObj,done}
      else return todoObj
    })
    this.setState({todos:newTodos})
  }

  deleteTodo=(id)=>{
    const {todos}= this.state
    const newTodos=todos.filter((todoObj)=>{
      return todoObj.id!==id
    })
    this.setState({todos:newTodos})
  }

  // 用于全选
  checkAllTodo=(done)=>{
    const {todos}=this.state
    const newTodos=todos.map((todoObj)=>{
      return {...todoObj,done}
    })
    this.setState({todos:newTodos})
  }
  // 用于清除所有已完成
  clearAllDone=()=>{
     const {todos}=this.state
    //  const newTodos=todos.filter((todoObj)=>{
    //   return todoObj.done===false
    //  })

    const newTodos=todos.filter((todoObj)=>{return !todoObj.done})
     this.setState({todos:newTodos})
  }

  render() {

    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header addTodo={this.addTodo}/>
          <List todos={this.state.todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
          <Footer todos={this.state.todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />
        </div>
      </div>
    )
  }
}

app.css

css 复制代码
/*base*/
body {
  background: #fff;
}

.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

Header组件

回车添加一条信息,考虑了输入为空的情况

index.jsx

javascript 复制代码
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'

export default class Header extends Component {
    static propTypes={
        addTodo:PropTypes.func.isRequired//函数、必传
    }
    handleKeyUp=(event)=>{
            const {keyCode,target}=event
            // 判断是否是回车
            if(keyCode!=13)return
            if(target.value.trim()===''){
                alert('输入不应为空')
                return
            }
            const todoObj={id:nanoid(),name:target.value,done:false}

            this.props.addTodo(todoObj)

            // 清空输入
            target.value=''
        }
    handleKeyUp1=(event)=>{
        const {keyCode,target}=event
        if(keyCode!==13) return
        if(target.value.trim()===''){
            alert('input shuld not empty')
            return
        }
        const todoObj={id:nanoid(),name:target.value,done:false}
        this.props.addTodo(todoObj)
        target.value=''
    }
    render() {
        return (
            <div className="todo-header">
                {/* onkeydown onKeyUp */}
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
                
            </div>
        )
    }
}

生成唯一的id 有两个库可以选择,uuid(大) ,nanoid(轻量的库),这里选择了较轻量的nanoid

index.css

css 复制代码
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

List组件

index.jsx

javascript 复制代码
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
export default class List extends Component {
        static propTypes={
            todos:PropTypes.array.isRequired,
            updateTodo:PropTypes.func.isRequired,//函数、必传
            deleteTodo:PropTypes.func.isRequired,
        }

    render() {
        const {todos,updateTodo,deleteTodo}=this.props
        return (
            <ul className="todo-main">
                {todos.map((todo)=>{return <Item {...todo} key={todo.id} updateTodo={updateTodo} deleteTodo={deleteTodo} />})}
            </ul>
        )
    }
}

css

css 复制代码
/*main*/
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}

Item组件

javascript 复制代码
import React, { Component } from 'react'
import './index.css'
export default class Item
 extends Component {
  state={mouse:false}
  handleMouse=(flag)=>{
    return ()=>{
      this.setState({mouse:flag})
    }
  }
  // 勾选、取消勾选某一个todo
  handleCheck=(id)=>{
    return (event)=>{
      // console.log(id,event.target.checked)
      this.props.updateTodo(id,event.target.checked)
    }
  }
  handleDelete=(id)=>{
    if(window.confirm('sure delete')){
      this.props.deleteTodo(id)
    }

  }

  render() {
    const {id,name,done}=this.props
    const {mouse}=this.state    
    return (
      // onXXX事件绑定的函数如果写括号,会立即执行,将函数写成高阶形式,即返回一个匿名函数可避免这种情况
        <li style={{backgroundColor:mouse?'#ddd':'white'}} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
          <label>
            {/* checked:需要用onchange改 defaultChecked:后期可改 defaultChecked只管用一次? */}
            <input type="checkbox" checked={done} onChange={this.handleCheck(id)} />
            <span>{name}</span>            
          </label>
          <button className="btn btn-danger" style={{display:mouse?'block':'none'}} onClick={()=>{this.handleDelete(id)}}>删除</button>
        </li>
    )
  }
}
css 复制代码
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}

Footer组件

react checked属性会报错:

index.jsx:10 You provided a checked prop to a form field without an onChange handler.

This will render a read-only field. If the field should be mutable use defaultChecked.

Otherwise, set either onChange or readOnly.

解决 添加onchange事件或用defaultChecked

defaultChecked只在第一次指定时有作用

javascript 复制代码
import React, { Component } from 'react'
import './index.css'
export default class Footer extends Component {
  handleCheckAll=(event)=>{
    this.props.checkAllTodo(event.target.checked)
  }
  // 清除所有已完成的
  handleClearAllDone=()=>{
    this.props.clearAllDone()
  }
  render() {
        // 已完成的个数
    const doneCount=this.props.todos.reduce((pre,todo)=>pre+(todo.done?1:0) ,0)
    const total=this.props.todos.length
    return (
            <div className="todo-footer">
              <label>
                {/* 
                */}
                <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total!==0?true:false}/>
              </label>
              <span>
                <span>已完成{doneCount}</span> / 全部{total}
              </span>
              <button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
            </div>
    )
  }
}
css 复制代码
/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}

总结

例子很简单,但非常的巧妙,值得反复复习。

相关推荐
岁月向前4 小时前
iOS基础问题整理
前端
陈随易4 小时前
改变世界的编程语言MoonBit:配置系统介绍(下)
前端·后端·程序员
jump6804 小时前
【react】 useReducer 集中式管理组件的状态
前端
许泽宇的技术分享4 小时前
把 CLI 搬上 Web:在内网打造“可二开”的 AI IDE,为什么这条路更现实?
前端·ide·人工智能
jump6804 小时前
【react】useState是什么,怎么用
前端
进击的夸父5 小时前
前端合并的表格排序功能
前端
低保和光头哪个先来5 小时前
如何实现弹窗的 双击关闭 & 拖动 & 图层优先级
前端·javascript·css·vue.js
必然秃头5 小时前
前端面试题总结
前端
布列瑟农的星空6 小时前
近一年前端招人面试感悟
前端·面试