React系统学习笔记--超基础--超详细--超简洁--React应用基于脚手架(三)

1 使用creat-react-app创建react项目

脚手架是用webpack搭建的,我们现在不用从零开始搭建了

xxx脚手架:用来帮助程序原快速创建一个基于xxx库的模板项目

  • 包含了所有需要的配置(语法检查,jsx编译,devServer…)
  • 下载好了所有相关的依赖
  • 可以直接运行一个简单效果

react提供了一个用于创建react项目的脚手架库:create-react-app(基于React脚手架的项目,把项目的每个文件读懂,再把自己的业务逻辑加进去)

项目的整体技术架构为:react+webpack+es6+eslint

使用脚手架开发的项目的特点:模块化、组件化、工程化

有了这个库create-react-app,才能创建出来脚手架

1.1 创建项目并启动

老版本的创建项目的步骤

全局安装:npm i -g create-react-app

切换到想创建项目的目录,使用命令:create-react-arr hello-react

进入项目文件夹:cd hello-react

启动项目:npm start

现在创建项目的步骤

npx create-react-app my-app
cd my-app
npm start

1.2 react脚手架项目结构

public ---- 静态资源文件夹

​ favicon.icon ------ 网站页签图标

index.html -------- 主页面

​ logo192.png ------- logo图

​ logo512.png ------- logo图

​ manifest.json ----- 应用加壳的配置文件

​ robots.txt -------- 爬虫协议文件

在这里插入图片描述

src ---- 源码文件夹

​ App.css -------- App组件的样式

App.js --------- App组件

​ App.test.js ---- 用于给App做测试

​ index.css ------ 样式

index.js ------- 入口文件(相当于main.js)

​ logo.svg ------- logo图

​ reportWebVitals.js

​ — 页面性能分析文件(需要web-vitals库的支持)

​ setupTests.js

​ ---- 组件单元测试的文件(需要jest-dom库的支持)

<!-- React.StrictMode帮我们检查App组件及其子组件里面的写法是否合理 -->
<React.StrictMode>
  <App />
</React.StrictMode>

在这里插入图片描述

1.3 Hello组件体验

一个细节

第一种情况:
//文件module.js中定义
const React = {
    
    a:1, b:2}
React.Component = class Component {
    
    }
export default React
//使用
import React from './module.js'
const {
    
    Component} = React//这叫从React身上解构
第二种情况:
const React = {
    
    a:1, b:2}
export class Component {
    
    }//使用分别暴露,暴露Component
export default React//使用默认暴露,暴露React
//使用
import React,{
    
    Component} from './module.js'
//注意这不叫解构!!!这就是分别暴露之后的与引入,说明module文件里面使用了多种暴露形式

区分组件和普通函数文件

第一种:都是js结尾时,看首字母,组件首字母大写

第二种:将组件文件的后缀写成.jsx,react里面引入文件有两种后缀可以省略:js和jsx

优化点:引入文件时路径名写的太长,可以在文件夹下定义index.jsx,这样写在引入时可以省略文件名,少写一层
在这里插入图片描述

入口文件index.js

//React18不推荐这样写,会有警告,新写法可以参考React18
//引入react核心库
import React from "react";
//引入ReactDOM
import ReactDOM from "react-dom";
import App from './App'

//渲染App组件到页面
ReactDOM.render(<App />, document.getElementById('root'))

在这里插入图片描述

一个注意点:因为子组件最终都在App组件中引入使用,如果子组件样式命名冲突会发生覆盖,所以有时候需要做样式模块化(less可以用嵌套关系避免)
在这里插入图片描述

2 VScode中的React插件

ES7+ React/Redux/React-Native snippets 作者:dsznajder

3、功能界面的组件化编码流程

拆分组件:拆分界面,抽取组件

实现静态组件:使用组件实现静态页面效果

实现动态组件

​ 动态显示初始化数据

​ 数据类型

​ 数据名称

​ 保存在哪个组件?

​ 交互(从绑定事件监听开始)

4 TodoList案例

拆分静态组件和样式

App.jsx放子组件,App.css放公共样式,其余各自组件领走

关于todos数据放在哪里

目前我们只学了父给子传数据props,所以数据暂时放在共同的父亲App里面

父给子传数据

//App给List组件传
<List todos={
    
    this.state.todos} />

//List组件给Item组件传
//接收、解构
const {
    
     todos } = this.props
//传递的两种写法
//return <Item key={todo.id} id={todo.id} name={todo.name} done={todo.done} />
return <Item key={
    
    todo.id} {
    
    ...todo} />

//Item组件同样接收、解构、展示即可

新增todo:Header中的输入要引起App组件中状态的变化(子给父传递)

App组件给Header子组件传递过去一个函数,函数回调写在App中,子组件中的数据就可以以参数的形式返回到父组件App里

App.jsx

//用于添加一个todo,接收一个todo对象
addTodo = (todoObj) => {
    
    
    //获取原todos
    const {
    
     todos } = this.state
    //在数组前方追加一个todo
    const newTodos = [todoObj, ...todos]
    //更新状态
    this.setState({
    
     todos: newTodos })
}

<Header addTodo={
    
    this.addTodo} />

Header.jsx

//绑定回车事件
<input onKeyUp={
    
    this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />

//绑定事件的元素和要操作的元素相同,可以不用ref
handleKeyUp = (event) => {
    
    
    //回车键keycode是13,现在不推荐使用keyCode了,event.keyCode !== 13
    if (event.key !== 'Enter') return
    //添加的todo名字不能为空
    if (event.target.value.trim() === '') {
    
    
        alert('输入不能为空')
        return
    }
    //准备好新增的todoObj
    const todoObj = {
    
     id: nanoid(), name: event.target.value, done: false }
    //将todoObj传递给App(子给父)
    this.props.addTodo(todoObj)
    //清空输入框
    event.target.value = ''
}

在这里插入图片描述

鼠标移入移出效果

Item.jsx

state = {
    
     mouse: false }

//鼠标移入移出的回调,高阶函数写法
handleMouse = (flag) => {
    
    
    //注意:回调中函数可以写小括号,但是无论什么情况都要给事件一个回调函数,返回值是函数耶ok
    return () => {
    
    
        this.setState({
    
     mouse: flag })
    }
}

<li style={
    
    {
    
     backgroundColor: this.state.mouse ? '#95b196' : 'white' }} onMouseEnter={
    
    this.handleMouse(true)} onMouseLeave={
    
    this.handleMouse(false)}>
	...
	<button className="btn btn-danger" style={
    
    {
    
     display: this.state.mouse ? 'block' : 'none' }}>删除</button>
</li>

在这里插入图片描述

todo勾选,重点在于改变state

在Item子组件中拿到要操作项的id和勾选状态—通知App更新state数据(孙给父----一层一层传)

App.jsx

//更新todo对象---接收Item传过来的id和勾选
updateTodo = (id, done) => {
    
    
    //获取原状态的todos
    const {
    
     todos } = this.state
    //匹配处理数据
    const newTodos = todos.map((todoObj) => {
    
    
        //解构,然后相同的覆盖
        if (todoObj.id === id) return {
    
     ...todoObj, done: done }
        else return todoObj
    })
    //更新状态
    this.setState({
    
     todos: newTodos })
}
//给子组件List传递一个函数
<List todos={
    
    this.state.todos} updateTodo={
    
    this.updateTodo} />

List.jsx

//自己不用,反手就交给自己的子组件Item
const {
    
     todos, updateTodo } = this.props
return <Item key={
    
    todo.id} {
    
    ...todo} updateTodo={
    
    updateTodo} />

Item.jsx

//给勾选框绑定onChange事件,传入操作的id
<input type="checkbox" defaultChecked={
    
    done} onChange={
    
    this.handleCheck(id)} />

//勾选、取消勾选的回调
handleCheck = (id) => {
    
    
    //给input绑定,拿input的值,不需要ref,借助event即可
    //孙子组件给App传递数据
    return (event) => {
    
    
        this.props.updateTodo(id, event.target.checked)
    }
}

小结:状态在哪里操作状态的方法就在哪里
在这里插入图片描述

对props传递进行限制

App给Header和List传递了数据,所以在Header和List组件中对接收的数据进行限制

Header.jsx及List.jsx

//引入
import PropTypes from 'prop-types'

//对接收的props进行:类型、必要性限制
static propTypes = {
    
    
    addTodo: PropTypes.func.isRequired
}
    
static propTypes = {
    
    
    todos: PropTypes.array.isRequired,
    updateTodo: PropTypes.func.isRequired
}

删除某个todo

App.jsx

//删除一个todo
deleteTodo = (id) => {
    
    
    //获取原状态的todos
    const {
    
     todos } = this.state
    //删除指定id的todo对象
    const newTodos = todos.filter((todoObj) => {
    
    
        return todoObj.id !== id
    })
    //更新状态
    this.setState({
    
     todos: newTodos })
}
    
//准备好的函数给孙子组件传过去,一层一层传,借助子组件List
<List todos={
    
    this.state.todos} updateTodo={
    
    this.updateTodo} deleteTodo={
    
    this.deleteTodo} />

List.jsx

//反手交给Item,可以先解构
return <Item key={
    
    todo.id} {
    
    ...todo} updateTodo={
    
    updateTodo} deleteTodo={
    
    deleteTodo} />

Item.jsx

//绑定事件
<button onClick={
    
    () => this.handleDelete(id)} className="btn btn-danger" style={
    
    {
    
     display: this.state.mouse ? 'block' : 'none' }}>删除</button>

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

在这里插入图片描述

底部Footer

defaultChecked只能在第一次的时候生效,之后修改没有反应defaultChecked={doneCount === total ? true : false}

defaultChecked其实挺坑的!!!

checked会报错,需要onChange
在这里插入图片描述

<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total ? true : false} />

首先完成简单的全选按钮

App.jsx

//全选
checkAll = (done) => {
    
    
    //获取原状态的todos
    const {
    
     todos } = this.state
    //加工数据
    const newTodos = todos.map((todoObj) => {
    
    
        return {
    
     ...todoObj, done: done }
    })
    //更新状态
    this.setState({
    
     todos: newTodos })
}
//传过去
<Footer todos={
    
    this.state.todos} checkAll={
    
    this.checkAll} />

Footer.jsx

//已完成个数--条件统计
const doneCount = todos.reduce((pre, todo) => {
    
     return pre + (todo.done ? 1 : 0) }, 0)
//总数
const total = todos.length
//展示数据
<span>已完成{
    
    doneCount}</span> / 全部{
    
    total}


//写成defaultChecked会有bug(仅在第一次可以),写成checked会警告,所以写成回调形式
//全选checkbox的回调
handleCheckAll = (event) => {
    
    
    this.props.checkAll(event.target.checked)
}
    
<input type="checkbox" onChange={
    
    this.handleCheckAll} checked={
    
    doneCount === total && total !== 0 ? true : false} />

然后写清除已完成按钮功能

App.jsx

//清除已完成的
clearChecked = () => {
    
    
    //获取原状态的todos
    const {
    
     todos } = this.state
    const newTodos = todos.filter((todoObj) => {
    
    
        return todoObj.done === false
    })
    //更新状态
    this.setState({
    
     todos: newTodos })
}
//传过去
<Footer todos={
    
    this.state.todos} checkAll={
    
    this.checkAll} clearChecked={
    
    this.clearChecked} />

Footer.jsx

//清除已完成
handleClearChecked = () => {
    
    
    this.props.clearChecked()
}

<button onClick={
    
    this.handleClearChecked} className="btn btn-danger">清除已完成任务</button>

完整功能演示

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_55644132/article/details/127720177