1. React入门 ## 1.1. React基本认识
## 1.2. React基本使用
## 1.3. JSX的理解和使用
## 1.4. 模块与模块化, 组件与组件化的理解
2. React组件化编程 ## 2.1. 组件的定义与使用
## 2.2. 组件的3大属性: state, props, refs
## 2.3. 组件中的事件处理
## 2.4. 组件的组合使用
## 2.5. 组件收集表单数据
## 2.6. 组件的生命周期
## 2.7. 虚拟DOM与DOM diff算法
## 2.8. 命令式编程与声明式编程
1. React入门 1.1. React的基本认识 1). Facebook开源的一个js库
2). 一个用来动态构建用户界面的js库
3). React的特点
Declarative(声明式编码)
Component-Based(组件化编码)
Learn Once, Write Anywhere(支持客户端与服务器渲染)
高效
单向数据流
4). React高效的原因
虚拟(virtual)DOM, 不总是直接操作DOM(批量更新, 减少更新的次数)
高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域)
1.2. React的基本使用 相关 js 库
react.js: React 的核心库
react-dom.js: 提供操作 DOM 的 react 扩展库
babel.min.js: 解析 JSX 语法代码转为纯 JS 语法代码的库
在页面中导入 js
1 2 3 <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
编码
1 2 3 4 5 6 7 8 9 <script type="text/babel"> //必须声明 babel //创建虚拟 DOM 元素 const vDom = <h1>Hello React</h1> //千万不要加引号 //渲染虚拟 DOM到页面真实 DOM 容器中 ReactDOM.render(vDom, document.getElementById('test')) </script>
1.3. JSX的理解和使用 1). 理解
* 全称: JavaScript XML
* react定义的一种类似于XML的JS扩展语法: XML+JS
* 作用: 用来创建react虚拟DOM(元素)对象
2). 编码相关
* js中直接可以套标签, 但标签要套js需要放在{}中
* 在解析显示js数组时, 会自动遍历显示
* 把数据的数组转换为标签的数组:
var liArr = dataArr.map(function(item, index){
return <li key={index}>{item}</li>
})
3). 注意:
* 标签必须有结束
* 标签的class属性必须改为className属性
* 标签的style属性值必须为: {{color:'red', width:12}}
/**
*
* jsx 语法规则
* 1. 定义虚拟DOM时, 不要写引号
* 2.标签中混入js表达式时要用 {}, if, for 都不是表达式。能返回一个值的 东西就是表达式。表达式可以放到 赋值符号右边。
* 3.样式的类名指定不要用class,要用 className, class 是关键字
* 4.内联样式,要用 style={{key:value}}的形式, 最外面的花括号是和2中一样的,里面的那个花括号 是js中 对象的写法。
* 5. jsx 只能有一个 根标签
* 6.标签必须闭合, jsx里面标签会转换为html标签
* 7标签首字母,小写字母开头则转换为html同名元素,如果html中没有就会报错。如果首字母大写,react会去渲染组件,如果没有就报错。
*
* 8.函数式组件,函数名 必须 首字母 大写,,参考第7条
* 9,组件必须用标签的方式 放到 render 方法里面, 标签必须闭合。
*
*/
/**
* js 中类相关的知识点
* 1. 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
* 2. 子类必须调用父类的构造器,也就是必须调用super来初始化父类。
* 3.类中所定义的方法 都是放到了类的原型 对象上,供实例去使用。
*
*
*
*/
1.4. 几个重要概念理解 1). 模块与组件 1. 模块:
理解: 向外提供特定功能的js程序, 一般就是一个js文件
为什么: js代码更多更复杂
作用: 复用js, 简化js的编写, 提高js运行效率
2. 组件:
理解: 用来实现特定功能效果的代码集合(html/css/js)
为什么: 一个界面的功能太复杂了
作用: 复用编码, 简化项目界面编码, 提高运行效率
2). 模块化与组件化 1. 模块化:
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
2. 组件化:
当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
2. react组件化开发 2.1. 基本理解和使用 1). 自定义的标签: 组件类(函数)/标签
2). 创建组件类
//方式1: 无状态函数(简单组件, 推荐使用) 没有状态的称为简单组件
function MyComponentFunction(props) {
return <h2>我是函数定义的组件,适用于 简单组件 的定义</h2>
}
//方式2: ES6类语法(复杂组件, 推荐使用) 有状态的称为复杂组件
class MyComponentClass extends React.Component {
render () {
// render 是放在哪里的? MyComponentClass 的原型对象上的,供实例使用。
// render 中的 this 是谁? MyComponentClass 实例对象。 <=> MyComponentClass 组件实例对象
<h2>我是类定义的组件,适用于 复杂组件 的定义</h2>
}
}
3). 渲染组件标签
// 1. react 解析组件标签找到 MyComponentFunction 组件
// 2. 发现组件是使用函数定义的,随后调用该函数,将返回的 虚拟DOM转为真是DOM,随后渲染页面
//ReactDOM.render(<MyComponentFunction></MyComponentFunction>, document.getElementById("test"))
ReactDOM.render(<MyComponentFunction/>, document.getElementById("test"))
//ReactDOM.render(<MyComponentClass></MyComponentClass>, document.getElementById("test"))
ReactDOM.render(<MyComponentClass />, document.getElementById("test"))
// 1. react 解析组件标签找到 MyComponentClass 组件
// 2. 发现组件是使用 类 定义的,随后 new 出来该实例,并通过 该实例调用原型上的render 方法
// 3. 将render 返回的虚拟DOM转换为真实DOM,渲染页面
4). ReactDOM.render()渲染组件标签的基本流程
React内部会创建组件实例对象/调用组件函数, 得到虚拟DOM对象
将虚拟DOM并解析为真实DOM
插入到指定的页面元素内部
2.2. 组件的3大属性: state 1. 组件被称为"状态机", 页面的显示是根据组件的state属性的数据来显示
2. 初始化指定:
constructor() {
super()
this.state = {
stateName1 : stateValue1,
stateName2 : stateValue2
}
}
3. 读取显示:
this.state.stateName1
4. 更新状态-->更新界面 :
this.setState({stateName1 : newValue})
this.state 继承自 父类 React.Component
后续 hooks 也可以让 函数式组件 有 stage。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <script type="text/babel" charset="utf-8"> class Weather extends React.Component { constructor(props) { super(props); this.state = { isHot: true, } } render(){ return ( <h2>今天天气很 {this.state.isHot? "炎热": "凉爽"}</h2> ) } } ReactDOM.render(<Weather/>, document.getElementById("test")) </script> <script type="text/babel" charset="utf-8"> class Weather extends React.Component { constructor(props) { super(props); this.state = { isHot: true, } this.changeWeather = this.changeWeather.bind(this) // 绑定 this, 这里重新赋值了一个this.changeWeather,已经不是之前的 原型链上的 this.changeWeather 方法了。 } render(){ return ( <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot? "炎热": "凉爽"}</h2> ) } changeWeather(){ // changeWeather 函数放到 类里面了 // changeWeather 放在哪里, weather 的原型对象上, 供实例使用 // 由于 changeWeather 是作为onClick的回调,所以不是通过实例调用的,是直接调用的 // 就是说 把 this.changeWeather 这个函数名 重新赋值给了另外一个变量,然后 另外一个变量直接调用了。 // const x = this.changeWeather // x() // 类似上面这2行。 // this.state = {isHot: !this.state.isHot} state 不能直接更改 // 直接更改不会 改变页面变化的 const isHot = this.state.isHot console.log(this) this.setState({isHot: !isHot}) } } ReactDOM.render(<Weather/>, document.getElementById("test")) </script>
经过简化之后,去掉了 构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script type="text/babel" charset="utf-8"> class Weather extends React.Component { state = { // 这里时间给 实例对象 添加一个 成员属性。 isHot: true, } render(){ return ( <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot? "炎热": "凉爽"}</h2> ) } changeWeather = ()=>{ // 箭头函数,里面的this 就是 外面的weather类实例对象 const isHot = this.state.isHot this.setState({isHot: !isHot}) // 这里更新 动作 是一个合并的动作,不会把state 对象里面的 其他值给弄没了。 } } ReactDOM.render(<Weather/>, document.getElementById("test")) </script>
2.2. 组件的3大属性: props 所有组件标签的属性的集合对象
给标签指定属性, 保存外部数据(可能是一个function)
在组件内部读取属性: this.props.propertyName
作用: 从目标组件外部向组件内部传递数据
对props中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>
html 标签 属性, 就对应到 react 中的 prop了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <script type="text/babel" charset="utf-8"> class Person extends React.Component { render(){ return ( <ul> <li>姓名:{this.props.name}</li> <li>性别:{this.props.sex}</li> <li>年龄:{this.props.age}</li> </ul> ) } } // html 标签 属性, 就对应到 react 中的 prop了 ReactDOM.render(<Person name="bright" sex="男" age="20"/>, document.getElementById("test")) ReactDOM.render(<Person name="annie" age="18" sex="女"/>, document.getElementById("test1")) const p = {name: "hans", age: 18, sex: "nan"} ReactDOM.render(<Person {...p}/>, document.getElementById("test2")) //这里使用 ... 三个点 展开运算符,只能用于标签属性展开 </script>
对标签属性进行 限制,react 15.5 之后的版本就不能如下这么使用了,
1 2 3 4 5 6 7 Person.propTypes = { name: React.PropTypes.string, age: React.PropTypes.number, sex:React.PropTypes.string }
使用 prop-types.js 来进行标签属性限制
1 2 3 4 <script type="text/javascript" src="../js/react.development.js"></script> 引入这个 全局多一个 React 对象 <script type="text/javascript" src="../js/react-dom.development.js"></script> 引入这个 全局多一个 ReactDOM 对象 <script type="text/javascript" src="../js/prop-types.js"></script> 引入这个全局多一个 PropTypes 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <script type="text/babel" charset="utf-8"> class Person extends React.Component { render() { const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age + 1}</li> </ul> ) } } // 对传递的标签属性 进行 类型的限制,如果整形,字符串型。 // 对 默认值的限制 // 对必填属性的限制 Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, sex: PropTypes.string, speak:PropTypes.func,// 限制为函数类型 } Person.defaultProps = { sex: 'null', age: 0 } // html 标签 属性, 就对应到 react 中的 prop了 ReactDOM.render(<Person name="bright" sex="男" age={22}/>, document.getElementById("test")) ReactDOM.render(<Person name="annie" age={15} sex="女"/>, document.getElementById("test1")) ReactDOM.render(<Person name="annie"/>, document.getElementById("test1")) // 这里再来个 标签,age , sex 都没有设置,那么 就会使用到 默认值 Person.defaultProps 中的设置的值。 ReactDOM.render(<Person />, document.getElementById("test3")) //Warning: Failed prop type: The prop `name` is marked as required in `Person`, but its value is `undefined`. // 这里如果 标签属性 name 都没设置,那么会 报警告的,因为 我们现在了name是 必填的 name: PropTypes.string.isRequired, //这里 speak 限制是func 类型,这里用双引号表示字符串类型,这是不对的,会报 警告的。 ReactDOM.render(<Person name="annie" age={15} sex="女" speak="speak"/>, document.getElementById("test1")) //Warning: Failed prop type: Invalid prop `speak` of type `string` supplied to `Person`, expected `function`. // 正确的做法如下: 使用 {speak} 这样的方式设置标签speak的属性 ReactDOM.render(<Person name="annie" age={15} sex="女" speak={speak}/>, document.getElementById("test1")) function speak(){ console.log("speak function") } const p = {name: "hans", age: 18, sex: "nan"} ReactDOM.render(<Person {...p}/>, document.getElementById("test2")) //这里使用 ... 三个点 展开运算符,只能用于标签属性展开 </script>
完成对标签属性的限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 对传递的标签属性 进行 类型的限制,如果整形,字符串型。 // 对 默认值的限制 // 对必填属性的限制 Person.propTypes = { name: PropTypes.string.isRequired, //限制name必须的,且是字符串 age: PropTypes.number, // 限制年龄是 数字 sex: PropTypes.string, // 限制性别是字符串 speak:PropTypes.func,// 限制为函数类型 } Person.defaultProps = { sex: 'null', age: 0 }
标签属性 props 是只读的,不能修改的
1 TypeError: "name" is read-only
props的简写方式,直接放到类定义体里面,前面加上 static 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Person extends React.Component { render() { const {name, age, sex} = this.props // this.props.name="12312" 属性是只读的,不能修改 return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age + 1}</li> </ul> ) } // 对传递的标签属性 进行 类型的限制,如果整形,字符串型。 // 对 默认值的限制 // 对必填属性的限制 static propTypes = { name: PropTypes.string.isRequired, //限制name必须的,且是字符串 age: PropTypes.number, // 限制年龄是 数字 sex: PropTypes.string, // 限制性别是字符串 speak:PropTypes.func,// 限制为函数类型 } // 如果不加 static 就会是 加到 类的实例对象上了,例如之前的 stage . // 这里 需要加上 static,这个props 是 属于 这个 类的,而不是 实例对象。 static defaultProps = { sex: 'null', age: 0 } }
类中的构造器到底有什么作用呢? 传入了props,如果不传给super会这么样呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Weather extends React.Component { constructor(props) { super(props); this.state = { isHot: true, } this.changeWeather = this.changeWeather.bind(this) // 绑定 this, 这里重新赋值了一个this.changeWeather,已经不是之前的 原型链上的 this.changeWeather 方法了。 } 构造函数的作用: 官网描述, 通过给 this.state 赋值对象来初始化 内部stage的。 为事件函数绑定实例的。 在构造函数中 不要调用 setStage() 方法, 如果你的组件需要使用 stage,直接在构造函数中 为 this.state 赋值。 class Person extends React.Component { constructor(props) { super(props); // 构造器是否接收 props, 是否传递给super,取决于: 是否希望在构造器中通过 this 访问 props // 就是说这里没有传给 super 那么在后面的 构造器代码中 就不能使用 this.props 访问了。发现 在 render 方法还是能继续 使用 this.props 的。 console.log(props) } 如果不初始化 stage 或 不进行方法绑定,则不需要为 react组件实现构造函数。 在react 组件挂载之前,会调用他的构造函数,在为 react.component子类实现构造函数 时, 应在其他语句之前调用super(props),否则,this.props 在构造函数中可能出现未定义的bug。
函数式组件的 propsm, 函数式组件也只能 使用使用 props, 利用函数的参数来传入。 其他2个 stage, refs 都不能使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function PersonFunction(props){ const {name, age, sex} = props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age + 1}</li> </ul> ) } ReactDOM.render(<PersonFunction name="annie" age={15} sex="女"/>, document.getElementById("test2"))
2.2. 组件的3大属性: refs 组件内包含ref属性的标签元素的集合对象
给操作目标标签指定ref属性, 打一个标识
在组件内部获得标签对象: this.refs.refName(只是得到了标签元素对象)
作用: 找到组件内部的真实dom元素对象, 进而操作它
普通字符串形式的 ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script type="text/babel" charset="utf-8"> class Person extends React.Component { showDataLeft = ()=>{ console.log("input1 value:", this.refs.input1.value) } showDataRight = ()=>{ console.log("input2 value:", this.refs.input2.value) } render() { return ( <div> // 过时的 api,这里 使用 字符串 做 定义 refs了。后续不推荐使用了 <input ref="input1" type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref="input2" onBlur={this.showDataRight}type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Person />, document.getElementById("test")) </script>
回调 形式的ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script type="text/babel" charset="utf-8"> class Person extends React.Component { showDataLeft = ()=>{ console.log("input1 value:", this.input1.value) } showDataRight = ()=>{ console.log("input2 value:", this.input2.value) } render() { return ( <div> <input ref={(param)=>{this.input1 = param}} type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={(param)=>{this.input2 = param}} onBlur={this.showDataRight}type="text" placeholder="失去焦点提示数据"/> </div> //这里的回调 还能简写如下 <div> <input ref={ param =>this.input1 = param } type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={param =>this.input2 = param } onBlur={this.showDataRight}type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Person />, document.getElementById("test")) </script>
这种使用内联的方式 回调 ref ,这个回调方法会执行2次。第一次 会传入参数是 null的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <script type="text/babel" charset="utf-8"> class Person extends React.Component { state = { // 这里时间给 实例对象 添加一个 成员属性。 isHot: true, } showDataLeft = () => { console.log("input1 value:", this.input1.value) } showDataRight = () => { console.log("input2 value:", this.input2.value) } changeWeather = () => { const isHot = this.state.isHot this.setState({isHot: !isHot}) // 这里更新 动作 是一个合并的动作,不会把state 对象里面的 其他值给弄没了。 } render() { return ( <div> <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot ? "炎热" : "凉爽"}</h2> <br/> <input ref={(param)=>{this.input1 = param; console.log("input111111111111", param)}} type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={param => this.input2 = param} onBlur={this.showDataRight} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Person/>, document.getElementById("test")) </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <script type="text/babel" charset="utf-8"> class Person extends React.Component { state = { // 这里时间给 实例对象 添加一个 成员属性。 isHot: true, } showDataLeft = () => { console.log(this) console.log("input1 value:", this.input1.value) } showDataRight = () => { console.log("input2 value:", this.input2.value) } changeWeather = () => { const isHot = this.state.isHot this.setState({isHot: !isHot}) // 这里更新 动作 是一个合并的动作,不会把state 对象里面的 其他值给弄没了。 } input2 = (param) => { this.input2 = param; console.log("input2222222222", param) } render() { return ( <div> <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot ? "炎热" : "凉爽"}</h2> <br/> // 这个还是内联方式回调的。这样会调用2次,第一次就会 把这个值设置为null了,第2次调用就会传入当前标签 <input ref={(param) => {this.input1 = param;}} type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={this.input2} onBlur={this.showDataRight} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Person/>, document.getElementById("test")) </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <script type="text/babel" charset="utf-8"> class Person extends React.Component { state = { // 这里时间给 实例对象 添加一个 成员属性。 isHot: true, } showDataLeft = () => { console.log(this) console.log("input1 value:", this.input1.value) } showDataRight = () => { console.log("input2 value:", this.input2.value) } changeWeather = () => { const isHot = this.state.isHot this.setState({isHot: !isHot}) // 这里更新 动作 是一个合并的动作,不会把state 对象里面的 其他值给弄没了。 } // 注意这里的 inputRef 名称 千万别和 里面的 input1 命令成一样的了!!!!!!!!!! input1ref = (param) => { this.input1 = param; console.log("11", param) } input2ref = (param) => { this.input2 = param; console.log("22", param) } render() { return ( <div> <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot ? "炎热" : "凉爽"}</h2> <br/> // 想这种的 把回调绑定到类上的,之后刷新组件也只是会调用一次的。 <input ref={this.input1ref} type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={this.input2ref} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Person/>, document.getElementById("test")) </script>
使用 React.createRef();创建ref,这是官方最推荐的,这个在reac 16.3 之后版本才有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <!--<script type="text/javascript" src="../js/react.development.js"></script>--> <script src="https://unpkg.com/react@16.3.0/umd/react.development.js"></script> <!--<script type="text/javascript" src="../js/react-dom.development.js"></script>--> <script src="https://unpkg.com/react-dom@16.3.0/umd/react-dom.development.js"></script> <script type="text/javascript" src="../js/prop-types.js"></script> <!-- <script type="text/javascript" src="../js/babel.min.js" charset="utf-8"></script>--> <script src="https://unpkg.com/@babel/standalone@7.19.0/babel.min.js"></script> <script type="text/babel" charset="utf-8"> class Person extends React.Component { state = { // 这里时间给 实例对象 添加一个 成员属性。 isHot: true, } showDataLeft = () => { console.log(this) console.log("input1 value:", this.input1.value) } showDataRight = () => { console.log("input2 value:", this.input2.value) } changeWeather = () => { const isHot = this.state.isHot this.setState({isHot: !isHot}) // 这里更新 动作 是一个合并的动作,不会把state 对象里面的 其他值给弄没了。 } input1ref = (param) => { this.input1 = param; console.log("11", param) } input2ref = (param) => { this.input2 = param; console.log("22", param) } input3ref = React.createRef(); render() { return ( <div> <h2 onClick={this.changeWeather}>今天天气很 {this.state.isHot ? "炎热" : "凉爽"}</h2> <br/> <input ref={this.input1ref} type="text" placeholder="点击按钮提示数据"/> <button ref="button1" onClick={this.showDataLeft}>点我</button> <input ref={this.input2ref} type="text" placeholder="失去焦点提示数据"/> <br/> <input ref={this.input3ref} type="text" placeholder="input3ref"/> </div> ) } } ReactDOM.render(<Person/>, document.getElementById("test")) </script>
2.3. 组件中的事件处理 1. 给标签添加属性: onXxx={this.eventHandler}
2. 在组件中添加事件处理方法
eventHandler(event) {
}
3. 使自定义方法中的this为组件对象
在constructor()中bind(this)
使用箭头函数定义方法(ES6模块化编码时才能使用)
4. 事件监听
绑定事件监听
事件名
回调函数
触发事件
用户对对应的界面做对应的操作
编码
2.4. 组件的组合使用 1)拆分组件: 拆分界面,抽取组件
2)实现静态组件: 使用组件实现静态页面效果
3)实现动态组件
① 动态显示初始化数据
② 交互功能(从绑定事件监听开始)
2.5. 组件收集表单数据 受控组件
非受控组件
受控组件,可以省略ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <script type="text/babel" charset="utf-8"> class Login extends React.Component { handleSubmit = (event) => { event.preventDefault() // 阻止表单默认提交 console.log(this.state) } state = { username: "", password: "" } saveUsername = (event) => { this.setState({username: event.target.value}) } savePassword = (event) => { this.setState({password: event.target.value}) } render() { return ( <form action="" onSubmit={this.handleSubmit}> username<input onChange={this.saveUsername} type="text" name="username"/> password<input onChange={this.savePassword} type="text" name="password"/> <button>login</button> </form> ) } } ReactDOM.render(<Login/>, document.getElementById("test")) </script>
一个优化的写法,onchange 里面调用的一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <script type="text/babel" charset="utf-8"> class Login extends React.Component { handleSubmit = (event) => { event.preventDefault() // 阻止表单默认提交 console.log(this.state) } state = { username: "", password: "" } saveUsername = (event) => { this.setState({username: event.target.value}) } savePassword = (event) => { this.setState({password: event.target.value}) } save = (data) => { return (event) => { console.log(data, event.target.value) this.setState({[data]: event.target.value}) } } render() { return ( <form action="" onSubmit={this.handleSubmit}> username<input onChange={this.save("username")} type="text" name="username"/> password<input onChange={this.save("password")} type="text" name="password"/> <button>login</button> </form> ) } } ReactDOM.render(<Login/>, document.getElementById("test")) </script>
非受控组件,数据现用现取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script type="text/babel" charset="utf-8"> class Login extends React.Component { handleSubmit = (event) => { event.preventDefault() // 阻止表单默认提交 const {username, password} = this console.log(this, username.value, password.value) } render() { return ( <form action="" onSubmit={this.handleSubmit}> username<input ref={c => this.username = c} type="text" name="username"/> password<input ref={c => this.password = c} type="text" name="password"/> <button>login</button> </form> ) } } ReactDOM.render(<Login/>, document.getElementById("test")) </script>
2.6. 组件的生命周期 1. 组件的三个生命周期状态:
Mount:插入真实 DOM
Update:被重新渲染
Unmount:被移出真实 DOM
2. 生命周期流程:
* 第一次初始化显示: ReactDOM.render(<Xxx/>, containDom)
constructor()
componentWillMount() : 将要插入回调
render() : 用于插入虚拟DOM回调
componentDidMount() : 已经插入回调
* 每次更新state: this.setState({})
componentWillReceiveProps(): 接收父组件新的属性
componentWillUpdate() : 将要更新回调
render() : 更新(重新渲染)
componentDidUpdate() : 已经更新回调
* 删除组件: ReactDOM.unmountComponentAtNode(div): 移除组件
componentWillUnmount() : 组件将要被移除回调
3. 常用的方法
render(): 必须重写, 返回一个自定义的虚拟DOM
constructor(): 初始化状态, 绑定this(可以箭头函数代替)
componentDidMount() : 只执行一次, 已经在dom树中, 适合启动/设置一些监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <script type="text/babel" charset="utf-8"> class Test extends React.Component { constructor(props) { super(props); console.log("this is constructor function") } state = { count: 0 } add = () => { let {count} = this.state; count += 1 this.setState({count}) } componentWillMount(){ console.log("this is componentWillMount function") } componentDidMount() { console.log("this is componentDidMount function") } componentWillUnmount(){ console.log("this is componentWillUnmount function") } shouldComponentUpdate(){ console.log("this is shouldComponentUpdate function") return true } componentWillUpdate(){ console.log("this is componentWillUpdate function") } componentDidUpdate(){ console.log("this is componentDidUpdate function") } render() { console.log("this is render function") return ( <div> <h2>逐渐+1 {this.state.count}</h2> <button onClick={this.add}>+1</button> </div> ) } } ReactDOM.render(<Test/>, document.getElementById("test")) </script>
2.7. 虚拟DOM与DOM diff算法 1). 虚拟DOM是什么? 一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
如果只是更新虚拟DOM, 页面是不会重绘的
2). Virtual DOM 算法的基本步骤 用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把差异应用到真实DOM树上,视图就更新了
3). 进一步理解 Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
2.8. 命令式编程与声明式编程 声明式编程
只关注做什么, 而不关注怎么做(流程), 类似于填空题
命令式编程
要关注做什么和怎么做(流程), 类似于问答题
var arr = [1, 3, 5, 7]
// 需求: 得到一个新的数组, 数组中每个元素都比arr中对应的元素大10: [11, 13, 15, 17]
// 命令式编程
var arr2 = []
for(var i =0;i<arr.length;i++) {
arr2.push(arr[i]+10)
}
console.log(arr2)
// 声明式编程
var arr3 = arr.map(function(item){
return item +10
})
// 声明式编程是建立命令式编程的基础上
// 数组中常见声明式方法
map() / forEach() / find() / findIndex()