安装react脚手架工具

参照官方文档创建新的reactApp,配置对应环境

1
2
3
npx create-react-app todolist
cd todolist
npm start
成功预览

项目文件概要

各文件的作用

用案例学习react

通过案例Todolist来编写Todolist.js进而熟悉react的基础思想:对数据层的操作来管理(生成)对应HTML。
主要包括:

  • react组件的定义与挂载
  • JSX基础语法
  • 其他函数
  • 事件(与数据)绑定:数据的改变决定页面的变化

react的组件

一个页面很复杂,难于维护;react将整个页面拆分成若干个小组件,如页面的搜索可以分为button和input两个组件,这样更易维护。这便是前端工程的组件化。

定义组件

1
2
3
4
class App extends React.Component; //类继承React.Component这个基类
//为了省略,前后等效
import { Componet } from 'react';
class App extends Component;

其中,

1
2
3
4
import {Component} from 'react';
//等效于
import React from 'react';
const Component = React.Component;

挂载组件

1
2
3
4
import React from  'react';
//JSX语法,需要引入React组件
ReactDOM.render(<App />, document.getElementById('root'));
//使用JSX语法用<App />标签来实现组件的挂载,其中标签内首字母大写

JSX基础语法

1
2
3
4
5
{/* 这是一行注释*/}

{
//或者,这是一行注释
}
  • 注释使用{/*··· */}或{ //··· }【占三行】
  • JSX中组件标签是大写字母开头,如Fragment
  • JSX中小写字母标签是h5
  • JSX语法中,{js代码块}需要用’{ }’包裹起来

占位符与其他函数

react中render()函数只对一个元素进行渲染,这是就需要用<div>标签将所有代码封装起来。为了不在代码外层套上一个<div>,react引入了<Fragment>作为占位符代替<div>,起到与之前相同的作用,且没有外层包裹(div)。
此时,

1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom';
import Todolist from './Todolist';

ReactDOM.render(
<Todolist />,
document.getElementById('root')
);
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
import React, {
Component,
Fragment //react16中的占位符
} from 'react'

class TodoList extends Component {
render() {
return ( //只能有一个元素,div;或使用Fragment这个占位符

<Fragment>
<div>
<input
/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li> Learning engglish </li>
</ul>
</Fragment>
)
}
}

export default TodoList; //导出Todolist组件,从而index可以挂载Todolist

todlist1

事件绑定

react遵循响应式设计思想,其意为“响应”,这即是react之名的含义。其关注于数据层的操作,之后自动的生成HTML-DOM。

定义数据constructor

在组件内部,constructor(构造)的执行优先级最高,其中,数据存储位于this.state函数下,读作函数的状态。

1
2
3
4
5
6
7
constructor(props) { //构造器(小组件),执行优先级最高
super(props); //调用父类构造函数,Component,固定写法
this.state = { //组件的状态
inputValue: '', //需要与挂载的标签绑定
list: []
}
}

数据绑定(钩子)

绑定语法:在input标签内用onchange事件将input标签与handleInputChange函数绑定时,需要将“onchange”写作“onChange”。这样数据才会相互沟通,整个过程像钩在一起。特别地,需要用bind函数把this切换到同一层级,这样才能更方便地交换数据,就好像相对路径与绝对路径一样。除此以外,bind函数还可以传回其他参数,用“,”隔开即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
render() {
return ( //只能有一个元素,div;或使用Fragment这个占位符

<Fragment>
<div>
<input
//JSX语法中使用js,需要在js代码块外加‘{}’
value={this.state.inputValue}
//监听input框数据发生改变,onChange第二个首字母大写
//定义handInputChange方法,来改变iput内容

//bind转变绑定在handleInputChange的this指向Todolist
onChange= {this.handleInputChange.bind(this)}

/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li> Learning engglish </li>
</ul>
</Fragment>
)
}
1
2
3
4
5
6
7
handleInputChange(e) {
//this 应指向Todolist,使用handleInputChange.bind(this)
//改变数据使用setState函数
this.setState({
inputValue: e.target.value
})
}
  • state负责存储组件内的数据
  • JSX语法中使用js代码,需要用’{}’包裹块
  • 事件绑定时使用bind(this),对函数的作用域进行变更
  • 改变组件内的数据使用setvalue函数

回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<ul>
{ //回调函数(内容,index)
this.state.list.map((item, index) => {
//index作为key值是十分不善的行为,暂时先这样,不然报错
return (
// <div>
// <TodoItem />
// </div>
<li
key={index}
onClick={this.handleItemDelete.bind(this, index)}
// dangerouslySetInnerHTML={{ __html: item }}
></li>

)
})
}
</ul>

数据的immutable特性

react的state(数据组)具有immutable的特性,即不要直接修改state内的值(不利于react性能优化);而是采取对state副本进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
handleItemDelete(index) {
//immutable react要求不对state直接做修改,不方便性能优化
//因此,服药复制一份list数组
const list = [...this.state.list];

list.splice(index, 1); //es6,list的删除函数(索引,次数)

this.setState({
list: list
})

}
  • react-state的使用
  • 数据绑定
    • onclike与onchange事件绑定
    • bind往回调函数内传参
  • state的immutable特性

todolist01

美化

不被转义

使用JSX语法中的dangeroulySetInnerHTML的关键字

1
2
                            //__html两个下划线
dangerouslySetInnerHTML={{__html:item}}//支持html语法的键值

引入style.css

1
import './style.css' //css路径
1
2
3
4
5
.input{
border: 1px solid red;
}
···
</input>
1
2
3
4
5
6
7
8
9
10
11
12
<label htmlFor="insertArea">输入内容</label>{/*光标的聚焦*/}
<input
id='insertArea'
className='input'//不使用class,避免与class【类】的关键字混淆
//JSX语法中使用js,需要在js代码块外加‘{}’
value={this.state.inputValue}
//监听input框数据发生改变,onChange第二个首字母大写
//定义handleInputChange方法,来改变iput内容
//bind绑定handleInputChange的this指向TodoList
onChange={this.handleInputChange.bind(this)}

/>

label for

想要达到“点击label,光标自动聚焦到input框”的效果,需将for关键字写成‘htmlFor’

1
2
3
4
<label htmlFor="insertArea">输入内容</label>{/*光标的聚焦*/}
<input
id='insertArea'
/>
  • 键名为dangerouslySetInnerHTML的关键字使得标签内部的代码不被转义
  • 不使用class而是使用“className”来引入css的类名,避免与class【类】的关键字混淆
  • label的for标签替换成“htmlFor”来指向类名

todolist02

组件的拆分

既然已经实现了增删功能,那么如何将组件拆分!?TodoList(父组件)=>TodoItem(子组件)

新建组件

新增一个子组件TodoItem.js;

子组件TodoItem伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import react,{Component} from 'react';

class TodoItem extends Component(){
constructor(){
···//this指向
}
render(){
···//组件显示
}
otherfunction(){
···//组件显示中绑定的函数
}
}

export default TodoItem;

引入组件

在父组件引入子组件:

1
import TodoItem from './TodoItem'//拓展名.js可省略,由react自动补全

子父组件的值传递

todolist(父组件)向todoitem(子组件)传递值,是通过标签属性的方式,可以是数据及方法。
todoitem(子组件)接收todolist(父组件)的值,通过this.props.xxx的方式,其中xxx是数据及方法。特别地,子组件调用父组件的方法来改变父组件的值时,要将该方法的this通过bind绑定在父组件上一起传过来。

子组件TodoItem
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
import React, {
Component
} from 'react';

class TodoItem extends Component {

constructor(props) {
super(props);
//handleClick组件的this永远指向todoitem
//在constructor内绑定,可以节省很多资源
this.handleClick = this.handleClick.bind(this);
}

render() {
//todolist(父组件)通过属性向子组件(todoitem)传递参数
return (
<div onClick={this.handleClick}>
{this.props.content}
</div>)
}

handleClick() {
//子组件不允许直接删除父组件的内容
this.props.deleteItem(this.props.index)

}
}

export default TodoItem;

代码优化

解构赋值

es6的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
render() {
//todolist(父组件)通过属性向子组件(todoitem)传递参数

// 等效于const content=this.props.content;
const { content } = this.props;//结构赋值{} es6语法
return (
<div onClick={this.handleClick}>
{content}
</div>)
}

handleClick() {
//子组件不允许直接删除父组件的内容
// this.props.deleteItem(this.props.index)
const { deleteItem, index } = this.props;
deleteItem(index);

}
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
124
import React, {
Component,
Fragment //react16中的占位符
} from 'react';
//先引入组件,最后引入样式
import TodoItem from './TodoItem'; //TodoItem.js的js可以省略
import './style.css'; //直接引用当前目录下的syle.css


class TodoList extends Component {

constructor(props) { //构造器(小组件),执行优先级最高
super(props); //调用父类构造函数,Component,固定写法
this.state = { //组件的状态,包含两个数据,整个todolist组件
//要做的事情就是将往列表里添加数据的过程。
//将要做的事情
inputValue: '', //需要与挂载的标签绑定
//已经要做的事的数组
list: []
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}



render() {
return ( //只能有一个元素,div;或使用Fragment这个占位符

<Fragment>
<div>{/*如下是input框*/}
<label htmlFor="insertArea">输入内容</label>{/*光标的聚焦*/}
<input
id='insertArea'
className='input'//不使用class,避免与class【类】的关键字混淆
//JSX语法中使用js,需要在js代码块外加‘{}’
value={this.state.inputValue}
//监听input框数据发生改变,onChange第二个首字母大写
//定义handleInputChange方法,来改变iput内容
//bind绑定handleInputChange的this指向TodoList
onChange={this.handleInputChange}

/>
<button
//定义handleBtnClick方法,将'iputValue'存入'list'
onClick={this.handleBtnClick}>提交</button>

</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}

getTodoItem() {
//回调函数(内容,index)
return this.state.list.map((item, index) => {
//index作为key值是十分不善的行为,暂时先这样,不然报错
return (
<TodoItem
key={index}
content={item}
index={index}
//将todolist的this通过bind绑定在handleItemDelete上,即子组件的deleteItem上
deleteItem={this.handleItemDelete}
/>


// <li
// key={index}
// onClick={this.handleItemDelete.bind(this, index)}
// dangerouslySetInnerHTML={{ __html: item }}
// ></li>

)
})
}

handleInputChange(e) {
//this 应指向Todolist,使用handleInputChange.bind(this)
//改变数据使用setState函数
const value = e.target.value;//异步加载,提高性能
this.setState(() => ({
inputValue: value
}))
// this.setState({
// inputValue: e.target.value
// })
}


handleBtnClick() {
// this.setState({
// //...this.state.list,es6展开运算符,将之前的所有项逐项展开
// list: [...this.state.list, this.state.inputValue],
// inputValue: ''
// })
//优化后,prevState=this.state,改变之前的数据
this.setState((prevState)=>({
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}))

}

handleItemDelete(index) {
//immutable react要求不对state直接做修改,不方便性能优化
//因此,服药复制一份list数组
const list = [...this.state.list];

list.splice(index, 1); //es6,list的删除函数(索引,次数)

this.setState({
list: list
})

}


}

export default TodoList; //导出Todolist组件,从而index可以挂载Todolist

声明式与命令式开发

这两种开发方式是什么?
打个比方,要做成一道菜。声明式开发像是去写一个菜谱,当菜谱写完时,react就已经把菜【DOM】做好了。命令式更像是想着菜谱,把菜【DOM】给做出来。

react开发的特点:

  • react由数据驱动
  • 声明式开发
  • 与其他框架共存
  • 组件化,组件标签是首字母大写的标签
  • 单向数据流,父==值=>子,子只能使用值【或调用父的函数修改父的值】
  • 视图层框架,【需配合数据层框架,完成跨父子层传值,如redux】
  • 函数式编程【利于前端工程的自动化测试】

PropTypes

为了安全,子组件应对父组件传入的值类型做校验。

1
2
3
4
5
TodoItem.propTypes = {
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number
}

虚拟DOM

真实DOM如何生成

react底层生成真实DOM的过程:JSX模板 -> creatElement -> JS对象 -> 真实的DOM ;其中,JSX模板是react底层语法的另一种写法,因为react底层接口写出来的代码片段不易于理解且不方便。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
render(){
return <div><span>item</span></div>
//等效于
return React.creatElement('div',{},React.creatElement('span',{},'item'))
}
```
### 虚拟DOM如何工作
那么react是如何利用虚拟DOM提高性能的呢?
1. state 数据
2. JSX 模板
3. 数据 + 模板 生成虚拟DOM (虚拟DOM是一个**JS对象**,用它来描述真是DOM)【损耗了极少的性能,因为是用JS生成一个JS对象】虚拟DOM的结构 ['名称',{属性},'值']
如:['div',{id:'abc'},['span',{},'hello world']]
4. 用虚拟DOM的结构生成真实的DOM,来显示
```html
<div id='abc'><span>hello world</span></div>
  1. state 发生变化

  2. 数据 + 模板 生成新的虚拟DOM (极大地提升了性能)
    变:[‘div’,{id:’abc’},[‘span’,{},’bye bye’]]

  3. 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大地提升了性能)

  4. 直接操作DOM,改变span中的内容

虚拟DOM的优势

  1. 性能提升 <- 操作虚拟DOM进行比较,生成改变部分的真实DOM
  2. 使跨端应用得以实现。React Native 是基于虚拟DOM而存在,确切的说,之所以react可以跨端开发,是因为虚拟DOM可以在不同环境中被渲染成适应不同环境的代码。
    那和虚拟DOM有什么关系呢?那是因为虚拟DOM是一个JS对象,而JS对象在浏览器和原生应用中都能识别。

旧新虚拟DOM的DIFF算法

DIFF算法全称为difference算法,意味寻找虚拟DOM间的不同点。当数据被修改时,setState函数就会被调用一次,这是因为setState函数的功能就是修改数据的,理论上就会触发生成一次虚拟DOM的动作。那么,频繁地修改数据,即频繁地调用setState函数势必会造成性能的浪费。

那要怎么做才能将性能节省下来呢?答案很简单,攒起来做一次就好了;就像打印东西一下,一张一张地打印势必麻烦,那攒起来一下子全打印出来就好了。而setState被设计成异步函数正是出于此考量,当数据连续三次被修改的间隔较小时,setState函数就会将三次修改的内容并为一次进行触发一次生成虚拟DOM的动作。

具体的比对过程

compare
react采用对虚拟DOM进行同层比对的方式进行比较,当在第一层发现不同时,就会重新渲染本层及以下的内容——以此类推。

key值的作用

辅助虚拟DOM的同层比对更快进行,比较相同key的内容是否相同。因为index是变动的,所以不应作为key值,应用稳定的值作为key值。

删除e前 删除e后
0 -> h 0 -> h
1 -> e 1 -> l
2 -> l 2 -> l
3 -> l 3 -> o
4 -> o
生成新的虚拟DOM需要重新生成除“h,某个l”之外的其他所有部分
删除e前 删除e后
h -> h h -> h
e -> e l -> l
l -> l l -> l
l -> l o -> o
o -> o
生成新的虚拟DOM只需删除e即可

ref参数的使用

1
2
ref={(input)=>{this.input=input}}//指向input的DOM
ref = {(input) => {this.input=input}} //引用
不推荐使用ref,react是由数据驱动,并不推荐直接操作DOM

setState的回调函数

某些情况下,需要获取虚拟DOM执行之后的结果,比如有些你需要获取当前数据的长度,但是setState是异步函数,它的处理结果不是同步的。这时需要一个回调函数,即setState的第二次参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ul部分
<ul ref={(ul)=>{this.ul=ul}}> {/*箭头函数*/}
{this.getTodoItem()}
</ul>

//函数部分
handleBtnClick() {
// this.setState({
// //...this.state.list,es6展开运算符,将之前的所有项逐项展开
// list: [...this.state.list, this.state.inputValue],
// inputValue: ''
// })
//优化后,prevState=this.state,改变之前的数据
this.setState((prevState)=>({
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}),()=>{
console.log(this.ul.querySelectorAll('div').length);
})

}

但是在遇到极其复杂的业务时,例如操作动画时,可能需要这个函数。

react生命周期函数与性能优化

生命周期函数是指在某一个时刻组件会自动调用执行的函数。如render()函数,它是在数据发生改变时,自动执行,因此render()函数是一个生命周期函数。

  • componentWillMount 在组件即将被首次挂载到页面的时刻,自动被执行一次
  • componentDidMount 在组件被首次挂载到页面之后,自动被执行一次
  • shouldComponentUpdate 在组件被更新之前,自动被执行,并返回一个bool值
  • componentWillUpdate 某组件的shuldComponentUpdate函数返回的bool值来触发该组件的componentWillUpdate函数是否被执行
  • componentDidUpdate 在组件被更新之后,自动被执行
  • componentWillRecieveProps 当一个组件第二次接受参数时,该函数被自动执行,即当一个组件从父组件接受参数,且该组件在父组中出现过;再次接受参数时,该函数自动被执行
  • componentWillMount 在子组件将要被移除时,该函数自动被执行
  • render 当父组件的render函数被执行,子组件的render函数也会被执行,那么,如果子组件的数据没有被改变时,子render函数仍然被执行,势必会造成性能损失。

生命周期函数的使用

之前的todolist案例中,存在这样一个问题。在父组件数据发生改变的,子组件的render函数一直被自动执行,无疑,这造成了性能的浪费,如下图:

使用shouldComponentUpdate来判断子组件是否该被执行,判断依据于 子组件的数据是否被改变,即获取到的数据与之前的数据是否一样。这就需要用到shouldComponentUpdate的两个参数 nextPropsnextState,前者是代表接下来父组件传过来的数据流是什么,后者代表接下来的子组件的数据是什么。于是,有

1
2
3
4
5
6
7
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.content !== this.props.content) {
return true;
} else {
return false;
}
}

react的性能优化方式

  • 函数作用域绑定发生在constructor内this.handClick=this.handClick.bind(this),可优化性能
  • setState为异步函数,可以将虚拟DOM的多次比对合并成一次,降低虚拟DOM的比对频率
  • react的虚拟DOM采用同层比对的方式,结合key值可提升比对速度
  • 借助shouldComponentUpdate,避免无谓的render被自动执行

Ajax请求如何载入

react 嵌入ajax请求需要借助第三方库axios,windows下安装 npm install axios -S,之后重启react服务器 npm run start

ajax的请求写到componentDidMout函数里,因为componentWillMout、componentDidMout、constructor这三个函数均只会执行一次。其中,写道componentDidMount中较为推荐

1
2
3
4
5
componentDidMount(){
axios.get('/api/todolist')
.then(()=>{alert('succ')}) //成功
.catch(()=>{alert('error')})//失败
}

charles本地接口数据模拟

  • 安装charles软件
  • 新建todolist.json,内里写入一个数组
  • 使用charles软件的tools-mapLocalSettings-add 一个模拟。
  • 遇到问题,具体可参考
es6 优化代码
1
2
3
4
5
6
7
8
9
componentDidMount() {
axios.get('/api/todolist')
.then((res) => {
this.setState(()=>({
list:[...res.data]
}))
}) //成功
.catch(() => { alert('error') })//失败
}

react的动画效果

借助css的函数

借助css3的两个函数tansition与animation+keyframes可以实现简单动画效果。如下

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
import React, {
Component,
Fragment
} from 'react';

import './style.css';

class App extends Component {

constructor(props) {
super(props);
this.state = {
show: true
}
this.handleToggle = this.handleToggle.bind(this);
}

render() {
return (
<Fragment>
<h1 className={this.state.show ? 'show' : 'hide'}> hello </h1>
<button onClick={this.handleToggle}> Toggle</button>
</Fragment>

)
}

handleToggle() {
this.setState({
show: this.state.show ? false : true
})
}

}

export default App;
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
.show {
opacity: 1;
/* ease-in 曲线 */
/* transition: all 1s ease-in; */
animation: show-item 1s ease-out forwards;
}

.hide {
opacity: 0;
/* fowards保持最后一帧动画 */
animation: hide-item 1s ease-in forwards;
/* transition: all 1s ease-in; */
}

@keyframes hide-item {
0% {
opacity: 1;
color: red;
}
50% {
opacity: 0.5;
color: orange;
}
100% {
opacity: 0;
color: yellow;
}
}

@keyframes show-item {
0% {
opacity: 0;
color: green;
}
50% {
opacity: 0.5;
color: rgb(0, 174, 255);
}
100% {
opacity: 1;
color: purple;
}
}

cssanimation

借助reac第三方库

css3 的动画效果似乎有些单薄,好在 react-transition-group 提供了丰富的JS动画效果,借助此模块的钩子函数【类似生命周期函数】,都能实现什么效果呢?

1
2
3
4
5
6
7
8
9
10
11
import { CSSTransition } from 'react-transition-group';

<CSSTransition
in={this.state.show}
timeout={1000}//动画执行多久,ms
classNames='fade'
unmountOnExit
// el指的是这个组件本身
onEnter={(el)=>{el.style.color='blue'}}
appear={true}//第一次显示页面的时候,就会显示动画效果
>

为所有元素增加效果

需要将 CSSTransition 和 TransitionGrou 配合,为所有的组件添加动态效果。

1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
<App />,
document.getElementById('root')
);
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
import React, {
Component,
Fragment
} from 'react';

import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './style.css';

class App extends Component {

constructor(props) {
super(props);
this.state = {
list: []
}
this.handleAddItem = this.handleAddItem.bind(this);
}

render() {
return (
<Fragment>
<TransitionGroup

>
{
//循环展示list内的元素,map
this.state.list.map((item, index) => {
return (
<CSSTransition
timeout={1000}//动画执行多久,ms
classNames='fade'
unmountOnExit
// el指的是这个组件本身
onEnter={(el) => { el.style.color = 'blue' }}
appear={true}//第一次显示页面的时候,就会显示动画效果
key={index}
>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.handleAddItem}> Toggle</button>
</Fragment>

)
}

handleAddItem() {
this.setState((prevState) => {
return {
list: [...prevState.list, 'item']
}
})
}

// handleToggle() {
// this.setState({
// show: this.state.show ? false : true
// })
// }

}

export default App;
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
.fade-enter, .fade-appear {
/* 入场动画执行的第一个时刻 */
opacity: 0;
}

.fade-enter-active, .fade-appear-active {
/* 入场动画执行的第二个时刻到动画结束 */
opacity: 1;
transition: opacity 2s ease-in;
}

.fade-enter-done {
/* 入场动画执行完成 */
opacity: 1;
}

.fade-exit {
opacity: 1;
}

.fade-exit-active {
/* 出场动画执行的第二个时刻 */
opacity: 0;
transition: opacity 2s ease-in;
}

.fade-exit-done {
opacity: 0;
}

transitionGroup

Redux与react

react为视图层框架,Redux为数据层框架;开发大型项目,需要两者相结合——用来解决react组件间传值的问题。Redux就是将react里的数据集中存放在一个地方,方便数据沟通。其中,Redux=Reducer+Flux,Flux由facebok推出,但是数据的store间存在着数据依赖;于是开发出redux的方式。

redux的优点

是否采用redux

advanceAbotRedux

redux数据流动方式

flowOfRedux

  • Store是所有数据的存储位置

继续优化Todolist

借助 react UI 框架库 antd,在TodoList根目录安装npm install antd --save,并重启 react app npm run start

如何使用redux

  1. 安装redux

    1
    npm install redux --save
  2. src 目录下创建 store 文件夹,且在该文件夹下创建 index.js【管理员】 和 reducer.js【管理手册】,在根目录 TodoList.js 引入 redux 的数据。其中,redux 实例 store 的数据获取使用 getState() 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    import React from 'react';
    import ReactDOM from 'react-dom';
    import TodoList from './TodoList';

    ReactDOM.render(
    <TodoList />,
    document.getElementById('root')
    );
    1
    2
    3
    4
    5
    6
    7
    8
    const defaultState = {
    inputValue:'hello',
    list:['learn english','learn react']
    } //创建有功能【列表】的空手册

    export default (state=defaultState, action) => {
    return 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
    import React, { Component } from 'react';

    import { Input, Button, List } from 'antd';
    import store from './store/index';
    import 'antd/dist/antd.css';


    // const data = [
    // 'Racing car sprays burning fuel into crowd.',
    // 'Japanese princess to wed commoner.',
    // 'Australian walks 100km after outback crash.',
    // 'Man charged over missing wedding girl.',
    // 'Los Angeles battles huge wildfires.',
    // ];



    class TodoList extends Component {

    constructor(props){
    super(props);
    this.state=store.getState();//get store 数据的方法

    }

    render() {
    return (
    <div style={{ marginTop: '10px', marginLeft: '10px' }}>
    <div>
    <Input value={this.state.inputValue} placeholder="Todo info" style={{ width: '300px' }} />
    <Button type="primary">提交</Button>
    </div>
    <List
    style={{marginTop:'10px',width:'364px'}}
    bordered
    dataSource={this.state.list}
    renderItem={item => (
    <List.Item>
    {item}
    </List.Item>
    )}
    />
    </div>
    )
    }
    }

    export default TodoList;
  3. 安装 redux devtools 插件,方便后面调试。

  4. 使用事件将 html 标签 与 函数绑定,并在函数内创建action的属性,最后由store-dispatch 将该 action 传递到 reducer

  5. reducer 判断改动作的类型,并作出处理,返回最新的 state

  6. store内的state【数据】发生改变,并通过 subscribe 绑定并执行函数,由函数将新的数据反馈给组件。

  7. 组件的state发生改变,带动页面重新渲染。

动作派发

在 store/actionTypes.js 内创建并导出动作常量,从而有效地避免动作拼写错误。

1
2
3
4
// store/actionTypes.js 创建
export const CHANGE_INPUT_ITEM ='change_input_item';
// 在 TodoList.js 导入
import {CHANGE_INPUT_ITEM} from '/store/actionTypes'

~ 其实,我觉得vscode编辑器可以避免

统一管理action

在 store/actionCreators.js 内统一创建并管理 action。

1
2
3
4
export const getChangeInputItem=(value)=>({
type:'change_input_item'
value
})

Redux 小结

  1. 常识
    1.1 store 是唯一
    1.2 只有store可以改变store的内容,reducer只能拷贝store里的数据,操作之后并返回给store,最后由tore自我更新
    1.3 reducer 内必须是函数。纯函数是 输入(state,action)给定,那么输出一定;而且不会有任何副作用。副作用是指 不该做的事情,如对传入参数的修改。
  2. 核心API
    2.1 createStore 创建store
    2.2 store.dispatch 把 action 传入 store
    2.3 store.getState 获取 store 内所有的数据内容
    2.4 store.subscribe 订阅 store 的改变,只要 store 发生改变,就会触发回调函数

组件

组件拆分

组件分为UI组件容器组件。业内,把UI组件被称为“傻瓜组件”,容器组件被称为“聪明组件”。为什么?这是因为 UI组件主要负责页面渲染,容器组件主要负责页面的逻辑。

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
import React, { Component } from 'react'
import { Input, Button, List } from 'antd';

class TodoListUI extends Component {
render() {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={this.props.inputValue}
placeholder="Todo info"
style={{ width: '300px' }}
onChange={this.props.handleInputChange}
/>
<Button type="primary"
onClick={this.props.handleButtonClick}

>提交</Button>
</div>
<List
style={{ marginTop: '10px', width: '364px' }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item onClick={(index) => { this.props.handleItemDelete(index) }}>
{item}
</List.Item>
)}
/>
</div>
)

}
}

export default TodoListUI;
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
import React, { Component } from 'react';

import store from './store/index';
import 'antd/dist/antd.css';

import TodoListUI from './TodoListUI'


// const data = [
// 'Racing car sprays burning fuel into crowd.',
// 'Japanese princess to wed commoner.',
// 'Australian walks 100km after outback crash.',
// 'Man charged over missing wedding girl.',
// 'Los Angeles battles huge wildfires.',
// ];



class TodoList extends Component {

constructor(props) {
super(props);
this.state = store.getState();//get store 数据的方法
//作用域绑定
this.handleInputChange = this.handleInputChange.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleButtonClick = this.handleButtonClick.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
//store 的数据只要发生改变,handleStoreChange函数就会自动执行
store.subscribe(this.handleStoreChange)

}

render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleButtonClick={this.handleButtonClick}
handleItemDelete={this.handleItemDelete}
/>
)
}

handleInputChange(e) {
const action = {//定义 action
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action)//如何传入动作给 store
}

handleStoreChange() {
//当感知到store组件内的数据发生改变,就替换掉原来store里的数据
this.setState(store.getState())
}

handleButtonClick() {
const action = {
type: 'add_todo_item'
}
store.dispatch(action)
}

handleItemDelete(index) {
const action = {
type: 'delete_todo_item',
index
}
store.dispatch(action)

}

}

export default TodoList;
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
const defaultState = {
inputValue: '',
list: []
} //创建有功能【列表】的空手册

//reducer 可接收state,但不能修改state
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state));//深拷贝
newState.inputValue = action.value;
return newState;
}
if (action.type === 'add_todo_item') {
const newState = JSON.parse(JSON.stringify(state))
newState.list.push(newState.inputValue)
newState.inputValue = ''
return newState
}
if (action.type === 'delete_todo_item') {
const newState = JSON.parse(JSON.stringify(state))
newState.list.splice(action.index, 1)//数组删除函数
return newState
}

return state;
}

无状态组件

无状态组件就是一个函数。当一个普通组件只有一个render函数时,可以替换成一个无状态组件=>可以提高性能。

  • 无状态组件是一个函数
  • class 要执行生命周期函数和render函数。
改写成无状态组件
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
import React, { Component } from 'react'
import { Input, Button, List } from 'antd';


const TodoListUI = (props) => {//传入props
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={props.inputValue}//删掉this.,以下类推
placeholder="Todo info"
style={{ width: '300px' }}
onChange={props.handleInputChange}
/>
<Button type="primary"
onClick={props.handleButtonClick}

>提交</Button>
</div>
<List
style={{ marginTop: '10px', width: '364px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (
<List.Item onClick={(index) => { props.handleItemDelete(index) }}>
{item}
</List.Item>
)}
/>
</div>
)
}

// class TodoListUI extends Component {
// render() {
// return (
// <div style={{ marginTop: '10px', marginLeft: '10px' }}>
// <div>
// <Input
// value={this.props.inputValue}
// placeholder="Todo info"
// style={{ width: '300px' }}
// onChange={this.props.handleInputChange}
// />
// <Button type="primary"
// onClick={this.props.handleButtonClick}

// >提交</Button>
// </div>
// <List
// style={{ marginTop: '10px', width: '364px' }}
// bordered
// dataSource={this.props.list}
// renderItem={(item, index) => (
// <List.Item onClick={(index) => { this.props.handleItemDelete(index) }}>
// {item}
// </List.Item>
// )}
// />
// </div>
// )

// }
// }

export default TodoListUI;

Redux的中间件

redux-thunk可以是 action 的返回值可以是一个函数。这样的函数就可以是一个异步函数

  1. 安装redux-thunk,访问redux-thunk github 库,并安装

  2. 在 /store/index.js 使用 applyMiddleware(),

    1
    2
    3
    4
    5
    6
    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';

    // Note: this API requires redux@>=3.1.0
    const store = createStore(rootReducer, applyMiddleware(thunk));
  3. 调整 redux-devtools-extension的配置,同时运行 redux-thunk 与 redux-devtools-extension。

    配置 store/index.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import { createStore, applyMiddleware, compose } from 'redux'

    import reducer from './reducer'
    import thunk from 'redux-thunk';

    const composeEnhancers =
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
    // Specify extension’s options like name,
    // actionsBlacklist, actionsCreators, serialize...
    }) : compose;//compose 需要从react内引入

    const enhancer = composeEnhancers(
    applyMiddleware(thunk),
    // other store enhancers if any
    );


    //调用该函数,创建管理员
    const store = createStore(reducer, enhancer);
    //如果安装了 redux devtools, 那么就使用该工具


    export default store;

    redux-thunk

  4. redux-thunk 的工作流程。

    流程图

  5. redux 的中间件是用来连接 Action 与 Store。其中,redux-thunk是对 dispatch 方法进行封装——升级。升级在哪里?之前的dispatch只能接受对象。升级后,dispatch可以接受对象及函数。

    1. 传递对象——直接传给store
    2. 传递函数——先执行函数——判断是否需要store——决定是否传给store

      redux-saga

      除此以外,还存在其他中间件。如 redux-logger, redux-saga(单独把异步的操作拆分到不同文件夹)。react 项目异步处理常用 redux-thunk 或 redux-saga
  6. 安装 redux-saga

  7. 导入 sagaMiddleware 组件,import createSagaMiddleware from 'redux-saga'

  8. 建立 sagaMiddleware 实例

    1
    2
    3
    4
    5
    const sagaMiddleware = createSagaMiddleware()
    const enhancer = composeEnhancers(
    applyMiddleware(sagaMiddleware)
    // other store enhancers if any
    );
  9. 新建 /store/sagas.js 文档,并引入 /store/index.js文件

    1
    2
    3
    4
    5
    function* mySagas(){

    }

    export default mySagas;

react-redux 重写 Todolist

  1. 新建 TodoList.js 文件
    TodoList.js,记得导出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React, { Component } from 'react'

    class TodoList extends Component {

    render() {
    return (
    <div>
    <div>
    <input />
    <button>提交</button>

    </div>
    <ul>
    <li>Hpl</li>
    </ul>
    </div>
    )
    }
    }

    export default TodoList;
  2. 创建 redux
    1. 新建 store/index.js 文件【管理员】
      创建 store
      1
      2
      3
      4
      5
      6
      import { createStore } from 'redux'
      import reducer from './reducer';

      const store = createStore(reducer)//创建管理员;链接管理员与手册

      export default store
    2. 创建 ./store/reducer.js 文件【管理手册】,包括 表格+条例
      表格+条例
      1
      2
      3
      4
      5
      6
      7
      8
      const defaultState={//表格
      inputVale:'',
      List:[]
      }

      export default (state=defaultState,action)=>{
      return state //条例
      }
  3. 链接 TodoList【react】 与 Store【redux】
    在TodoList.js 内加入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import store from './store/index'; //store导入Todolist

    class TodoList extends Component {

    constructor(props) {
    super(props)
    this.state = store.getState()//得到store的数据
    }

    ···
    }
  4. 用 react-redux 做链接
    1. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      import React from 'react';
      import ReactDOM from 'react-dom';
      import TodoList from './TodoList';
      import {Provider} from 'react-redux'
      import store from './store/index';

      const App=(
      //Provider 链接上 store,那么 Provider内部的组件(如TodoList)均可获取store内的数据
      <Provider store={store}>
      <TodoList />
      </Provider>
      )

      ReactDOM.render(
      App,
      document.getElementById('root')
      );

      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
      import React, { Component } from 'react'
      import { connect } from 'react-redux'

      class TodoList extends Component {

      render() {
      return (
      <div>
      <div>
      <input
      value={this.props.inputValue}
      onChange={this.props.changeInputValue}
      />
      <button
      onClick={this.props.handleClick}
      >提交</button>

      </div>
      <ul>
      {
      this.props.list.map((item, index) => {
      return <li
      onClick={this.props.handleDelete}
      key={index}>{item}</li>
      })
      }
      </ul>
      </div>
      )
      }

      }


      const mapStateToProps = (state) => {
      return {
      inputValue: state.inputValue,
      list: state.list
      }
      }

      const mapDispatchToProps = (dispatch) => {
      return {
      //派发action
      changeInputValue(e) {
      const action = {
      type: 'change_input_value',
      value: e.target.value //改变action内的value
      }
      dispatch(action)
      },
      handleClick() {
      const action = {
      type: 'add_item'
      }
      dispatch(action)
      },
      handleDelete(index){
      const action = {
      type: 'delete_item',
      index: index //改变action内的value
      }
      dispatch(action)
      }
      }
      }

      //把store的数据按照mapStateToProps规则映射到朝向TodoList组件的props内对应的参数上
      //如果store的state做修改,那么通过朝向store的props按照mapDispatchToProps规则进行操作
      export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

      //state和props的区别
      // 某个时刻他们的内容是相同的,props是传递过程的数据流,state是对应在组件上的数据
      //可以说props是只存在于组件间流动的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
      const defaultState = {
      inputValue: '',
      list: []
      }

      export default (state = defaultState, action) => {
      if (action.type === 'change_input_value') {
      const newState = JSON.parse(JSON.stringify(state))
      newState.inputValue = action.value //接受到action的value
      return newState
      }
      if (action.type === 'add_item') {
      const newState = JSON.parse(JSON.stringify(state))
      newState.list.push(newState.inputValue)
      newState.inputValue = ''
      return newState
      }
      if (action.type === 'delete_item') {
      const newState = JSON.parse(JSON.stringify(state))
      newState.list.splice(action.index, 1)//删除数组内下标为index的那一项
      return newState
      }


      return state
      }
    2. 代码优化
    • 解构赋值使得代码精简
      1
      const { inputValue,list,changeInputValue,handleClick,handleDelete } = this.props;
    • TodoList 为 UI 组件,只有一个render()方法,改写成无状态组件=> 提高性能
      TodoList 改写提高性能
      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
      import React, { Component } from 'react'
      import { connect } from 'react-redux'

      const TodoList = (props) => { //定义函数
      const { inputValue, list, changeInputValue, handleClick, handleDelete } = props;

      return (
      <div>
      <div>
      <input
      value={inputValue}
      onChange={changeInputValue}
      />
      <button
      onClick={handleClick}
      >提交</button>

      </div>
      <ul>
      {
      list.map((item, index) => {
      return <li
      onClick={handleDelete}
      key={index}>{item}</li>
      })
      }
      </ul>
      </div>
      )
      }


      const mapStateToProps = (state) => {
      return {
      inputValue: state.inputValue,
      list: state.list
      }
      }

      const mapDispatchToProps = (dispatch) => {
      return {
      //派发action
      changeInputValue(e) {
      const action = {
      type: 'change_input_value',
      value: e.target.value //改变action内的value
      }
      dispatch(action)
      },
      handleClick() {
      const action = {
      type: 'add_item'
      }
      dispatch(action)
      },
      handleDelete(index) {
      const action = {
      type: 'delete_item',
      index: index //改变action内的value
      }
      dispatch(action)
      }
      }
      }

      //把store的数据按照mapStateToProps规则映射到朝向TodoList组件的props内对应的参数上
      //如果store的state做修改,那么通过朝向store的props按照mapDispatchToProps规则进行操作
      export default connect(mapStateToProps, mapDispatchToProps)(TodoList);//返回结果是容器组件

      //state和props的区别
      // 某个时刻他们的内容是相同的,props是传递过程的数据流,state是对应在组件上的数据
      //可以说props是只存在于组件间流动的state

评论