React 学习笔记(19)React 脚手架组件使用,上手简单项目

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

前言

做一个TodoList项目就是一个代办列表,网上发现好多这样的案例。代办事项的添加,删除,标记完成的功能。首先拆分组件,今天就写成一个完整的流程文档,从零开始吧

拆分组件&分析

分为Footer,Header,Item,List四个组件。现在没有做组件间的数据传递,把所有的数据操作都放在src/App.jsx里面,注意现在我们在src文件夹下编码的时候都采用jsx来写。

image.png

  • 每一个事件都有一个ID,名字,是否完成的状态
  • Header组件至少有一个输入框,回车自动插入数据,数据不能为空
  • Item组件循环遍历数组,并且每一个元素有一个删除按钮
  • Footer组件收集数组个数,完成状态的个数

App.js

import React, { Component } from 'react'
import Header from './components/Header/index'
import List from './components/List/index'
import Footer from './components/Footer/index'
import './App.css'

export default class App extends Component {
	state = {todos:[]}  //初始化状态

	//增加列表的元素,传递一个并追加到数组中并重新渲染页面
	addTodo = (todoObj)=>{
		const {todos} = this.state
		const newTodos = [todoObj,...todos]
		this.setState({todos:newTodos})
	}

	//传ID去删除数组对象
	deleteTodo = (id)=>{
		//获取原来的todos
		const {todos} = this.state
		//删除指定id的todo对象
		const newTodos = todos.filter((todoObj)=>{
			return todoObj.id !== id
		})
		//更新状态
		this.setState({todos:newTodos})
	}
        
	//清除所有已完成的
	clearAllDone = ()=>{
		const {todos} = this.state
		const newTodos = todos.filter((todoObj)=>{
			return !todoObj.done
		})

		this.setState({todos:newTodos})
	}

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

复制代码
  • 初始化state为一个空数组
  • JavaScript 方法中使用filter() 方法创建一个新数组,也叫数组过滤器, 其包含通过所提供函数实现的测试的所有元素,把测试通过的元素放在新数组里返回,不通过的丢弃。
  • App里面的组件包含了整个项目的所有子组件。HeaderListFooter为同级组件。通过import引入,在import关键字引入的时候先引入第三方组件再引入内置组件

有两层div标签包裹,其CSS文件App.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;
  }
  
复制代码

复习一下之前之前没用过,或者印象不深的CSS,

  • vertical-align:设置元素的垂直对齐方式

  • cursor:规定了光标显示的形状 (悬浮在按钮上就变了)

  • box-shadow:实现图层阴影效果,有6个可设置属性,从左到右依次是:阴影类型 X轴位移 Y轴位移 阴影大小 阴影扩展 阴影颜色。inset为内阴影

  • :hover :选择获得焦点

  • :focus :选择鼠标悬停

    扫描二维码关注公众号,回复: 14263276 查看本文章

Header

index.jsx:

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)=>{
		//键盘单击事件event解构赋值
		const {keyCode,target} = event
		//回车按键KeyCode为13
		if(keyCode !== 13) return
		//添加的todo名字不能为空
		if(target.value.trim() === ''){
			alert('输入不能为空')
			return
		}
		const todoObj = {id:nanoid(),name:target.value,done:false}
		//将新增的数据集给传递给父组件
		this.props.addTodo()
		//清空输入框数据
		target.value = ''
	}

	render() {
		return (
			<div className="todo-header">
				<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
			</div>
		)
	}
}
复制代码
  • 这里需要安装一个nanoid库,用来生成唯一不重复ID,按理来说这里应该交给后端处理,然后端插入到数据库返回自增ID。

  • 子组件只能通过 props 来传递数据

index.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);
}
复制代码

又出现了 outline: none;,今天测试一下效果: 使用了 outline: none;点击输入框 image.png 没有使用了 outline: none;点击输入框,box-shadow的蓝色阴影就无效了 image.png

Item

index.jsx:

import React, { Component } from 'react'
import './index.css'

export default class Item extends Component {

	state = {mouse_state:false}

	//鼠标移入、移出的回调
	handleMouse = (flag)=>{
		return ()=>{
			this.setState({mouse_state:flag})
		}
	}

	//勾选、取消勾选某一个todo的回调
	handleCheck = (id)=>{
		return (event)=>{
			this.props.updateTodo(id,event.target.checked)
		}
	}

	//删除一个todo的回调
	handleDelete = (id)=>{
		if(window.confirm('确定删除吗?')){
			this.props.deleteTodo(id)
		}
	}

	render() {
		const {id,name,done} = this.props
		const {mouse_state} = this.state
		return (
			<li style={{backgroundColor:mouse_state ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
				<label>
					<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
					<span>{name}</span>
				</label>
				<button onClick={()=> this.handleDelete(id) } className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button>
			</li>
		)
	}
}
复制代码

这里有一个关键的效果,就是悬浮列表li标签的时候显示删除按钮。使用了两个事件:onMouseEnteronMouseLeave,使用了同一个回调函数:handleMouse,传了不同的函数做了不一样的效果展示。

index.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;
}
复制代码
  • list-style:值为none示设置列表项标记的类型为空,即列表项前无标记,默认标记是实心圆,非常丑。

  • content:页面中的内容插入,initial:将此属性设置为其默认值

  • border-bottom: none: 表示最后一个li元素去掉下边框,会和ul下边框重复。

image.png

List

index.jsx:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'

export default class List extends Component {

	//对接收的props进行:类型、必要性的限制
	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 key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
					})
				}
			</ul>
		)
	}
}
复制代码

主要是做一个列表外层容器,在组件使用了map方法,并且对其函数的参数进行限制。没有特别的功能 index.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;
}
复制代码

效果&总结

代码写完了,看看效果:

222.gif

主要是拆分组件的思想还有组件间的数据传递,数据操作,还了解到了一些CSS细节,还需要继续锻炼学习,感觉自己练习太少了,总结太少了,好多不用不实际操作都理解什么意思。

猜你喜欢

转载自juejin.im/post/7108207588101062670