脚手架是用webpack搭建的,我们现在不用从零开始搭建了
xxx脚手架:用来帮助程序原快速创建一个基于xxx库的模板项目
react提供了一个用于创建react项目的脚手架库:create-react-app(基于React脚手架的项目,把项目的每个文件读懂,再把自己的业务逻辑加进去)
项目的整体技术架构为:react+webpack+es6+eslint
使用脚手架开发的项目的特点:模块化、组件化、工程化
有了这个库create-react-app
,才能创建出来脚手架
老版本的创建项目的步骤
全局安装: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
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>
一个细节
第一种情况:
//文件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可以用嵌套关系避免)
ES7+ React/Redux/React-Native snippets 作者:dsznajder
拆分组件:拆分界面,抽取组件
实现静态组件:使用组件实现静态页面效果
实现动态组件
动态显示初始化数据
数据类型
数据名称
保存在哪个组件?
交互(从绑定事件监听开始)
拆分静态组件和样式
App.jsx放子组件,App.css放公共样式,其余各自组件领走
关于todos数据放在哪里
目前我们只学了父给子传数据props,所以数据暂时放在共同的父亲App里面
父给子传数据
//App给List组件传
<List todos={this.state.todos} />
//List组件给Item组件传
//接收、解构
const { todos } = this.props
//传递的两种写法
//return
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其实挺坑的!!!
首先完成简单的全选按钮
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>
完整功能演示