# 收集表单数据
# 非受控组件
页面内所有的输入类 dom,如果是 现用现取 ,那就是非受控组件
<script type="text/babel">
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const { username, password } = this;
alert(
`你输入的用户名是:${username.value},你输入的密码是:${password.value}`
);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input ref={(c) => (this.username = c)} type="text" name="username" />
密码:
<input
ref={(c) => (this.password = c)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
# 受控组件
页面中所有输入类的 dom, 随着你的输入维护你的状态就是受控组件(相当于 Vue 中的 v-model)
-
案例一
<script type="text/babel"> //创建组件 class Login extends React.Component { //初始化状态 state = { username: "", //用户名 password: "", //密码 }; //保存用户名到状态中 saveUsername = (event) => { this.setState({ username: event.target.value }); }; //保存密码到状态中 savePassword = (event) => { this.setState({ password: event.target.value }); }; //表单提交的回调 handleSubmit = (event) => { event.preventDefault(); //阻止表单提交 const { username, password } = this.state; alert(`你输入的用户名是:${username},你输入的密码是:${password}`); }; render() { return ( <form onSubmit={this.handleSubmit}> 用户名: <input onChange={this.saveUsername} type="text" name="username" /> 密码: <input onChange={this.savePassword} type="password" name="password" /> <button>登录</button> </form> ); } } //渲染组件 ReactDOM.render(<Login />, document.getElementById("test")); </script> -
案例二 (脚手架)
import { Component } from "react"; class FormDemo extends Component { state = { msg: "hello world", isAgree: false, comment: "", likes: ["vue"], }; render() { const { msg, isAgree, comment, likes } = this.state; // react 希望用户制作成受控表单, value 与 state 关联,并且使用对应的 onchange 事件修改关联的 state // 如果不想修改,或者不想制作成受控表单,可以将 value 改为 defaultValue // input:text password number checkbox textarea 都要弄成受控表单 return ( <div> <input id="msg" type="text" value={msg} onChange={this.handleChange} /> <input id="isAgree" type="checkbox" checked={isAgree} onChange={this.handleChange} /> <textarea id="comment" value={comment} onChange={this.handleChange} ></textarea> <hr /> <h3>你喜欢的技术</h3> Vue <input type="checkbox" value="vue" checked={likes.includes("vue")} onChange={this.chooseLikes} /> react <input type="checkbox" value="react" checked={likes.includes("react")} onChange={this.chooseLikes} /> angular <input type="checkbox" value="angular" checked={likes.includes("angular")} onChange={this.chooseLikes} /> 微信小程序 <input type="checkbox" value="小程序" checked={likes.includes("小程序")} onChange={this.chooseLikes} /> </div> ); } handleChange = (e) => { const target = e.target; // 如果 target.type 是 checkbox 我们获取的值就是 target.checked 否则就是 target.value const res = target.type === "checkbox" ? target.checked : target.value; this.setState({ [target.id]: res, }); }; chooseLikes = (e) => { const target = e.target; const { likes } = this.state; // 尽量不要对state进行直接修改 if (target.checked) { this.setState({ likes: [...likes, target.value], }); } else { this.setState({ likes: likes.filter((item) => item !== target.value), }); } }; } export default FormDemo;
# 高阶函数和函数柯里化
-
高阶函数:如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数。
- 若 A 函数,接收的参数是一个函数,那么 A 就可以称之为高阶函数。
- 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map () 数组身上常见的方法等等
-
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a) { return (b) => { return (c) => { return a + b + c; }; }; }
# 使用柯里化的实现
<script type="text/babel">
class Login extends React.Component {
state = {
username: "", //用户名
password: "", //密码
};
//保存表单数据到状态中
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value });
};
};
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
{/* 如果函数不加小括号就是将这个函数作为onChange的回调*/}
{/* 如果函数加了小括号就是将这个函数的返回值作为onChange的回调*/}
<input
onChange={this.saveFormData("username")}
type="text"
name="username"
/>
密码:
<input
onChange={this.saveFormData("password")}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
# 不使用柯里化的实现
<script type="text/babel">
class Login extends React.Component {
state = {
username: "", //用户名
password: "", //密码
};
//保存表单数据到状态中
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value });
};
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input
onChange={(event) => this.saveFormData("username", event)}
type="text"
name="username"
/>
密码:
<input
onChange={(event) => this.saveFormData("password", event)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
# 生命周期 (旧)
只有类组件有生命周期,函数式组件没有生命周期!!!
生命周期函数不写成箭头函数也可以使用 this
组件的生命周期分为三个阶段: 挂载 (组件首次加载), 更新 (组件的 state 和 props 发生改变), 卸载 (组件的 dom 消失)
# 组件挂载流程
-
constructor () 一般都省略
constructor 主要是用来初始化 state 以及为事件函数绑定实例 (处理事件函数的 this 指向,让其指向组件本身)
如果 state 在外边定义了,就不要在 constructor 中定义了
constructor 内禁止使用 setState -
componentWillMount () 组件将要被挂载的钩子
-
render () 组件中必须使用的
render 是在初始化的时候和状态更新之后调用
根据 state 和 props 渲染 react 节点 (虚拟节点),发生变化重新渲染
且需要是纯函数(里面不要写异步操作) -
componentDidMount()
组件 dom 渲染完毕时调用,可以发送网络请求使用 setState 更新数据,获取真实 dom 节点
# 组件卸载流程
componentWillUnmount()组件即将被销毁,做解绑事件,清除定时器等ReactDom.unmountComponentAtNode(document.getElementById(‘test’))卸载组件的方法
# setState 流程
-
shouldComponentUpdate () 组件是否应该更新, 通过 return true/false 来控制组件是否更新 (控制组件更新的阀门)
如果不写这个钩子,React 会自动返回 true,如果自己写了这个钩子,则必须返回一个 true/false -
componentWillUpdate () 组件将要更新
-
render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染
-
componentDidUpdate()组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点
# forceUpdate 流程
强制更新 ,可以不对状态进行任何的修改,组件也能进行更新
-
componentWillUpdate () 组件将要更新
-
render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染
-
componentDidUpdate () 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点
<script type="text/babel">
class Count extends React.Component {
//强制更新按钮的回调
force = () => {
this.forceUpdate();
};
// 会执行 componentWillUpdate,render,componentDidUpdate 这三个生命周期钩子
render() {
return (
<button onClick={this.force}>
不更改任何状态中的数据,强制更新一下
</button>
);
}
}
//渲染组件
ReactDOM.render(<Count />, document.getElementById("test"));
</script>
# 父组件 render 流程
-
componentWillReceiveProps () 第一次传的不算,组件将要接收新的 props
-
shouldComponentUpdate () 组件是否应该更新, 通过 return true/false 来控制组件是否更新 (控制组件更新的阀门)
如果不写这个钩子,React 会自动返回 true,如果自己写了这个钩子,则必须返回一个 true/false -
componentWillUpdate () 组件将要更新
-
render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染
-
componentDidUpdate () 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点
<script type="text/babel">
//父组件A
class A extends React.Component {
//初始化状态
state = { carName: "奔驰" };
changeCar = () => {
this.setState({ carName: "奥拓" });
};
render() {
return (
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName} />
</div>
);
}
}
//子组件B
class B extends React.Component {
//组件将要接收新的props的钩子
componentWillReceiveProps(props) {
console.log("B---componentWillReceiveProps", props);
}
render() {
return <div>我是B组件,接收到的车是:{this.props.carName}</div>;
}
}
ReactDOM.render(<A />, document.getElementById("test"));
</script>
# 生命周期 (旧) 总结
-
初始化阶段:由 ReactDOM.render () 触发 — 初次渲染
- constructor()
- componentWillMount()
- render()
componentDidMount()=====>常用,组件 dom 渲染完毕时调用,可以发送网络请求,打开定时器,订阅消息,使用 setState 更新数据,获取真实 dom 节点
-
更新阶段:由组件内部 this.setSate () 或父组件 render 触发
- shouldComponentUpdate (nextProps,nextState) 接受两个参数,新的 props 和新的 state。组件是否应该可以更新,需要返回一个布尔值,用的不多
- componentWillUpdate()
render()=====>必须使用的一个,初始化渲染或更新渲染调用- componentDidUpdate (prevProps,prevState,snapshot): 以前的 props 和 state,以及 getSnapshotBeforeUpdate () 传递下来的快照值
-
卸载组件:由 ReactDOM.unmountComponentAtNode () 触发
componentWillUnmount()=====>常用,组件即将被销毁,做解绑事件,清除定时器,取消订阅消息等
# 生命周期 (新)
- 除了
componentWillUnmount之外,所有带will的都需要加上UNSAFE_前缀,因为新版本里这三个(componentWillMount,componentWillReceiveProps,componentWillUpdate)即将被弃用 新加了两个生命周期函数,但是不常用(static getDerivedStateFromProps(),getSnapshotBeforeUpdate) 这两个基本用不到static getDerivedStateFromProps(props,state):从 props 那里得到一个派生的状态。第二个参数 state 会将定义好的 state 传过来。 state 的值在任何时候都取决于 props 的时候 (包括后期的更新也取决于 props) 就用它,仅适用于罕见的用例,开发中用不到,这个东西会使代码冗余,并使组件难以维护getSnapshotBeforeUpdate(prevProps, prevState, snapshot):基本不用,在最近一次渲染输出 (提交到 DOM 节点) 之前调用,它使得组件能在发生更改之前从 DOM 中捕获一些信息 (例如:滚动位置)可以在更新之前获取一些信息,个人理解这个生命函数就是在更新之前做的一些事情
# getSnapshotBeforeUpdate 案例
此案例效果:在动态加载 li 的情况下,鼠标放在列表的哪里就固定在哪里
<style>
.list {
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news {
height: 30px;
}
</style>
<script type="text/babel">
class NewsList extends React.Component {
state = { newsArr: [] };
componentDidMount() {
setInterval(() => {
//获取原状态
const { newsArr } = this.state;
//模拟一条新闻
const news = "新闻" + (newsArr.length + 1);
//更新状态
this.setState({ newsArr: [news, ...newsArr] });
}, 1000);
}
getSnapshotBeforeUpdate() {
return this.refs.list.scrollHeight;
}
componentDidUpdate(preProps, preState, height) {
this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
}
render() {
return (
<div className="list" ref="list">
{this.state.newsArr.map((n, index) => {
return (
<div key={index} className="news">
{n}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<NewsList />, document.getElementById("test"));
</script>
# 生命周期 (新) 总结
-
初始化阶段:由 ReactDOM.render () 触发 — 初次渲染
- constructor()
- getDerivedStateFromProps
- render()
componentDidMount()=====>常用,组件 dom 渲染完毕时调用,可以发送网络请求,打开定时器,订阅消息,使用 setState 更新数据,获取真实 dom 节点
-
更新阶段:由组件内部 this.setSate () 或父组件重新 render 触发
- getDerivedStateFromProps
- shouldComponentUpdate()
render()=====>必须使用的一个,初始化渲染或更新渲染调用- getSnapshotBeforeUpdate
- componentDidUpdate()
-
卸载组件:由 ReactDOM.unmountComponentAtNode () 触发
componentWillUnmount()=====>常用,组件即将被销毁,做解绑事件,清除定时器,取消订阅消息等
# 虚拟 DOM 的 diffing 算法
# 概念
会根据虚拟 DOM 创建出一棵树,如果虚拟 DOM 树没有变化则真实树放置不动。如果虚拟 DOM 树改变则真实树也会改变。
虚拟 DOM 的最小单位是节点 (标签),且会逐层对比
# key 值的作用
-
简单的说:
key是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用。 -
详细的说: 当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,
随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:-
旧虚拟 DOM 中
找到了与新虚拟 DOM相同的 key:
(1). 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
(2). 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM -
旧虚拟 DOM 中
未找到与新虚拟 DOM相同的 key
根据数据创建新的真实 DOM,随后渲染到到页面
-
-
用 index 作为 key 可能会引发的问题:
- 使用 index 作为 key 值,虚拟 dom 对比就是顺序对比,如果在数组前面插入元素,它需要对比整个数组,用 id 属性,只需要一次
- 若对数据进行逆序添加、逆序删除等破坏性操作:会产生没有必要的真实 DOM 更新,
会导致严重的效率问题 - 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新
(界面有问题)
-
开发中如何选择 key?:
- 最好使用每条数据的
唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。 - 如果确定只是简单的展示数据,用 index 也是可以的。
- 最好使用每条数据的
# React 脚手架
VSCode 下载 React 插件: 以 ES7 开头那个
npm 已经内置了 react 的脚手架工具 (脚手架:用于搭建环境的工具),要求 Node >= 8.10 和 npm >= 5.6
安装脚手架:npm i create-react-app -gnpx create-react-app 项目名创建新的 React 项目npm start运行- 在组件中输标签不能自动补去设置中,搜索 react,添加项 javascript,值为 javascriptreact
- 或去文件 - 首选项 - 设置中提示的配置文件 (setting.json) 中输入,要保证最后一项没有逗号
“emmet.includeLanguages”: {
“javascript”: “javascriptreact”,
“wxml”: “html”
},
# public 静态资源文件夹
-
public 下的 index.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" /> <!-- 应用加壳时的配置文件 --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <!-- 若浏览器不支持js,则展示标签中的内容 --> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>
# src 源码文件夹
App.js对应 vue 中 App.vueindex.js对应 vue 中的 main.js,入口文件index.css是全局样式reportWebVitals.js页面性能分析文件setupTests.js组件单元测试的文件
# Hello 案例
可以安装一个 VSCode 插件:ES7+ React/Redux/React-Native snippets,方便开发
-
App.js
<!-- 这里的Component是ES6语法的导出,而不是解构赋值 --> import React, { Component } from "react"; import Hello from "./components/Hello/Hello"; import Welcome from "./components/Welcome/Welcome"; /* 用{}可以引入分别暴露和统一暴露的东西,这里是ES6的另外一种引入方式,不是解构赋值。 如果有这种引用方式,说明 from 后边的文件里边用了多种暴露的形式 */ // 创建并暴露App组件 export default class App extends Component { render() { return ( <div> <Hello /> <Welcome /> </div> ); } } -
index.js
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; ReactDOM.render(<App />, document.getElementById("root")); -
Hello.jsx
注意这里的后缀是
.jsx,主要是为了和其他的 js 文件区分,jsx用来表示组件import React, { Component } from "react"; import "./Hello.css"; export default class Hello extends Component { render() { return <h1 className="title">Hello</h1>; } } -
Welcome.jsx
import React, { Component } from "react"; // 样式的模块化 // 如果两个组件中有同名的属性,则后边的覆盖前边的,使用了模块化之后就可以解决这个问题 // 对应 Vue 中的 scoped import welcome from "./Welcome.module.css"; export default class Welcome extends Component { render() { return <h1 className={welcome.title}>Welcome</h1>; } }
# TodoList–props 案例
-
List.jsx
export default class List extends Component { render() { const { todos } = this.props; return ( <ul className="todo-main"> {todos.map((todo) => { return ( <Item key={todo.id} // 此处是将todo整体作为props传递给Item组件 {...todo} /> ); })} </ul> ); } } -
Item.jsx
export default class Item extends Component { render() { const { name, done } = this.props; return ( <li> <label> <!-- 只管第一次上来勾还是不勾,以后想改也可以改,但是可能会有小 bug(defaultChecked只在第一次执行的时候有作用,之后就没作用了,需要给checkbox指定onChange事件),checked 就是一下子写死了 --> <input type="checkbox" defaultChecked={done} /> <span>{name}</span> </label> <button>删除</button> </li> ); } }
# TodoList 案例
# todoList 案例相关知识点
- 拆分组件、实现静态组件,注意:
className、style的写法 - 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
- 某个组件使用:放在其自身的 state 中
- 某些组件使用:放在他们共同的父组件 state 中(官方称此操作为:状态提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过 props 传递
- 【子组件】给【父组件】传递数据:通过 props 传递,要求父提前给子传递一个函数
- 注意
defaultChecked和checked的区别,类似的还有:defaultValue 和 value - 状态在哪里,操作状态的方法就在哪里
reduce 用处:数组统计,条件求和,筛选最值
# 案例源码
-
App.jsx
import React, { Component } from "react"; import Header from "./components/Header"; import List from "./components/List"; import Footer from "./components/Footer"; export default class App extends Component { //状态在哪里,操作状态的方法就在哪里 //初始化状态 state = { todos: [ { id: "001", name: "吃饭", done: true }, { id: "002", name: "睡觉", done: true }, { id: "003", name: "打代码", done: false }, { id: "004", name: "逛街", done: false }, ], }; //addTodo用于添加一个todo,接收的参数是todo对象 addTodo = (todoObj) => { //获取原todos const { todos } = this.state; //追加一个todo const newTodos = [todoObj, ...todos]; //更新状态 this.setState({ todos: newTodos }); }; //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 }); }; //deleteTodo用于删除一个todo对象 deleteTodo = (id) => { //获取原来的todos const { todos } = this.state; //删除指定id的todo对象 const newTodos = todos.filter((todoObj) => { return todoObj.id !== id; }); //更新状态 this.setState({ todos: newTodos }); }; //checkAllTodo用于全选 checkAllTodo = (done) => { //获取原来的todos const { todos } = this.state; //加工数据 const newTodos = todos.map((todoObj) => { return { ...todoObj, done }; }); //更新状态 this.setState({ todos: newTodos }); }; //clearAllDone用于清除所有已完成的 clearAllDone = () => { //获取原来的todos 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} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} /> <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} /> </div> </div> ); } } -
Header.jsx
import React, { Component } from "react"; import PropTypes from "prop-types"; import { nanoid } from "nanoid"; export default class Header extends Component { //对接收的props进行:类型、必要性的限制 static propTypes = { addTodo: PropTypes.func.isRequired, }; //键盘事件的回调 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> ); } } -
List.jsx
import React, { Component } from "react"; import PropTypes from "prop-types"; import Item from "../Item"; 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> ); } } -
Item.jsx
import React, { Component } from "react"; export default class Item extends Component { state = { mouse: false }; //标识鼠标移入、移出 //鼠标移入、移出的回调 handleMouse = (flag) => { return () => { this.setState({ mouse: 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 } = this.state; return ( <li style={{ backgroundColor: mouse ? "#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> ); } } -
Footer.jsx
import React, { Component } from "react"; export default class Footer extends Component { //全选checkbox的回调 handleCheckAll = (event) => { this.props.checkAllTodo(event.target.checked); }; //清除已完成任务的回调 handleClearAllDone = () => { this.props.clearAllDone(); }; render() { const { todos } = this.props; //已完成的个数 const doneCount = todos.reduce( (pre, todo) => 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 onClick={this.handleClearAllDone} className="btn btn-danger"> 清除已完成任务 </button> </div> ); } }