目录
React应用(基于React脚手架)
react脚手架
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查、jsx编译、devServer…)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
- react提供了一个用于创建react项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
创建项目并启动
命令
- npm start运行项目
- npm run build 打包
- npm test检测
- npm run eject展示webpack配置文件(不可以回退)
- 第一步,全局安装:npm i -g create-react-app
- 第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
- 第三步,进入项目文件夹:cd hello-react
- 第四步,启动项目:npm start
react脚手架项目结构
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- %PUBLIC_URL% 代表 public 文件夹的路径-->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />、
<!-- 开启理想视口,用于移动端页面适配 -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于适配浏览器也签+地址栏颜色(仅支持安卓手机浏览器) -->
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 用于指定网页添加到主屏后的图标 -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<title>React App</title>
</head>
<body>
<!-- 若浏览器不支持js则展示该内容 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- 容器 -->
<div id="root"></div>
</body>
</html>
index.js
// 核心库
import React from 'react';
// 操作dom
import ReactDOM from 'react-dom';
// 样式
import './index.css';
// app组件
import App from './App';
//记录页面性能
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
// React.StrictMode 检测语法
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
app.js
- //注意 Component 此处不是解构赋值 react内部 采用了多种暴露方式 默认爆露react
- // export default react export Component
export 跟export default 的区别
- .export与export default均可用于导出常量、函数、文件、模块等
- .在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要
- 输出单个值,使用export default
- 输出多个值,使用export
- export default与普通的export不要同时使用
//创建外壳组件
//注意 Component 此处不是解构赋值 react内部 采用了多种暴露方式 默认爆露react
// export default react export Component
import React,{Component} from "react";
// 创建并暴露app
export default class App extends Component{
render(){
return(
<div>
hello react
</div>
)
}
}
样式模块化
- 给css文件添加module关键字 index.module.css
- import hello from ./index.module.css 方式引入
- 使用 className={hello.title}
功能界面的组件化编码流程(通用)
1. 拆分组件: 拆分界面,抽取组件
2. 实现静态组件: 使用组件实现静态页面效果
3. 实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
组件的组合使用-TodoList案例
效果
功能: 组件化实现此功能
1. 显示所有todo列表
2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
组件拆分
主页面app.jsx
- 状态在哪里,操作状态的方法就在哪里
- 获取原数据 处理数据 更改状态
-
{ ...item, done } 复制一个todos对象修改选中状态
-
1.【父组件】给【子组件】传递数据:通过props传递
-
2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
import React, { Component } from 'react'
import './App.css'
import Header from './components/Header/index'
import List from './components/List'
import Footer from './components/Footer'
export default class App extends Component {
//状态在哪里,操作状态的方法就在哪里
render() {
const { todos } = this.state
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<List todos={todos} updateTodos={this.updateTodos} deleteTodo={this.deleteTodo} />
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearDone={this.clearDone} />
</div>
</div>
)
}
//初始化状态
state = {
todos: [
{ id: "001", name: '吃饭', done: true },
{ id: "002", name: '睡觉', done: true },
{ id: "003", name: '打代码', done: false },
]
}
//addTod 用于添加一个 todos ,接收的参数是一个对象
addTodo = (data) => {
//获取原数据
const { todos } = this.state
//追加一个 todos
const newTodos = [...todos, data]
//更新状态
this.setState({ todos: newTodos })
}
//用于更新一个 todo 对象
updateTodos = (id, done) => {
//获取原来状态
const { todos } = this.state
//处理数据
const newTodos = todos.map((item) => {
//复制一个todos对象修改选中状态
if (item.id == id) return { ...item, done }
else return item
})
//更新状态
this.setState({ todos: newTodos })
}
//用于删除一个 todo 对象
deleteTodo = (id) => {
//获取原来状态
const { todos } = this.state
//删除指定id的todo
const newTodos = todos.filter((item) => {
return item.id != id
})
//更新状态
this.setState({ todos: newTodos })
}
//用于全选
checkAllTodo = (done) => {
//获取原来状态
const { todos } = this.state
//处理数据
const newTodos = todos.map((item) => {
//复制一个todos对象修改选中状态
return { ...item, done }
})
//更新状态
this.setState({ todos: newTodos })
}
//用于所有已完成的
clearDone = () => {
//获取原来状态
const { todos } = this.state
//删除指定id的todo
const newTodos = todos.filter((item) => {
return item.done = false
})
//更新状态
this.setState({ todos: newTodos })
}
}
Header 组件
import { nanoid } from 'nanoid'生成唯一id的插件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { nanoid } from 'nanoid'
import './index.css'
export default class Header extends Component {
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
)
}
//,对接收的props进行类型,必要性的限制
static propTypes = {
addTodo: PropTypes.func.isRequired
}
//键盘事件的回调
handleUp = (e) => {
//解构赋值获取 keyCode, target
const { keyCode, target } = e
//判断是否是回车
if (keyCode !== 13) return
//添加的todo名字不能为空
if (target.value.trim() === '') {
alert("输入不能为空")
return
}
// 准备好一个todo对象 nanoid生成唯一值插件
const obj = { id: nanoid(), name: target.value, done: false }
//将 对象传递给app
this.props.addTodo(obj)
}
}
Item组件
切记事件方法必须传入一个函数,如果传值应该写高阶函数,避免直接调用
defaultChecked、defaultValue 只在初始渲染时由状态控制,之后更新不再跟状态有关系,而checked、value在全过程中都受状态控制
import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
render() {
const { id, name, done } = this.props
const { mouse } = this.state
return (
// 切记事件方法必须传入一个函数,如果传值应该写高阶函数,避免直接调用
<li onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)} style={{ backgroundColor: mouse ? "#ddd" : "white" }} >
<label>
{/* defaultChecked、defaultValue 只在初始渲染时由状态控制,之后更新不再跟状态有关系,而checked、value在全过程中都受状态控制 */}
<input type="checkbox" checked={done} onChange={this.handleChange(id)} />
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{ display: mouse ? "block" : 'none' }} onClick={() => this.handleDelete(id)}>删除</button>
</li >
)
}
state = { mouse: false }//标识鼠标移入,移除
// 鼠标移入,移除回调
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
}
}
//勾选取消勾选的回调
handleChange = (id) => {
return (e) => {
this.props.updateTodos(id, e.target.checked);
}
}
// 删除一个todo的回调
handleDelete(id) {
if (window.confirm('确定删除吗')) { this.props.deleteTodo(id) }
}
}
List组件
import PropTypes from 'prop-types' 对接收的props进行类型,必要性的限制
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './index.css'
import Item from '../Item'
export default class List extends Component {
render() {
const { todos, updateTodos, deleteTodo } = this.props
return (
<ul className="todo-main">
{todos.map((todos) => {
return <Item key={todos.id} {...todos} updateTodos={updateTodos} deleteTodo={deleteTodo} />
})}
</ul>
)
}
//,对接收的props进行类型,必要性的限制
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodos: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
}
Footer组件
reduce计算值 参数:回调函数(函数上一次的返回值,第一次为reduce参数2,每一项),初始值
import React, { Component } from 'react'
import './index.css'
export default class Footer extends Component {
render() {
const { todos } = this.props
//总数
const total = todos.length
// 计算已完成的个数
//reduce计算值 参数:回调函数(函数上一次的返回值,第一次为reduce参数2,每一项),初始值
const doneCount = todos.reduce((pre, current) => {
return pre + (current.done ? 1 : 0)
}, 0)
return (
<div className="todo-footer">
<label>
{/*defaultChecked只执行一次 */}
<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.handleClearAllDone}> 清除已完成任务</button>
</div>
)
}
//全选的回调
handleCheckAll = (e) => {
this.props.checkAllTodo(e.target.checked)
}
// 清除所有已完成的回调
handleClearAllDone = () => {
this.props.clearDone()
}
}
总结
1.拆分组件、实现静态组件,注意:className、style的写法
2.动态初始化列表,如何确定将数据放在哪个组件的state中?
——某个组件使用:放在其自身的state中
——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
3.关于父子之间通信:
1.【父组件】给【子组件】传递数据:通过props传递
2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
5.状态在哪里,操作状态的方法就在哪里