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 库

  1. react.js: React 的核心库
  2. react-dom.js: 提供操作 DOM 的 react 扩展库
  3. 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()