安装react脚手架工具
参照官方文档创建新的reactApp,配置对应环境
1 | npx create-react-app todolist |
成功预览
项目文件概要
各文件的作用
用案例学习react
通过案例Todolist来编写Todolist.js进而熟悉react的基础思想:对数据层的操作来管理(生成)对应HTML。
主要包括:
- react组件的定义与挂载
- JSX基础语法
- 其他函数
- 事件(与数据)绑定:数据的改变决定页面的变化
react的组件
一个页面很复杂,难于维护;react将整个页面拆分成若干个小组件,如页面的搜索可以分为button和input两个组件,这样更易维护。这便是前端工程的组件化。
定义组件
1 | class App extends React.Component; //类继承React.Component这个基类 |
其中,
1 | import {Component} from 'react'; |
挂载组件
1 | import React from 'react'; |
JSX基础语法
1 | {/* 这是一行注释*/} |
- 注释使用{/*··· */}或{ //··· }【占三行】
- JSX中组件标签是大写字母开头,如Fragment
- JSX中小写字母标签是h5
- JSX语法中,{js代码块}需要用’{ }’包裹起来
占位符与其他函数
react中render()函数只对一个元素进行渲染,这是就需要用<div>
标签将所有代码封装起来。为了不在代码外层套上一个<div>
,react引入了<Fragment>
作为占位符代替<div>
,起到与之前相同的作用,且没有外层包裹(div)。
此时,
1 | import React from 'react'; |
1 | import React, { |
事件绑定
react遵循响应式设计思想,其意为“响应”,这即是react之名的含义。其关注于数据层的操作,之后自动的生成HTML-DOM。
定义数据constructor
在组件内部,constructor(构造)的执行优先级最高,其中,数据存储位于this.state
函数下,读作函数的状态。
1 | constructor(props) { //构造器(小组件),执行优先级最高 |
数据绑定(钩子)
绑定语法:在input标签内用onchange事件将input标签与handleInputChange函数绑定时,需要将“onchange”写作“onC
hange”。这样数据才会相互沟通,整个过程像钩在一起。特别地,需要用bind函数把this切换到同一层级,这样才能更方便地交换数据,就好像相对路径与绝对路径一样。除此以外,bind函数还可以传回其他参数,用“,”隔开即可。
1 | render() { |
1 | handleInputChange(e) { |
- state负责存储组件内的数据
- JSX语法中使用js代码,需要用’{}’包裹块
- 事件绑定时使用bind(this),对函数的作用域进行变更
- 改变组件内的数据使用
setvalue
函数
回调函数
1 | <ul> |
数据的immutable特性
react的state(数据组)具有immutable的特性,即不要直接修改state内的值(不利于react性能优化);而是采取对state副本进行修改。
1 | handleItemDelete(index) { |
- react-state的使用
- 数据绑定
- onclike与onchange事件绑定
- bind往回调函数内传参
- state的immutable特性
美化
不被转义
使用JSX语法中的dangeroulySetInnerHTML的关键字
1 | //__html两个下划线 |
引入style.css
1 | import './style.css' //css路径 |
1 | .input{ |
1 | <label htmlFor="insertArea">输入内容</label>{/*光标的聚焦*/} |
label for
想要达到“点击label,光标自动聚焦到input框”的效果,需将for关键字写成‘htmlFor’。
1 | <label htmlFor="insertArea">输入内容</label>{/*光标的聚焦*/} |
- 键名为dangerouslySetInnerHTML的关键字使得标签内部的代码不被转义
- 不使用class而是使用“className”来引入css的类名,避免与class【类】的关键字混淆
- label的for标签替换成“htmlFor”来指向类名
组件的拆分
既然已经实现了增删功能,那么如何将组件拆分!?TodoList(父组件)=>TodoItem(子组件)
新建组件
新增一个子组件TodoItem.js;
子组件TodoItem伪代码
1 | import react,{Component} from 'react'; |
引入组件
在父组件引入子组件:
1 | import TodoItem from './TodoItem'//拓展名.js可省略,由react自动补全 |
子父组件的值传递
todolist(父组件)向todoitem(子组件)传递值,是通过标签属性的方式,可以是数据及方法。
todoitem(子组件)接收todolist(父组件)的值,通过this.props.xxx
的方式,其中xxx是数据及方法。特别地,子组件调用父组件的方法来改变父组件的值时,要将该方法的this通过bind绑定在父组件上一起传过来。
子组件TodoItem
1 | import React, { |
代码优化
解构赋值
es6的语法
1 | render() { |
1 | import React, { |
声明式与命令式开发
这两种开发方式是什么?
打个比方,要做成一道菜。声明式开发像是去写一个菜谱,当菜谱写完时,react就已经把菜【DOM】做好了。命令式更像是想着菜谱,把菜【DOM】给做出来。
react开发的特点:
- react由数据驱动
- 声明式开发
- 与其他框架共存
- 组件化,组件标签是首字母大写的标签
- 单向数据流,父==值=>子,子只能使用值【或调用父的函数修改父的值】
- 视图层框架,【需配合数据层框架,完成跨父子层传值,如redux】
- 函数式编程【利于前端工程的自动化测试】
PropTypes
为了安全,子组件应对父组件传入的值类型做校验。
1 | TodoItem.propTypes = { |
虚拟DOM
真实DOM如何生成
react底层生成真实DOM的过程:JSX模板 -> creatElement -> JS对象 -> 真实的DOM ;其中,JSX模板是react底层语法的另一种写法,因为react底层接口写出来的代码片段不易于理解且不方便。例如:
1 | render(){ |
state 发生变化
数据 + 模板 生成新的虚拟DOM (极大地提升了性能)
变:[‘div’,{id:’abc’},[‘span’,{},’bye bye’]]比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大地提升了性能)
直接操作DOM,改变span中的内容
虚拟DOM的优势
- 性能提升 <- 操作虚拟DOM进行比较,生成改变部分的真实DOM
- 使跨端应用得以实现。React Native 是基于虚拟DOM而存在,确切的说,之所以react可以跨端开发,是因为虚拟DOM可以在不同环境中被渲染成适应不同环境的代码。
那和虚拟DOM有什么关系呢?那是因为虚拟DOM是一个JS对象,而JS对象在浏览器和原生应用中都能识别。
旧新虚拟DOM的DIFF算法
DIFF算法全称为difference算法,意味寻找虚拟DOM间的不同点。当数据被修改时,setState函数就会被调用一次,这是因为setState函数的功能就是修改数据的,理论上就会触发生成一次虚拟DOM的动作。那么,频繁地修改数据,即频繁地调用setState函数势必会造成性能的浪费。
那要怎么做才能将性能节省下来呢?答案很简单,攒起来做一次就好了;就像打印东西一下,一张一张地打印势必麻烦,那攒起来一下子全打印出来就好了。而setState被设计成异步函数正是出于此考量,当数据连续三次被修改的间隔较小时,setState函数就会将三次修改的内容并为一次进行触发一次生成虚拟DOM的动作。
具体的比对过程
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 | ref={(input)=>{this.input=input}}//指向input的DOM |
setState的回调函数
某些情况下,需要获取虚拟DOM执行之后的结果,比如有些你需要获取当前数据的长度,但是setState是异步函数,它的处理结果不是同步的。这时需要一个回调函数,即setState的第二次参数。
1 | // ul部分 |
但是在遇到极其复杂的业务时,例如操作动画时,可能需要这个函数。
react生命周期函数与性能优化
生命周期函数是指在某一个时刻组件会自动调用执行的函数。如render()函数,它是在数据发生改变时,自动执行,因此render()函数是一个生命周期函数。
- componentWillMount 在组件即将被首次挂载到页面的时刻,自动被执行一次
- componentDidMount 在组件被首次挂载到页面之后,自动被执行一次
- shouldComponentUpdate 在组件被更新之前,自动被执行,并返回一个bool值
- componentWillUpdate 某组件的shuldComponentUpdate函数返回的bool值来触发该组件的componentWillUpdate函数是否被执行
- componentDidUpdate 在组件被更新之后,自动被执行
- componentWillRecieveProps 当一个组件第二次接受参数时,该函数被自动执行,即当一个组件从父组件接受参数,且该组件在父组中出现过;再次接受参数时,该函数自动被执行
- componentWillMount 在子组件将要被移除时,该函数自动被执行
- render 当父组件的render函数被执行,子组件的render函数也会被执行,那么,如果子组件的数据没有被改变时,子render函数仍然被执行,势必会造成性能损失。
生命周期函数的使用
之前的todolist案例中,存在这样一个问题。在父组件数据发生改变的,子组件的render函数一直被自动执行,无疑,这造成了性能的浪费,如下图:
使用shouldComponentUpdate来判断子组件是否该被执行,判断依据于 子组件的数据是否被改变,即获取到的数据与之前的数据是否一样。这就需要用到shouldComponentUpdate的两个参数 nextProps
和nextState
,前者是代表接下来父组件传过来的数据流是什么,后者代表接下来的子组件的数据是什么。于是,有
1 | shouldComponentUpdate(nextProps, nextState) { |
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 | componentDidMount(){ |
charles本地接口数据模拟
es6 优化代码
1 | componentDidMount() { |
react的动画效果
借助css的函数
借助css3的两个函数tansition与animation+keyframes可以实现简单动画效果。如下
1 | import React, { |
1 | .show { |
借助reac第三方库
css3 的动画效果似乎有些单薄,好在 react-transition-group 提供了丰富的JS动画效果,借助此模块的钩子函数【类似生命周期函数】,都能实现什么效果呢?
1 | import { CSSTransition } from 'react-transition-group'; |
为所有元素增加效果
需要将 CSSTransition 和 TransitionGrou 配合,为所有的组件添加动态效果。
1 | import React from 'react'; |
1 | import React, { |
1 | .fade-enter, .fade-appear { |
Redux与react
react为视图层框架,Redux为数据层框架;开发大型项目,需要两者相结合——用来解决react组件间传值的问题。Redux就是将react里的数据集中存放在一个地方,方便数据沟通。其中,Redux=Reducer+Flux
,Flux由facebok推出,但是数据的store间存在着数据依赖;于是开发出redux的方式。
redux的优点
是否采用redux
redux数据流动方式
- Store是所有数据的存储位置
继续优化Todolist
借助 react UI 框架库 antd,在TodoList根目录安装npm install antd --save
,并重启 react app npm run start
如何使用redux
安装redux
1
npm install redux --save
src 目录下创建 store 文件夹,且在该文件夹下创建 index.js【管理员】 和 reducer.js【管理手册】,在根目录 TodoList.js 引入 redux 的数据。其中,redux 实例 store 的数据获取使用 getState() 函数。
1
2
3
4
5
6
7
8import 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
8const 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
48import 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;安装 redux devtools 插件,方便后面调试。
使用事件将 html 标签 与 函数绑定,并在函数内创建action的属性,最后由store-dispatch 将该 action 传递到 reducer
reducer 判断改动作的类型,并作出处理,返回最新的 state
store内的state【数据】发生改变,并通过 subscribe 绑定并执行函数,由函数将新的数据反馈给组件。
组件的state发生改变,带动页面重新渲染。
动作派发
在 store/actionTypes.js 内创建并导出动作常量,从而有效地避免动作拼写错误。
1 | // store/actionTypes.js 创建 |
~ 其实,我觉得vscode编辑器可以避免
统一管理action
在 store/actionCreators.js 内统一创建并管理 action。
1 | export const getChangeInputItem=(value)=>({ |
Redux 小结
- 常识
1.1 store 是唯一
1.2 只有store可以改变store的内容,reducer只能拷贝store里的数据,操作之后并返回给store,最后由tore自我更新
1.3 reducer 内必须是纯函数。纯函数是 输入(state,action)给定,那么输出一定;而且不会有任何副作用。副作用是指 不该做的事情,如对传入参数的修改。 - 核心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 | import React, { Component } from 'react' |
1 | import React, { Component } from 'react'; |
1 | const defaultState = { |
无状态组件
无状态组件就是一个函数。当一个普通组件只有一个render函数时,可以替换成一个无状态组件=>可以提高性能。
- 无状态组件是一个函数
- class 要执行生命周期函数和render函数。
改写成无状态组件
1 | import React, { Component } from 'react' |
Redux的中间件
redux-thunk可以是 action 的返回值可以是一个函数。这样的函数就可以是一个异步函数
安装redux-thunk,访问redux-thunk github 库,并安装
在 /store/index.js 使用 applyMiddleware(),
1
2
3
4
5
6import { 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));调整 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
24import { 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
redux-thunk 的工作流程。
流程图
redux 的中间件是用来连接 Action 与 Store。其中,redux-thunk是对 dispatch 方法进行封装——升级。升级在哪里?之前的dispatch只能接受对象。升级后,dispatch可以接受对象及函数。
安装 redux-saga
导入 sagaMiddleware 组件,
import createSagaMiddleware from 'redux-saga'
建立 sagaMiddleware 实例
1
2
3
4
5const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware)
// other store enhancers if any
);新建 /store/sagas.js 文档,并引入 /store/index.js文件
1
2
3
4
5function* mySagas(){
}
export default mySagas;
react-redux 重写 Todolist
- 新建 TodoList.js 文件
TodoList.js,记得导出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import 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; - 创建 redux
- 新建 store/index.js 文件【管理员】
创建 store
1
2
3
4
5
6import { createStore } from 'redux'
import reducer from './reducer';
const store = createStore(reducer)//创建管理员;链接管理员与手册
export default store - 创建 ./store/reducer.js 文件【管理手册】,包括 表格+条例
表格+条例
1
2
3
4
5
6
7
8const defaultState={//表格
inputVale:'',
List:[]
}
export default (state=defaultState,action)=>{
return state //条例
}
- 新建 store/index.js 文件【管理员】
- 链接 TodoList【react】 与 Store【redux】
在TodoList.js 内加入
1
2
3
4
5
6
7
8
9
10
11import store from './store/index'; //store导入Todolist
class TodoList extends Component {
constructor(props) {
super(props)
this.state = store.getState()//得到store的数据
}
···
} - 用 react-redux 做链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import 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
74import 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是只存在于组件间流动的state1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const 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
}- 代码优化
- 解构赋值使得代码精简
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
72import 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