本文所用代码链接:https://oss.rainsheep.cn/save/react%E5%85%A8%E5%AE%B6%E6%A1%B6%E8%B5%84%E6%96%99.zip
1. React 入门
1.1 React 简介
1.1.1 官网
- 英文官网: https://reactjs.org/
- 中文官网: https://react.docschina.org/
1.1.2 介绍描述
- 用于动态构建用户界面的 JavaScript 库(只关注于视图)
- 由Facebook开源
1.1.3 React的特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
1.1.4 React 高效的原因
- 使用虚拟 (virtual)DOM, 不总是直接操作页面真实 DOM。
- DOM Diffing 算法, 最小化页面重绘。
1.2 React 基本使用
1.2.1 效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello_react</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
</html>
1.2.2 相关 js 库
- react.js:React 核心库,如 props、state、refs。
- react-dom.js:提供操作 DOM 的 react 扩展库。
- babel.min.js:解析 JSX 语法代码转为 JS 代码的库。
1.2.3 创建虚拟 DOM 的两种方式
-
纯JS方式(一般不用)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>2_使用js创建虚拟DOM</title> </head> <body> <!-- 准备好一个“容器” --> <div id="test"></div> <!-- 引入react核心库 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript"> //1.创建虚拟DOM const VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span', {}, 'Hello,React')) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM, document.getElementById('test')) </script> </body> </html>
-
JSX方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>1_使用jsx创建虚拟DOM</title> </head> <body> <!-- 准备好一个“容器” --> <div id="test"></div> <!-- 引入react核心库 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <!-- 引入babel,用于将jsx转为js --> <script type="text/javascript" src="../js/babel.min.js"></script> <script type="text/babel"> /* 此处一定要写babel */ //1.创建虚拟DOM const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */ <h1 id="title"> <span>Hello,React</span> </h1> ) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM, document.getElementById('test')) </script> </body> </html>
1.2.4 虚拟 DOM 与真实 DOM
-
React 提供了一些 API 来创建一种 “特别” 的一般 js 对象,下面创建的就是一个简单的虚拟 DOM 对象
const VDOM = React.createElement('xx', {id: 'xx'}, 'xx')
-
虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
-
我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM。
-
关于虚拟 DOM
- 本质是 Object 类型的对象(一般对象)
- 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
- 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3_虚拟DOM与真实DOM</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<div id="demo"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
const TDOM = document.getElementById('demo')
console.log('虚拟DOM', VDOM);
console.log('真实DOM', TDOM);
console.log(typeof VDOM);
console.log(VDOM instanceof Object);
</script>
</body>
</html>
1.3 React JSX
1.3.1 效果
1.3.2 JSX
- 全称: JavaScript XML
- react 定义的一种类似于 XML 的 JS 扩展语法: JS + XML本质是
React.createElement(component, props, ...childen)
方法的语法糖 - 作用: 用来简化创建虚拟 DOM
- 写法:
var ele = <h1>Hello JSX!</h1>
- 注意1:它不是字符串, 也不是 HTML/XML 标签
- 注意2:它最终产生的就是一个 JS 对象
- 写法:
- 标签名任意: HTML 标签或其它标签
- 标签属性任意: HTML 标签属性或其它
- 基本语法规则
- 遇到
<
开头的代码, 以标签的语法解析- 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错。
- 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。
- 遇到以
{
开头的代码,以 JS 语法解析: 标签中的 js 表达式必须用{ }
包含 - 定义虚拟 DOM 时,不要写引号。
- 样式的类名指定不要用 class,要用 className。
- 内联样式,要用
style={{key:value}}
的形式去写。 - 只有一个根标签 (
<></>
和<div></div>
一致) - 标签必须闭合
- 遇到
1.3.3 渲染虚拟 DOM(元素)
- 语法:
ReactDOM.render(virtualDOM, containerDOM)
- 作用: 将虚拟 DOM 元素渲染到页面中的真实容器 DOM 中显示
- 参数说明
- 参数一: 纯 js 或 jsx 创建的虚拟 dom 对象
- 参数二: 用来包含虚拟 DOM 元素的真实 dom 元素对象(一般是一个 div)
<script type="text/babel">
const myId = "aTgUiGu";
const myData = "HeLlo,rEaCt";
//1.创建虚拟DOM
const VDOM = (
<div>
<h2 className="title" id={myId.toLowerCase()}>
<span style={{ color: "white", fontSize: "29px" }}>
{myData.toLowerCase()}
</span>
</h2>
<h2 className="title" id={myId.toUpperCase()}>
<span style={{ color: "white", fontSize: "29px" }}>
{myData.toLowerCase()}
</span>
</h2>
<input type="text" />
</div>
);
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById("test"));
</script>
1.3.4 js 语句与表达式
js 表达式
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
- a
- a+b
- demo(1)
- arr.map()
- function test () {}
js 语句
if(){}
for() {}
switch() {case: xxxx}
1.3.5 JSX 练习
需求: 动态展示如下列表
<script type="text/babel" >
//模拟一些数据
const data = ['Angular','React','Vue']
//1.创建虚拟DOM
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
1.4 模块与组件、模块化与组件化的理解
1.4.1 模块
- 理解:向外提供特定功能的 js 程序, 一般就是一个 js 文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用 js, 简化 js 的编写, 提高 js 运行效率
1.4.2 组件
- 理解:用来实现局部功能效果的代码和资源的集合 (html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
1.4.3 模块化
当应用的 js 都以模块来编写的, 这个应用就是一个模块化的应用
1.4.4 组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
2. React 面向组件编程
2.1 基本理解与使用
2.1.1 使用 React 开发者工具调试
Chrome 插件:React Developer Tools
安装完后开发者工具会多两个工具
当项目为开发状态(未打包),插件为橘色
线上为蓝色
Components 能展示组件及属性,Profiler 能展示网站的性能。
2.1.2 函数式组件和类式组件
函数式组件:
<script type="text/babel">
//1.创建函数式组件
function MyComponent() {
console.log(this); //此处的 this 是 undefined,因为 babel 编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
</script>
类式组件:
<script type="text/babel">
//1.创建类式组件
class MyComponent extends React.Component {
render() {
// render 是放在哪里的?—— MyComponent 的原型对象上,供实例使用。
// render 中的 this 是谁?—— MyComponent 的实例对象 <=> MyComponent 组件实例对象。
console.log('render中的this:', this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React 解析组件标签,找到了 MyComponent 组件。
2.发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法。
3.将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
*/
</script>
为什么 render 中的 this 组件实例对象?
因为调用 render 的实例是组件实例,React 帮创建的。
2.1.3 注意
- 组件名必须首字母大写
- 虚拟 DOM 元素只能有一个根元素
- 虚拟 DOM 元素必须有结束标签
2.1.4 渲染类组件标签的基本流程
- React 内部会创建组件实例对象
- 调用 render() 得到虚拟 DOM, 并解析为真实 DOM
- 插入到指定的页面元素内部
2.2 组件三大核心属性1:state
2.2.1 效果
需求: 定义一个展示天气信息的组件
- 默认展示天气炎热或凉爽
- 点击文字切换天气
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
console.log('constructor');
super(props)
//初始化状态
this.state = {isHot: false, wind: '微风'}
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
console.log('render');
//读取状态
const {isHot, wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather() {
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用,所以 changeWeather 中 this 不是实例本身
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot: !isHot})
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
state 简写方式
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//初始化状态
state = {isHot: false, wind: '微风'}
render() {
const {isHot, wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = () => {
const isHot = this.state.isHot
this.setState({isHot: !isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
2.2.2 理解
- state 是组件对象最重要的属性, 值是对象(可以包含多个 key-value 的组合)
- 组件被称为"状态机", 通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
2.2.3 强烈注意
-
组件中 render 方法中的 this 为组件实例对象
-
组件自定义的方法中 this 为 undefined,如何解决?
- 强制绑定 this: 通过函数对象的 bind()
- 箭头函数
-
状态数据,不能直接修改或更新
2.3 组件三大核心属性2:props
2.3.1 效果
需求: 自定义用来显示一个人员信息的组件
- 姓名必须指定,且为字符串类型;
- 性别为字符串类型,如果性别没有指定,默认为男
- 年龄为字符串类型,且为数字类型,默认值为18
2.3.2 理解
- 每个组件对象都会有 props(properties的简写) 属性
- 组件标签的所有属性都保存在 props 中
2.3.3 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改 props 数据
2.3.4 编码操作
-
内部读取某个属性值
this.props.name
-
对 props 中的属性值进行类型限制和必要性限制
第一种方式(React v15.5 开始已弃用)
Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number }
第二种方式(新):使用 prop-types 库进限制(需要引入 prop-types 库)
Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number. }
-
扩展属性: 将对象的所有属性通过 props 传递
<Person {...person}/>
-
默认属性值:
Person.defaultProps = { age: 18, sex:'男' }
-
组件类的构造函数
constructor(props){ super(props) console.log(props)//打印所有属性 }
2.3.5 实例
2.3.5.1 props 基本使用
<script type="text/babel">
//创建组件
class Person extends React.Component {
render() {
// console.log(this);
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>, document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>, document.getElementById('test2'))
const p = {name: '老刘', age: 18, sex: '女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))
</script>
2.3.5.2 对 props 进行限制
<script type="text/babel">
//创建组件
class Person extends React.Component {
render() {
// console.log(this);
const {name, age, sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex为字符串
age: PropTypes.number,//限制age为数值
speak: PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>, document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>, document.getElementById('test2'))
const p = {name: '老刘', age: 18, sex: '女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))
function speak() {
console.log('我说话了');
}
</script>
2.3.5.3 props 的简写方式
<script type="text/babel">
//创建组件
class Person extends React.Component {
constructor(props) {
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor', this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex为字符串
age: PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
render() {
// console.log(this);
const {name, age, sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>, document.getElementById('test1'))
</script>
2.3.5.4 函数组件使用 props
<script type="text/babel">
//创建组件
function Person(props) {
const {name, age, sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex为字符串
age: PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>, document.getElementById('test1'))
</script>
2.4 组件三大核心属性3:refs 与事件处理
2.4.1 效果
需求: 自定义组件, 功能说明如下:
- 点击按钮, 提示第一个输入框中的值
- 当第 2 个输入框失去焦点时, 提示这个输入框中的值
2.4.2 理解
组件内的标签可以定义 ref 属性来标识自己
2.4.3 编码
-
字符串形式 ref
<input ref="input1"/>
<script type="text/babel"> //创建组件 class Demo extends React.Component { //展示左侧输入框的数据 showData = () => { const {input1} = this.refs alert(input1.value) } //展示右侧输入框的数据 showData2 = () => { const {input2} = this.refs alert(input2.value) } render() { return ( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/> </div> ) } } //渲染组件到页面 ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test')) </script>
-
回调形式的 ref
<input ref={(c)=>{this.input1 = c}}
<script type="text/babel"> //创建组件 class Demo extends React.Component { //展示左侧输入框的数据 showData = () => { const {input1} = this alert(input1.value) } //展示右侧输入框的数据 showData2 = () => { const {input2} = this alert(input2.value) } render() { return ( <div> <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/> </div> ) } } //渲染组件到页面 ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test')) </script>
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。
这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
<script type="text/babel"> //创建组件 class Demo extends React.Component { state = {isHot: false} showInfo = () => { const {input1} = this alert(input1.value) } changeWeather = () => { //获取原来的状态 const {isHot} = this.state //更新状态 this.setState({isHot: !isHot}) } saveInput = (c) => { this.input1 = c; console.log('@', c); } render() { const {isHot} = this.state return ( <div> <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2> {/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/} <input ref={this.saveInput} type="text"/><br/><br/> <button onClick={this.showInfo}>点我提示输入的数据</button> <button onClick={this.changeWeather}>点我切换天气</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Demo/>, document.getElementById('test')) </script>
-
createRef 创建 ref 容器
myRef = React.createRef() <input ref={this.myRef}/>
<script type="text/babel"> //创建组件 class Demo extends React.Component { /* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的 */ myRef = React.createRef() myRef2 = React.createRef() //展示左侧输入框的数据 showData = () => { alert(this.myRef.current.value); } //展示右侧输入框的数据 showData2 = () => { alert(this.myRef2.current.value); } render() { return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/> </div> ) } } //渲染组件到页面 ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test')) </script>
2.4.4 事件处理
- 通过 onXxx 属性指定事件处理函数(注意大小写)
- React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
- 通过 event.target 得到发生事件的 DOM 元素对象
<script type="text/babel">
//创建组件
class Demo extends React.Component {
/*
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
*/
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData = (event) => {
console.log(event.target);
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = (event) => {
alert(event.target.value);
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
</script>
2.5 收集表单数据
2.5.1 效果
需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息
2.5.2 理解
包含表单的组件分类
-
非受控组件(输入类的,没有用状态维护,现用现取)
<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>
-
受控组件(输入类的维护至状态中,随用随取,双向绑定)
<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>
ref 尽量少用,受控组件可以减少 ref 的使用
2.6 组件的生命周期
2.6.1 效果
需求:定义组件实现以下功能:
- 让指定的文本做显示 / 隐藏的渐变动画
- 从完全可见,到彻底消失,耗时2S
- 点击“不活了”按钮从界面中卸载组件
<script type="text/babel">
//创建组件
//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Life extends React.Component {
state = {opacity: 1}
death = () => {
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//组件挂完毕
componentDidMount() {
console.log('componentDidMount');
this.timer = setInterval(() => {
//获取原状态
let {opacity} = this.state
//减小0.1
opacity -= 0.1
if (opacity <= 0) opacity = 1
//设置新的透明度
this.setState({opacity})
}, 200);
}
//组件将要卸载
componentWillUnmount() {
//清除定时器
clearInterval(this.timer)
}
//初始化渲染、状态更新之后
render() {
console.log('render');
return (
<div>
<h2 style={{opacity: this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Life/>, document.getElementById('test'))
</script>
2.6.2 理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React 组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2.6.3 生命周期流程图(旧)
生命周期的三个阶段(旧)
初始化阶段: 由 ReactDOM.render() 触发---初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()
更新阶段: 由组件内部 this.setSate() 或父组件重新 render 触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
卸载组件: 由 ReactDOM.unmountComponentAtNode() 触发
- componentWillUnmount()
componentWillReceiveProps 在父组件更新(非第一次 render) 时会被调用。
<script type="text/babel">
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render() =====> 必须使用的一个
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//创建组件
class Count extends React.Component {
//构造器
constructor(props) {
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count: 0}
}
//加1按钮的回调
add = () => {
//获取原状态
const {count} = this.state
//更新状态
this.setState({count: count + 1})
}
//卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = () => {
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount() {
console.log('Count---componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount() {
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount() {
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate() {
console.log('Count---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate() {
console.log('Count---componentDidUpdate');
}
render() {
console.log('Count---render');
const {count} = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//父组件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);
}
//控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate() {
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate() {
console.log('B---componentDidUpdate');
}
render() {
console.log('B---render');
return (
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
//渲染组件
ReactDOM.render(<A/>, document.getElementById('test'))
</script>
2.6.4 声明周期流程图(新)
生命周期的三个阶段(新)
初始化阶段: 由 ReactDOM.render() 触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
更新阶段: 由组件内部 this.setSate() 或父组件重新 render 触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
<script type="text/babel">
//创建组件
class Count extends React.Component {
// change the world
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. getDerivedStateFromProps
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps
2. shouldComponentUpdate()
3. render()
4. getSnapshotBeforeUpdate
5. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//构造器
constructor(props) {
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count: 0}
}
//加1按钮的回调
add = () => {
//获取原状态
const {count} = this.state
//更新状态
this.setState({count: count + 1})
}
//卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = () => {
this.forceUpdate()
}
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
return null
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount() {
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount() {
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('Count---shouldComponentUpdate');
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps, preState, snapshotValue) {
console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
}
render() {
console.log('Count---render');
const {count} = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Count count={199}/>, document.getElementById('test'))
</script>
getDerivedStateFromProps 例子
getDerivedStateFromProps
是 React 组件生命周期中的一个静态方法,用于在组件接收到新的属性(props)时更新组件的状态(state)。它在组件的每次渲染之前调用,并返回一个对象,表示需要更新的状态,或者返回 null
表示不需要更新状态。
以下是一个使用 getDerivedStateFromProps
的简单示例:
import React from 'react';
class MyComponent extends React.Component {
state = {
prevPropsValue: null,
};
static getDerivedStateFromProps(nextProps, prevState) {
// 在这个方法中,您可以根据新的 props 更新状态
if (nextProps.value !== prevState.prevPropsValue) {
return { prevPropsValue: nextProps.value };
}
return null; // 不需要更新状态
}
render() {
return <div>Previous Props Value: {this.state.prevPropsValue}</div>;
}
}
export default MyComponent;
在这个示例中,当组件接收到新的 value
属性时,getDerivedStateFromProps
方法会将这个新属性的值与之前的状态进行比较。如果不同,就会返回一个新的状态对象,将新的属性值存储在 prevPropsValue
中。如果相同,就返回 null
,表示不需要更新状态。
请注意,getDerivedStateFromProps
是一个静态方法,因此它没有访问组件实例的 this
。它接收两个参数:nextProps
(新的属性值)和prevState
(之前的状态值)。您可以使用这些参数来判断是否需要更新状态。
然而,需要注意的是,尽管 getDerivedStateFromProps
在某些情况下可以用于更新状态,但在大多数情况下,更推荐使用受控组件或其他状态管理方法,以保持组件的可预测性和可维护性。
getSnapShotBeforeUpdate 的使用场景
<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
}
// height 为 getSnapshotBeforeUpdate 的返回值
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>
2.6.5 重要的钩子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送 ajax 请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
2.6.6 即将废弃的钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
现在使用会出现警告,下一个大版本需要加上 UNSAFE_
前缀才能使用,以后可能会被彻底废弃,不建议使用。
2.7 虚拟 DOM 与 DOM Diffing 算法
2.7.1 效果
需求:验证虚拟DOM Diffing算法的存在
<script type="text/babel">
class Time extends React.Component {
state = {date: new Date()}
componentDidMount() {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
render() {
return (
<div>
<h1>hello</h1>
<input type="text"/>
<span>
现在是:{this.state.date.toTimeString()}
<input type="text"/>
</span>
</div>
)
}
}
ReactDOM.render(<Time/>, document.getElementById('test'))
</script>
2.7.2 基本原理图
2.7.3 key 的作用
虚拟 DOM 中 key 的作用:
- 简单的说: key 是虚拟 DOM 对象的标识, 在更新显示时 key 起着极其重要的作用。
- 详细的说: 当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟DOM】与【旧虚拟DOM】的 diff 比较,比较规则如下:
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 中内容没变, 直接使用之前的真实 DOM
- 若虚拟 DOM 中内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
- 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
- 根据数据创建新的真实DOM,随后渲染到到页面
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
遍历列表时,key 最好不要用 index,用 index 作为 key 可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新 ==> 界面有问题。
- 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
开发中如何选择key?:
- 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
- 如果确定只是简单的展示数据,用 index 也是可以的。
<script type="text/babel">
/*
慢动作回放----使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
*/
class Person extends React.Component {
state = {
persons: [
{id: 1, name: '小张', age: 18},
{id: 2, name: '小李', age: 19},
]
}
add = () => {
const {persons} = this.state
const p = {id: persons.length + 1, name: '小王', age: 20}
this.setState({persons: [p, ...persons]})
}
render() {
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj, index) => {
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj) => {
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/>
</li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>, document.getElementById('test'))
</script>
3. React 应用(基于 React 脚手架)
3.1 使用 create-react-app 创建 react 应用
3.1.1 react 脚手架
- xxx 脚手架: 用来帮助程序员快速创建一个基于 xxx 库的模板项目
- 包含了所有需要的配置(语法检查、jsx 编译、devServer…)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
- react 提供了一个用于创建 react 项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
3.1.2 创建项目并启动
- 全局安装:npm i -g create-react-app
- 切换到想创项目的目录,使用命令:create-react-app hello-react
- 进入项目文件夹:cd hello-react
- 启动项目:npm start
3.1.3 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 ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js
--- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
3.1.4 样式的模块化
样式为什么需要模块化?看一个例子
Hello 组件(Hello/index.jsx)
import React,{Component} from 'react'
import './index.css'
export default class Hello extends Component{
render(){
return <h2 className="title">Hello,React!</h2>
}
}
Welcom 组件(Welcome/index.jsx)
import React, {Component} from 'react'
import './index.css'
export default class Welcome extends Component {
render() {
return <h2 className="title">Welcome</h2>
}
}
两个组件都引入了各自目录下的 index.css ,且两个样式文件存在 class 冲突,最后组件会统一打包成一个文件,其中一个样式会被另外一个样式覆盖,所以样式文件需要模块化。
解决样式冲突,我们可以利用 less 的嵌套关系来解决,或者使用以下的模块化方式,两种方式都可以
样式文件模块化方式
- 先将模块下的 index.css 更名为 index.module.css (必须加 module)
-
import React,{Component} from 'react' import hello from'./index.module.css' export default class Hello extends Component{ render(){ return <h2 className={hello.title}>Hello,React!</h2> } }
3.1.5 功能界面的组件化编码流程(通用)
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件
- 交互(从绑定事件监听开始)
- 动态显示初始化数据
3.2 组件的组合使用 - TodoList
功能: 组件化实现此功能
1. 显示所有 todo 列表
2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
App.jsx
import React, {Component} from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
import './App.css'
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'
import './index.css'
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>
)
}
}
Item.jsx
import React, {Component} from 'react'
import './index.css'
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>
)
}
}
List.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
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>
)
}
}
Footer.jsx
import React, {Component} from 'react'
import './index.css'
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>
)
}
}
4. React ajax
5. React 路由
6. React UI 组件库
流行的开源 React UI 组件库有以下两个:
material-ui (国外)
- 官网: http://www.material-ui.com/#/
- github: https://github.com/callemall/material-ui
ant-design (国内蚂蚁金服)
- 偏向于后台系统的开发
- 官网: https://ant.design/index-cn
- Github: https://github.com/ant-design/ant-design/
7. redux
8. 项目打包运行
npm run start
运行的是开发服务器。
npm run build
会将项目打包,打包后会生成 build 文件夹。
serve
可以根据某个文件夹,作为服务器运行,端口 5000
- 安装:
npm i serve -g
- 启动:
serve [目录]