0%

react-note

1. React简介

1.1 React官网

1.2 React的特点

  1. Declarative(声明式编程)
  2. Component-Based(组件化编码)
  3. Learn Once,Write Anywhrer(支持客户与服务器渲染)
  4. 高效
  5. 单向数据流

1.3 React高效的原因

  1. 虚拟(virtual)DOM,不总是直接操作DOM
  2. DOM Diff算法,最小化页面重绘

2.前端三大主流框架

三大框架一大抄 vue.js抄袭Angular.js,Angular2反过来抄vue组件化

  • Angular.js:出来较早的前端的框架,学习曲线比较陡,NG1学习起来比较麻烦,NG2~NG5开始,进行了一些列的改革,也提供了组件化开发的概念;从NG2开始,也支持使用TS(TypeScript)进行编程
  • Vue.js:最火(关注的人比较多)的一门前端框架,它是中国人开发的,对我们来说,文档友好一些;
  • React.js:最流行(用的人比较多)的一门框架,因为它的设计很优秀

3.React与Vue对比

组件化方面

  1. 什么是模块化: 从代码的角度进 行分析的;把一些可复用的代码,抽离为单个模块;便于项目的维护和开发;
  2. 什么是组件化: 从UI界面的角度 来进行分析的;把一些可复用的UI元素,抽离为单独的组件;
  3. 组件化的好处: 随着项目规模的增大,手里的组件就越来越多;节省开发成本,便于把现有组件,拼接为一个完整的页面。
  4. Vue是如何实现组件化的: 通过 .vue文件,来创建对应的组件;
    • template 结构
    • script 行为
    • style 样式
  5. React是如何让实现组件化的: React中有组件化概念,但是没有像vue这样的组件模板文件;React中,一切都是以js表现的;因此要学习React,JS要合格;ES6和ES7(async和await)要会用。

开发团队方面

  • React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
  • Vue:第一版。主要是作者尤雨溪专门进行维护的,当Vue更新到2.x版版本后,也有了一个以尤雨溪为主的开源小团队,进行相关维护;

社区方面

  • 在社区方面,React由于诞生的较早,所以社区比较强大,一些常见的问题、坑、最优解决方案,文档、博客在社区中都是可以很方便就能找到的
  • Vue是近两年才获取来的,所以,在社区方面相对React来说,要小一些,可能有一些坑,还没人踩过。

移动APP开发体验方面

  • Vue,结合Weex这门技术,提供了迁移到移动端App开发的体验(Weex,目前指示一个小玩具,并没有很大的成功案例)
  • React,结合ReactNative,也提供了迁移到移动App的开发体验(RN用到最多,也是最火最流行的)

4. React的基本使用

2.1 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>01.React的基本使用</title>
</head>
<body>
<div id="test"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel"> //告诉babel解析代码
// 1.创建虚拟dom元素对象
var vDom = <h1>Hello React!</h1> //不是字符串
// 2.将虚拟DOM渲染到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById('test'));
</script>
</body>
</html>

2.2相关库

react.js:React核心库

react-dom.js:供操作DOM的react扩展库

babel.min.js:解析JSX语法代码转为纯JS语法代码库

5. React JSX

1
const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

JSX 可以生成 React “元素”。我们将在下一章节中探讨如何将这些元素渲染为 DOM。下面我们看下学习 JSX 所需的基础知识。

3.1 虚拟DOM

React提供了一些API来创建一钟“特别”的一般js对象

  • ```js
    var element = React.createElement(‘h1’,{id:’mytitle’},’hello’)
    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

    - 上面创建的就是一个简单的虚拟DOM对象

    虚拟DOM对象最终会被React转换为真实的DOM

    我们编码时基本只需要操作react虚拟DOM相关数据,react会转换为真实DOM变化而更新界面

    #### 3.2 JSX

    1. 全称: JavaScript XML

    2. react定义的一种类似于XML的JS扩展yy垡:XML+JS

    3. 作用:用来创建react虚拟DOM(元素)对象

    > var ele = <h1>Hello JSX!</h1>
    >
    > 注意1:它不是字符串,也不是HTML/XML标签
    >
    > 注意2:它最终产生的标签就是一个JS对象

    4. 标签名任意:HTML标签或其他标签

    5. 标签属性任意:HTML标签属性或其他

    6. 基本语法规则

    > 遇到<开头的代码,以标签语法解析:html同名标签转换为html标签元素,其他标签需要特别解析
    >
    > 遇到{开头的代码,以js语法解析:标签钟js代码必须用{}包含

    7. babel.js的作用

    > 浏览器不能直接解析jsx代码,需要babel转译为js代码才能被浏览器识别运行
    >
    > 只要用了JSX,都要加上type=“test/babel",声明需要babel处理

    #### 3.3 渲染虚拟DOM(元素)

    1. 语法:`ReactDOM.render(virtualDOM,containerDOM)`
    2. 作用:将虚拟DOM元素渲染到页面中的真实容器DOM显示
    3. 参数说明:
    - 参数一:纯js或jsx创建的虚拟dom对象
    - 参数二:用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

    #### 3.4 创建虚拟DOM的两种方式

    1. 纯JS(一般不用)

    ```js
    React.createElement('h1',{id:'mytitle'},'title')
  1. JSX:

    1
    <h1 id='myTitle'>{title}</h1>

3.5 JSX练习

需求:动态展示列表数据

6. 模块与组件和模块化组件的理解

4.1 模块

  1. 理解:向外提供特定功能的js程序,一般就是一个js文件
  2. 为什么:js代码更多更复杂
  3. 作用:复用js,简化js的编写,提高js的运行效率

4.2 组件

  1. 理解: 用来实现特定(局部)功能效果的代码集合(html/js/css)
  2. 为什么:一个界面的功能更复杂
  3. 作用:提高代码的复用性,便于程序可维护性,提高运行效率

4.3 模块化

当应用的js都以模块来编写,这个应用就是一个模块化应用

4.4 组件化

当应用是以多组件的方式实现,这个应用就是一个组件化应用

7. React面向组件编程

5.1 自定义组件(Component)

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>03.React面向组件编程 Demo</title>
</head>
<body>
<div id="example1"></div>
<div id="example2"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
// 1.定义组件
// 方式 1.工厂函数组件(简单组件)
function MyComponent() {
return <h2>工厂函数组件(简单组件)</h2>;
}
// 方式2.ES6类组件(复杂组件)
class MyComponent2 extends React.Component{
render() {

return <h2>ES6类组件</h2>
}
}

// 2.渲染组件标签
ReactDOM.render(<MyComponent/>, document.getElementById('example1'));
ReactDOM.render(<MyComponent2/>, document.getElementById('example2'));
</script>
</body>
</html>

5.2 组件的三大属性 state

  1. state是组件对象最重要的属性,值是对象(可以包含多个数据,类似Vue中vm实例的data属性)
  2. 组件被称为”状态机“,通过更新组件的state来更新对应的页面显示(重新渲组件)
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>02.组件的三大属性state</title>
</head>
<body>
<div id="example1"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
/*
需求:自定义组件,功能如下
1.显示h2标题,初始文本为:Hello React
2.点击标题更新:Hello World
*/
// 定义组件
class Hello extends React.Component {
// 初始化state
constructor(props) {
super(props);
this.state = {
isHello: false,
};
// 将新增的方法中的this指向组件对象
this.toHello = this.toHello.bind(this);
}

render() {
// 读取state值
const {isHello} = this.state;
return <h2 onClick={this.toHello}>{isHello ? 'Hello World' : 'Hello React'}</h2>
}

// 新添加的方法中的this指向默认不是组件对象
toHello() {
const isHello = !this.state.isHello;
// 更新状态
this.setState({
isHello
})
}
}

// 渲染组件标签
ReactDOM.render(<Hello/>, document.getElementById('example1'))
</script>
</body>
</html>

5.3 组件的三大属性 props

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>03.组件的三大属性props</title>
</head>
<body>
<div id="example1"></div>
<div id="example2"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/*
需求:自定义用来显示一个人员信息的组件,效果如页面,说明
1.如果性别没有指定,默认为 男
2.如果年龄没有指定,默认为 18
*/
// 1.定义组件

// function Person(props) { // 函数工厂
// return (
// <ul>
// <li>姓名:{props.name}</li>
// <li>性别:{props.sex}</li>
// <li>年龄:{props.age}</li>
// </ul>
// )
// }

class Person extends React.Component { // 类的形式
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
)
}
}

// 2.指定属性默认值
Person.defaultProps = {
sex: '男',
age: 18
};
//3.对组件Props的属性值进行类型限定和必要性限定
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string
};

// 准备数据
const p1 = {
name: '张三',
sex: '男',
age: 20

};

const p2 = {
name: 'JACK',
};

// 2.渲染组件标签
/*
...的作用
1.打包
function(..as){} fn(1,2,3)
2.解包
const arr1 = [1,2,3] const arr2 = [6,...arr1,9]
*/
ReactDOM.render(<Person {...p1}/>, document.getElementById('example1'));
ReactDOM.render(<Person name={p2.name}/>, document.getElementById('example2'));
</script>
</body>
</html>

5.4 组件的三大属性 refs 与事件处理

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>04.组件的三大属性refs</title>
</head>
<body>
<div id="example"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/*
需求:自定义组件,功能说明如下
1.界面如果页面所示
2.点击按钮,提示第一个输入框的值
2.当第2个输入框试去焦点时,提示这个输入框中的值
*/
// 1.定义组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.showInput = this.showInput.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}

render() {
// <input type="text" ref="content"/>&nbsp; 不推荐使用
// <input type="text" ref={(input) => this.input = input}/> 将当前的input挂载到组件对象中
return (
<div>
<input type="text" ref={(input) => this.input = input}/>&nbsp;
<button onClick={this.showInput}>提示输入</button>
&nbsp;
<input type="text" placeholder="失去焦点提示内容" onBlur={this.handleBlur}/>
</div>
)
}

showInput() { //点击事件
// const input = this.refs.content;
// alert(input.value);
// console.log(input);
alert(this.input.value);
}

handleBlur(event) {
alert(event.target.value)
}
}

// 2.渲染组件标签
ReactDOM.render(<MyComponent/>, document.getElementById('example'));
</script>
</body>
</html>

5.5 组合组件的应用

Dmoe01

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>05.组合组件的使用</title>
</head>
<body>
<div id="example"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/**
* 数据保存在哪个组件当中
* 看数据是只有单个组件需要使用到还是多个组件都需要使用,
* 多个组件使用可以将数据设置在其共同的父组件当中,单个组件使用可以直接设置在组件内部
* 难点:在子组件中改变父组件的状态
* 子组件中不能直接改变父组件的状态
* 状态在哪个组件,更新状态的行为就应该在哪个组件中
* 解决:父组件定义函数,传递给子组件,子组件通过这个函数回调父组件
* 组件化编写功能流程
* 1.拆分组件
* 2.实现静态组件(只有静态页面,没有动态数据交互)
* 3.实现动态组件
* - 1.实现初始化数据显示
* - 2.实现交互功能
*/
// 定义组件
class App extends React.Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
todos: ['吃饭', '上班', '睡觉', '敲代码']
};
this.addTodo = this.addTodo.bind(this);
}

render() {
return (
<div>
<h2>Simple TODO List</h2>
<Add size={this.state.todos.length} func={this.addTodo}/>
<List todos={this.state.todos}/>
</div>
)
}

addTodo(todo) {
// this.state.todos.unshift(todo); //不允许操作
console.log(todo);
if (todo !== "" && todo !== null) {
const {todos} = this.state;
todos.unshift(todo);
// 修改状态
this.setState({
todos
})
} else {
alert("输入内容不能为空!")
}
}
}


class Add extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}

render() {
return (
<div>
<input type="text" ref={(input) => this.input = input}/>
<button onClick={this.handleClick}>Add #{this.props.size + 1}</button>
</div>
)
}

handleClick() {
// 子组件调用父组件
let val = this.input.value.trim();
// console.log(val);
this.props.func(val);
}
}

// 接收组件传递的数据
Add.propTypes = {
size: PropTypes.number.isRequired,
func: PropTypes.func.isRequired
};

class List extends React.Component {
constructor(props) {
super(props);
}

render() {
return (
<ul>
{
this.props.todos.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
)
}
}

// 接受父组件传递的数据
List.propTypes = {
todos: PropTypes.array.isRequired,
};

ReactDOM.render(<App/>, document.getElementById('example'))
</script>
</body>
</html>

Demo02

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
65
66
67
68
69
70
71
72
73
74
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>06.组合组件的使用-form</title>
</head>
<body>
<div id="example"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/*
需求:自定义包含表单组件
1.界面如下所示
2.输入用户密码后,点击登录提示输入信息
3.不提交表单

包含表单的组件分类
1.受控组件:表单项输入数据能自动收集成状态 --->vue 双向数据绑定 v-model
2.非受控组件:需要时才手动读取表单输入框中的数据
*/
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.NameChange = this.NameChange.bind(this);
this.state = {
name: '',
pwd: ''
}
}

render() {
return (
<form action="/test" onSubmit={this.handleSubmit}>
<label>用户名:</label>
<input type="text" value={this.state.value} onChange={this.NameChange}/>
<label>密码:</label>
<input type="password" value={this.state.pwd}
onChange={this.handleChange}/>
<button type="submit">登录</button>
</form>
)
}

handleSubmit(event) {
// 阻止事件默认行为
event.preventDefault();
alert("用户名:" + this.state.name + "\t" + "密码:" + this.state.pwd)
}

// 绑定state中的pwd
handleChange(event) {
// 读取输入框中的值更新pwd的状态
const pwd = event.target.value;
this.setState({pwd})
}

// 绑定state中的密码
NameChange(event) {
const name = event.target.value;
this.setState({name})
}

}

ReactDOM.render(<LoginForm/>, document.getElementById('example'))
</script>
</body>
</html>

8.组件的生命周期

8.1 React生命周期图谱

React生命周期图

示例:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>07.组件的生命周期</title>
</head>
<body>
<div id="example"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/prop-types/15.6.1/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
/*
需求:自定义组件
1.让指定的文本做显示/隐藏的动画
2.切换时间为2s
3.点击按钮从界面中移除组件页面
*/
class Life extends React.Component {
constructor(props) {
console.log("constructor");
super(props);
this.state = {
opacity: 1,
};
this.destroyComponent = this.destroyComponent.bind(this);
}

// {{opacity: opacity}} 外围大括号代表js语句块 内部大括号代表key-value
componentDidMount() { //组件挂在到Dom完毕之后
// 启动循环定时器
console.log("componentDidMount");
// this.intervalId = setInterval(function () {
// console.log("定时器");
// let {opacity} = this.state;
// opacity -= 0.1;
// if (opacity <= 0) {
// opacity = 1;
// }
// this.setState({
// opacity,
// })
// }.bind(this), 100);
}

destroyComponent() {
// 移除组件
ReactDOM.unmountComponentAtNode(document.getElementById('example'));
}

componentWillMount() {
console.log("componentWillMount");
}

componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate" + prevProps + prevState + snapshot);
}

componentDidCatch(error, errorInfo) {
console.log("componentDidCatch")
}


// 组件将要移除时
componentWillUnmount() {
// 清理定时器
clearInterval(this.intervalId);
}

render() {
console.log("render()");
const {opacity} = this.state;
return (
<div>
<h2 style={{opacity: opacity}}>{this.props.msg}</h2>
<button onClick={this.destroyComponent}>清除组件</button>
</div>
);
}
}

Life.propTypes = {
msg: PropTypes.string.isRequired
};

ReactDOM.render(<Life msg="React 很简单"/>, document.getElementById('example'));
</script>
</body>
</html>

8.2 生命周期流程:

  1. 第一次初始化渲染显示:ReactDOM.render()
    • constructor():创建对象初始化state
    • componentWillMount():将要插入回调
    • render():用于插入虚拟DOM
    • componentDidMount():已经插入回调
  2. 每次更新state:this.setState()
    • componentWillUpdate():将要更新回调
    • render():更新(重新渲染)
    • componentDidUdate():已经更新回调
  3. 移除组件:ReactDOM.unmountComponentAtNode(containerDom)
    • componentWillUnmount():组件将要被移除

9.虚拟DOM与Diff算法

9.1基本原理

创建虚拟DOM树(一般JS对象)—>真实DOM树—>绘制界面显示—>getState()更新状态—>重新创建虚拟DOM树—>新/旧树比较差异—>更新差异对应真实DOM—>局部界面重绘

9.2Diif算法

  • tree diff;

    新旧两颗DOM树,逐层对比的过程,就是Tree Diff;当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到

  • component diff;

    在进行Tree Diff的时候,每一层中,组件级别的对比,叫做Component Diff;

    如果对比前后,组件的类型相同,则暂时认为组件不要被更新

    如果对比前后,组件类型不同,则需要更新

  • element diff;

    在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做ElementDiff;

React diff

10. React应用

10.1 react 脚手架

  1. xxx脚手架:用于帮助程序猿快速创建一个基于xxx库的模板项目
  2. react提供了一个用于创建react项目的脚手架库:create-react-app
  3. 项目的整体技术架构为:react+webpack+es6+eslint
  4. 使用脚手架开发的项目的特点:模块化,组件化,工程化
1
2
3
4
npm i -g create-react-app // 全局安装create-react-app
create-react-app ${yourPrjName} //创建你自己的React项目
cd ${yourPrjName} // 进入你创建的项目目录
npm start // 编译并启动项目
  1. React组件间的通讯

11.1 Props传递

  1. 共同的数据放在父组件上,特有的数据放在自己的组件内部(state)
  2. 通过props可以传递一般数据和函数数据
  3. 一般数据—>父组件传递数据给子组件—>子组件读取数据
  4. 函数数据—>子组件传递数据给父组件—>子组件调用函数(回调函数)

11.2 使用消息订阅(subscribe)-发布(publish)机制

  1. 工具库PubSubJS

  2. 下载:

    1
    npm install pubsub-js --save
  3. 使用:

    1
    2
    3
    import PubSub from 'pubsub-js' //引入
    PubSub.subscribe(‘delete',function(data){}): //订阅
    PubSub.publish('delete',data) //发布消息

11.3 context方式

context是一个全局变量,像是一个大容器,在任何地方都可以访问到,我们可以把要通信的信息放在context上,然后在其他组件中可以随意取到;
但是React官方不建议使用大量context,尽管他可以减少逐层传递,但是当组件结构复杂的时候,我们并不知道context是从哪里传过来的;而且context是一个全局变量,全局变量正是导致应用走向混乱的罪魁祸首.

层级关系:App.jsx->List.jsx->ListItem.jsx

App.jsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
import List from './components/List';

const list = [
{
text: '题目一',
},
{
text: '题目二',
},
];
export default class App extends Component {
render() {
return (
<div>
<List
list={list}
/>
</div>
);
}
}

List.jsx:

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
import ListItem from './ListItem';

class List extends Component {
// 父组件声明自己支持context
static childContextTypes = {
color: PropTypes.string,
}
static propTypes = {
list: PropTypes.array,
}
// 提供一个函数,用来返回相应的context对象
getChildContext() {
return {
color: 'red',
};
}
render() {
const { list } = this.props;
return (
<div>
<ul>
{
list.map((entry, index) =>
<ListItem key={`list-${index}`} value={entry.text} />,
)
}
</ul>
</div>
);
}
}

ListItem.jsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class ListItem extends Component {
// 子组件声明自己要使用context
static contextTypes = {
color: PropTypes.string,
}
static propTypes = {
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<li style={{ background: this.context.color }}>
<span>{value}</span>
</li>
);
}
}

export default ListItem;

11.4 redux

12 react-route

12.1 react-router的理解

  1. react的一个插件库
  2. 专门用来实现一个SPA应用
  3. 基于reacr的项目基本都会用到词库

12.2 SPA的理

  1. 单页Web应用(single page web application, SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,本身也不会像服务器发送请求
  4. 当点击链接是,只会做页面的局部刷新
  5. 数据都需要通过ajax/axios发送网络请求,并在前端异步展现
  6. 目前SPA应用非常流行,Vue-Router也对Vue能够实现SPA提供支持

12.3 路由的理解

  1. 什么是路由

    • 一个路由就对应一个映射关系(key:value)
    • key为路由路径,value可能是function/component
  2. 路由分类

    • 后台路由:node服务器端路由,value是function,用来处理客户端的亲贵并返回一个响应数据
    • 前端路由:浏览器端路由,value是一个component,当请求的是路由path时,浏览器端没有发送http请求,而是显示对应的组件
  3. 后台路由

    • 注册路由:router.get(path,function(req,res))
    • 当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
  4. 前端路由

    • 注册路由:

      1
      <Route path="/about" commponent={About}>
    • 当浏览器的hash变为#about时,当前路由组件就会变为About组件

12.4 前端路由的实现

  1. histoy库
  2. 管理浏览器绘画历史(history)的工具库
  3. 包装的是原生BOM中window.history和window.loaction.hash

13. react-router相关API

13.1 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<BrowserRouter>

<HashRouter>

<Route>

<Redirect>

<Link>

<NavLink>

<Switch>

13.2 其他

  • history 对象

13.3 react-router-dom简单使用

示例:

  • index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React from 'react';
    import {render} from 'react-dom';
    import App from './components/app';
    import reportWebVitals from './reportWebVitals';
    import './index.scss'


    import {BrowserRouter} from 'react-router-dom'

    render(
    <BrowserRouter>
    <App/>
    </BrowserRouter>
    ,
    document.getElementById('root')
    )
    ;

    reportWebVitals();
  • app.jsx

    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
    import React, {Component} from "react";
    import {Switch, Route, Redirect} from 'react-router-dom'
    import About from "../views/about";
    import Home from "../views/home";
    import MyNavLink from "./MyNavLink";

    class App extends Component {
    state = {};
    static propTypes = {};

    render() {
    return (
    <div className="container">
    <div className="row">
    <div className="col-xs-offset-2 col-xs-8">
    <div className="page-header">
    <h2>React Router Demo</h2>
    </div>
    </div>
    </div>
    <div className="row">
    <div className="col-xs-2 col-xs-offset-2">
    <div className="list-group">
    <MyNavLink className='list-group-item' to='/about'/>
    <MyNavLink className='list-group-item' to='/home'/>
    </div>
    </div>
    <div className="col-xs-6">
    <div className="panel">
    <div className="panel-body">
    <Switch>
    <Route path='/about' component={About} className="list-group-item "/>
    <Route path='/home' component={Home} className="list-group-item "/>
    <Redirect to='/about'/>
    </Switch>
    </div>
    </div>
    </div>
    </div>
    </div>
    );
    }
    }

    export default App;
  • MyNavLink.jsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React, {Component} from "react";
    import {NavLink} from "react-router-dom";
    class MyNavLink extends Component {
    /*
    {...this.props} 将外部传递的所有属性传递给组件
    */
    render() {
    return (
    <NavLink {...this.props} activeClassName='active-class'>About</NavLink>
    )
    }
    }

    export default MyNavLink;
  • about.jsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import React, {Component} from "react";

    class About extends Component {
    state = {};
    static propTypes = {};

    render() {
    return (

    <div>
    <h2>About</h2>
    </div>
    )
    }
    }

    export default About;
  • home.jsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React, {Component} from "react";

    class Home extends Component {
    state = {};
    static propTypes = {};

    render() {
    return (
    <div>
    <h2>Home</h2>
    </div>
    )
    }
    }

    export default Home;

项目源码

13.4 嵌套路由的使用

13.4.1 如何编写路由效果
  1. 编写路由组件
  2. 在父路由组件中指定
    • 路由链接: <NavLink>
    • 路由:<Route>

14. 最流行的开源React UI组件库

14.1 meterial-ui(国外)

14.2 ant-design(国内蚂蚁金服)

14.2.1 按需打包
  1. 下载包

    • npm i react-app-rewired@2.0.2-next.0 -D

      出现了这个错误,The "injectBabelPlugin" helper has been deprecated as of v2.0. You can use customize-cra plugins in replacement - https://github.com/arackaf/customize-cra#available-plugins

      1. 问题的分析react-scripts升级到2.1.2 以后破坏了 react-app-rewired。然后 react-app-rewired升级到 2.x以后直接干掉了所有 helpers
      2. 问题的解决: 把react-app-rewired 进行降级后可以了, 可以通过 npm install react-app-rewired@2.0.2-next.0 命令进行降级。 在安装低版本的react-app-rewired以后,重新npm install,最后再npm start启动项目就可以解决问题了。
    • npm i babel-plugin-import -D

  2. 修改默认配置

    • package.json

      1
      2
      3
      4
      5
      "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test",
      }
    • config-overrides.js

      1
      2
      3
      4
      5
      const {injectBabelPlugin} = require('react-app-rewired');
      module.exports = function override(config, env) {
      config = injectBabelPlugin(['import', {libraryName: 'antd-mobile', style: 'css'}], config);
      return config;
      };

15. redux

15.1 学习文档

15.2 redux是什么

  1. redux是一个独立专门用于做状态管理的js库(不是react插件库)
  2. 它可以用react,angular,vue(vuex)等项目中,但基本与react配合使用
  3. 作用:管理react应用中多个组件共享状态

15.3 什么情况下需要使用redux

  1. 总体原则:能不用就不用,如果不用比较吃力才考虑使用
  2. 某个组建的状态,需要共享
  3. 某个状态需要在任何地方都可以拿到
  4. 一个组件需要改变全局状态
  5. 一个组件需要改变另一个组件状态

15.4 redux的核心API

15.4.1 createStore
  1. 作用:

    创建包含指定reducer的store对象

  2. 编码:

    1
    2
    3
    import {createStore} from 'redux'
    import counter from './reducers/counter'
    const store = createStore(counter)
15.4.2 store对象
  1. 作用:

    redux库最核心的管理d对象

  2. 它内部维护着:

    state

    reducer

  3. 核心方法

    getState() 读取状态

    dispatch(action) 提交行为

    subscribe(listener) 订阅监听用于监听state的状态改变

  4. 编码:

    1
    2
    store.getState()
    store.dispatch({type:'INCREMENT',number})

15.5 redux的三个核心概念

15.5.1 action
  • 标识要执行行为的对象

  • 包含2个方面的属性

    type:标识属性,值为字符串,唯一,必要熟悉 可以理解为key

    xxx: 数据属性,值为任意类型,可选属性

  • 例子:

    1
    2
    3
    4
    const action = 	{
    type:'INCREMENT'
    number:2
    }
  • Action Creator(创建Action函数)

    1
    const increment = (data)=>({type:'INCREMENT',data})
15.5.2 reducer
  • 根据旧的state和action,产生新的state的纯函数(优点类似Vuex中mutation)

  • 样例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export defailt function counter(state=0,cation){
    swotch(action.type){
    case 'INCREMENT':
    retturn state +action.data
    case:'DECREMENT':
    return state-action.data
    default:
    return state
    }
    }
  • 注意:

    返回一个新的状态,不要修改原来的状态

15.5.3 store
  • 将state,action与reducer联系在一起的对象

  • 如何得到此对象

    1
    2
    3
    import {createStore} from 'redux'
    import reducer from './reducers'
    const store =createStore(reducer)
  • 此对象的功能

    getstate():得到对象

    dispatch(action):分发action,触发reducer调用,产生新的state

    subscribe(listener):注册监听,当产生新的state时,自动调用

15.6 react-redux

中文文档:https://www.redux.org.cn/docs/react-redux/

15.6.1 理解
  1. 一个react插件库
  2. 专门用来简化react应用中使用redux
15.6.2 React-Redux将所有组件分成两类
  1. UI组件

    只负责UI的呈现,不带有任何业务逻辑通过props接收数据(一般数据和函数),不适用任何Redux的API一般保存在components文件下

  2. 容器组件

    负责管理数据和业务逻辑,不负责呈现UI,使用Redux的API,一般保存在containers文件下

15.6.3 React-Redux 相关API
  1. Provider

    让所有组件都可以得到state数据

    1
    2
    3
    <Provider store={store}>
    <App/>
    </Provider>
  2. connect()

    用于包装UI组件生产容器组件

    1
    2
    3
    4
    5
    import {connect} from 'react-redux';
    export default connect(
    state=>({count:state}), // 父函数会将这两个对象一一结构交给子函数
    {increase,reduce}
    )(Counter);
  3. mapStateToprops()

    将外部的数据(即state对象)转换为UI组件的标签属性

    1
    2
    3
    4
    const mapStateToprops = function(state){
    return {
    value:state}
    }
  1. mapDispatchToProps()

    将分发action函数转换为UI组件的标签属性

    简洁语法可以直接指定为actions对象或包含多个action方法的对象

    1
    2
    3
    function mapDispatchToProps(dispatch) {
    return { actions: bindActionCreators(actionCreators, dispatch) }
    }

15.7 Redux异步编程

15.7.1 问题
  1. redux默认是不能进行异步处理的
  2. 应用中又需要再redux中执行异步任务(ajax,定时器)
15.7.2 下载redux插件(异步中间件)
1
npm install --save redux-thunk
15.7.3 index.js
1
2
3
4
5
6
7
8
9
10
import {createStore,applyMiddleware} from "redux";
// 引入异步中间件
import thunk from "redux-thunk";
import {counter} from "./reducers";
// 创建一个store对象
const store = createStore(
counter,
applyMiddleware(thunk) // 应用异步中间件
); //内部会第一次调用reducer函数得到初始state\
export default store;

15.8 使用redux调试工具

15.8.1 浏览器下载Redux Devtools扩展
15.8.2 下载工具依赖包
1
npm install --save-dev redux-devtools-extension
15.8.3 编码
1
2
3
4
5
6
7
8
9
10
11
import {createStore, applyMiddleware} from "redux";
import {composeWithDevTools} from "redux-devtools-extension";
// 引入异步中间件
import thunk from "redux-thunk";
import {counter} from "./reducers";
// 创建一个store对象
const store = createStore(
counter,
composeWithDevTools(applyMiddleware(thunk))// 应用异步中间件
); //内部会第一次调用reducer函数得到初始state\
export default store;

16 . code hub

17 函数柯里化

维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 普通的add函数
function add(x, y) {
return x + y
}

// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}

add(1, 2) // 3
curryingAdd(1)(2) // 3
谢谢老板