目录
前言
上一篇:React面向组件编程(下)
本篇内容:React应用(基于react脚手架)
一、初始化react脚手架
1.react脚手架:
1.xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
a.包含了所有需要的配置(语法检查、jsx编译、devServer…)
b.下载好了所有相关的依赖
c.可以直接运行一个简单效果
2.react提供了一个用于创建react项目的脚手架库: create-react-app
3.项目的整体技术架构为: react + webpack + es6 + eslint
4.使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
2.创建项目并启动
- 全局安装:npm i -g create-react-app
- 切换到你想创建项目的目录下,使用命令:create-react-app xxx(项目名,比如,hello-react)
- 进入项目文件夹:cd hello-react
- 启动项目:yarn start或npm start
注:若create-react-app安装失败,可以尝试更换npm源,更换成淘宝的,参考
安装成功后:
启动项目:
会让你选择用哪个浏览器打开,成功的页面(即成功运行了react官方给的模板项目):
二、脚手架文件介绍
1.public
public文件一般存静态资源,比如页面、样式、公共的图片
以后写项目时,只有一个.html文件
,也就是单页面应用(SPA)
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
2.src
App.js:定义了一个组件App
App.css:组件App的样式文件
App.test.js:测试App的,我们几乎不用
index.css:通用的样式
index.js:入口文件
logo.svg:logo图片
reportWebVitals.js:记录页面性能,里面用了web-vitals库
aetupTest.js:用于组件测试,里面用了第三方库web-vitals
三、一个简单的Hello组件
后缀同为.js
,区分组件文件和普通js文件:
1.组件文件首字母大写,普通js文件首字母小写
2.把组件文件后缀改为.jsx
上图中的写法在App.js
中引入组件时略显臃肿,下图中是另一种写法:
四、样式的模块化
当两个组件样式中有相同的className时,后引入的组件样式会覆盖掉之前的样式
- 把文件名改为
index.module.css
- 引入css文件时:
import hello from './index.module.css'
- 使用样式时:
className={hello.title}
样式的模块化用的不是很多,一般用less的嵌套写法即可
五、vscode中react插件的安装
在vscode的插件市场中搜索react👇
比如,一些代码片段:
rcc:类式组件
rfc:函数式组件
六、组件化编码流程
tips:
①打开终端快捷键:ctrl + ~
②停止运行快捷键:ctrl + C
-
拆分组件: 拆分界面,抽取组件
-
实现静态组件: 使用组件实现静态页面效果
-
实现动态组件
3.1 动态显示初始化数据3.1.1 数据类型 3.1.2 数据名称 3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
七、组件的组合使用-TodoList案例
功能: 组件化实现此功能
- 显示所有todo列表
- 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
1.静态组件
- App.js中引入模块的顺序
①外部模块②自己的模块③样式文件
- 拆分List与Item时,先把List整个拆分,再在List里面拆分Item
2.动态初始化列表
数据存放在App的state中,传给子组件List:
export default class App extends Component{
// 初始化状态
state={todos:[
{id:'001',name:'吃饭',done:true},
{id:'002',name:'睡觉',done:true},
{id:'003',name:'敲代码',done:false},
]}
render(){
const {todos}=this.state;
return (
<div className="todo-container">
<div className="todo-wrap">
<Header/>
<List todos={todos}/>
<Footer/>
</div>
</div>
)
}
}
把List中的数据批量传递给其子组件Item:
export default class List extends Component{
render(){
const {todos}=this.props;
return (
<ul className="todo-main">
{
todos.map(todo=>{
// return <Item key={todo.id} id={todo.id} name={todo.name} done={todo.done}/>
return <Item key={todo.id} {...todo}/> // 批量传递
})
}
</ul>
)
}
}
Item组件接收数据:
export default class Item extends Component {
render() {
const {name,done}=this.props
return (
<li>
<label>
<input type="checkbox" defaultChecked={done}/>
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{display:'none'}}>删除</button>
</li>
)
}
}
3.添加todo
父给子传数据:props
子给父传数据:利用函数
比如子组件Header给父组件App传数据:
需要给input框绑定一个键盘事件:onKeyUp
专门生成唯一id的库:uuid
安装:yarn add uuid
或者npm i uuid
但这个库有点大,不推荐,推荐nanoid
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import './index.css'
export default class Header extends Component {
handleKeyUp=(event)=>{
// 解构赋值获取keyCode,target
const {keyCode,target}=event
// 判断按下的是否为回车键
if(keyCode!==13) return
//添加的todo名字不能为空
if(target.value.trim()===''){
alert('输入不能为空!')
return
}
// 准备好一个todo对象
const todoObj={id:nanoid(),name:target.value,done:false}
// 将todoObj传递给App
this.props.addTodo(todoObj)
// 清空输入
target.value=''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
4.鼠标移入效果
鼠标悬浮在某一项上面时,该项高亮,且后面出现“删除”按钮
export default class Item extends Component {
state={mouse:false}
handleMouse=(flag)=>{
return ()=>{
console.log(flag)
this.setState({mouse:flag})
}
}
render() {
const {name,done}=this.props
const {mouse} = this.state
return (
<li style={{backgroundColor:mouse?'#ddd':'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type="checkbox" defaultChecked={done}/>
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button>
</li>
)
}
}
3.添加一个todo
根据勾选或者未勾选,更改状态里的值
tips:状态在哪里,操作状态的方法就在哪里
App给List传递props:
<List todos={todos} updateTodo={this.updateTodo}/>
// updateTodo用于更新一个todo对象
updateTodo=(id,done)=>{
// 获取状态中的todos
const {todos}=this.state
// 匹配 处理数据
const newTodos=todos.map((todoObj)=>{
if(todoObj.id===id) return {...todoObj,done}
else return todoObj
})
this.setState({todos:newTodos})
};
List给Item传递props:
return <Item key={todo.id} {...todo} updateTodo={updateTodo}/>
Item中的回调函数;
// 勾选、取消勾选某一个todo的回调
handleCheck=(id)=>{
return (event)=>{
// console.log(id,event.target.checked)
this.props.updateTodo(id,event.target.checked)
}
}
4.对props进行限制
安装第三方库:yarn add prop-types
引入:import PropTypes from 'prop-types'
对传递的props进行类型以及必要性的限制:
Header组件中:
static propTypes={
addTodo:PropTypes.func.isRequired
}
List组件中:
static propTypes={
todos:PropTypes.array.isRequired,
updateTodo:PropTypes.func.isRequired
}
5.删除一个todo
App组件中:
// deleteTodo用于删除一个todo对象
// tips:不能用delete,因为delete是个关键字,用于删除对象中的某个属性
deleteTodo=(id)=>{
// 获取原来的todos
const {todos}=this.state
// 删除指定id的todo对象
const newTodos=todos.filter((todoObj)=>{
return todoObj.id!==id
})
// 更新状态
this.setState({todos:newTodos})
}
Item组件中:
// 删除一个todo的回调
handleDelete=(id)=>{
// console.log('通知App删除',id)
// alert键只有确认
if(window.confirm('确定删除吗?')){
this.props.deleteTodo(id)
}
}
6.实现底部功能–Footer
tips:defaultChecked
只在第一次时起作用
App组件给子组件Footer传递函数:
<Footer todos={todos} checkAllTodo={this.checkAllTodo} checkAllDone={this.checkAllDone}/>
在App组件中修改状态的值:
// checkAllTodo用于全选
checkAllTodo=(done)=>{
// 获取原来的todos
const {todos}=this.state
// 加工数据
const newTodos=todos.map((todoObj)=>{
return {...todoObj,done}
})
this.setState({todos:newTodos})
}
//checkAllDone用于清除已完成任务
checkAllDone=()=>{
// 获取原来的todos
const {todos}=this.state
// 过滤数据
const newTodos=todos.filter((todoObj)=>{
//return todoObj.done===false
return !todoObj.done
})
this.setState({todos:newTodos})
}
子组件Footer调用父组件中的函数:
export default class Footer extends Component {
// 全选checkbox的回调
handleCheckAll=(event)=>{
this.props.checkAllTodo(event.target.checked)
}
// 清除已完成任务的回调
handleCheckAllDone=()=>{
this.props.checkAllDone()
}
render() {
const {todos}=this.props
// 已完成的个数
const doneCount=todos.reduce((pre,todo)=>{return pre + (todo.done?1:0)},0)
// 总数
const total=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 className="btn btn-danger" onClick={this.handleCheckAllDone}>清除已完成任务</button>
</div>
)
}
}
7.总结TodoList案例
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
某个组件使用:放在其自身的state中
某些组件使用:放在它们共同的父组件的state中(官方称此操作为状态提升) - 关于父子组件之间通信:
父–>子传递数据:通过props
子–>父传递数据:通过props,要求父提前给子传递一个函数 - 注意
defaultChecked
和checked
的区别,类似的还有defaultValue
和value
- 状态在哪里,操作状态的方法就在哪里
写在后面
这周的进度比较慢,今天已经周六了,原计划明天看到p100,现在才到p65
因为从周一到周三调了毕设的代码,现在也算跑通了
这两天要赶赶进度了
下一篇是第四章的内容:React ajax