源码地址
备忘
build 之后调试
在项目目录下
npm install -g serve
serve -s build
关于PWA
PWA: progressive web application
通过写网页的的方式来写手机APP应用
React 中的 immutable
React 中不允许直接对 state 直接改变
操作数据时,可以拷贝一个副本出来操作
React中的虚拟DOM
React简单逻辑:
1. state 数据
2. JSX 模板
3. 数据 + 模板 结合,生成真实的DOM,来显示
4. state 发生改变
5. 数据 + 模板 结合,生成真实的DOM,替换原有的DOM
......
缺陷:每一次都重新生成了完整的DOM片段,非常消耗性能
第一次优化之后的逻辑:
1. state 数据
2. JSX 模板
3. 数据 + 模板 结合,生成真实的 DOM ,来显示
4. state 发生改变
5. 数据 + 模板 结合,生成真实的 DOM ,不替换DOM
6. 拿新的 DOM 和旧的 DOM 做比对,找差异
7. 找出发生变化的 DOM ,替换老的 DOM 中改变的部分
......
缺陷:比对新旧的 DOM 又消耗了新的性能,性能的提升不够显著
第二次优化之后的逻辑:
1. state 数据
2. JSX 模板
3. 数据 + 模板 结合,生成真实的 DOM ,来显示
真实DOM: <div id="abc"><span>hello world</span></div>
4. 数据 + 模板 结合生成虚拟DOM(虚拟 DOM 就是一个JS对象,用来描述真实的DOM)
虚拟DOM: ['div', {id: 'abc'}, ['span', {}, 'hello world']]
5. 数据发生变化时,生成新的虚拟DOM
6. 比较新旧虚拟DOM
7. 根据虚拟DOM的不同,改变真实DOM
......
优点1:比较的性能消耗大幅减少
优点2:用JS生成JS对象的资源是极少的,但是生成真实的DOM,消耗的资源很大
优点3:比较虚拟DOM,就是比较JS对象,是极其节省资源的
React中的优化
1. state 数据
2. JSX 模板
3. 数据 + 模板 结合生成虚拟DOM(虚拟 DOM 就是一个JS对象,用来描述真实的DOM)
虚拟DOM: ['div', {id: 'abc'}, ['span', {}, 'hello world']]
4. 根据虚拟DOM生成真实DOM
真实DOM: <div id="abc"><span>hello world</span></div>
5. 数据发生变化时,生成新的虚拟DOM
6. 比较新旧虚拟DOM
7. 根据虚拟DOM的不同,改变真实DOM
......
优点:先生成虚拟DOM,大幅提升性能
虚拟DOM如何变成真实DOM
流程:JSX => React.createElement() => 虚拟DOM (JS对象) => 真实DOM
React 底层会通过 React.createElement() 这个方法,将 JSX 语法转成JS对象,React.createElement()可以接收三个参数,第一个为标签名称,第二参数为属性,第三个参数为内容,例如:
React.createElement('div', {'id': 'div1'}, 'hello world')`
React.createElement('div', {'id': 'div1'}, React.createElement('span', {}, 'hello'))`
虚拟DOM优点:
- 性能提升,DOM的比对变换成JS对象的比对;
- 跨端应用得以实现;
为什么React的setState()要被设计成一个异步的方法?
假如,这一个页面中,现在需要设置3次state,且三次设置间隔时间极短,如果每次都去都去修改state,极其浪费性能,React的底层会将3次setState合并成一个setState,只需做一次虚拟DOM的比对,然后去更新一次DOM,这样就可以省去另外两次setState产生的性能浪费,所以设计成异步方法的初衷,是为了提升性能。
虚拟DOM的Diff算法
Diff算法:difference算法,主要用于找差异,用于找新旧虚拟DOM间的差异

Diff算法有一个很重要的概念是同级比较,例如:在生成新的虚拟DOM后,Diff算法开始比较,先从第一级开始比较,如果第一级一样,就会比较第二级,以此类推。如果第一级不同,将不会继续比较将会把这一级下所有的旧虚拟DOM替换,然后替换真实的DOM。
虽然这么做可能会造成DOM重新渲染的浪费,但是会大幅提升比对的速度和性能。

Key值会大幅提升比对的性能。同时,根据下图比对的方法,不建议循环数组时使用 index 做 key 值,因为当数组长度发生变化时,index值可能会改变,那key值就发生改变,就会影响性能,所以尽量选用稳定的值做 key 值

React基础
渲染逻辑
当组件的state或者props发生改变的时候,render函数就会重新执行
当父组件的render函数被执行时,它的子组件的render都将被重新执行
特性
- 声明试开发:减少
DOM操作的代码量; - 可以与其他框架共存;
- 组件化,UI组件(负责数据展示)和容器组件(负责业务逻辑);
- 单向数据流(one-way stream of data),子组件只能使用值,不能去直接改变值;
- 视图层框架;
- 函数式编程:业务复杂的时候可以对函数进行拆分,各司其职,方便前端自动化测试,给值就能测试;
创建项目
- 安装脚手架
npm install -g create-react-app create-react-app (project name)项目名不能用大写npm start
setState
# 如果直接 return 的函数,可以这样写
this.setState(() => ({
inputValue: inputValue
}), () => {
console.log("异步的回调函数,非必填");
});
父子组件传值
- 父组件给子组件传值通过属性
- 子组件修改父组件的数据,通过调用方父组件法,间接的操作父组件的数据
PropTypes对传递的参数进行强校验
官方文档:react.docschina.org/docs/typech…
引入PropTypes,import PropTypes from 'prop-types';
// 对传递的参数强校验
TodoItem.propTypes = {
content: PropTypes.string.isRequired, // 限制为字符串且必传
deleteItem: PropTypes.func, // 限制为函数
index: PropTypes.number // 限制为数字
};
export default TodoItem;
传递参数的默认值
// 参数默认值,父组件未传值时的默认值
TodoItem.defaultProps = {
test: 'hello world'
}
使用 ref 操作 DOM
<div ref={(box) => {this.refBox = box}}></div>
React 的生命周期
在某一时刻组件会自动调用执行的函数

Initialization 初始化
constructor():class的构造函数,并非React独有
Mounting 挂载
componentWillMount(): 在组件即将被挂载到页面的时刻自动执行;render(): 页面挂载;componentDidMount(): 组件被挂载到页面之后自动执行;
componentWillMount() 和 componentDidMount(),只会在页面第一次挂载的时候执行,state变化时,不会重新执行
Updation 组件更新
shouldComponentUpdate(): 该生命周期要求返回一个bool类型的结果,如果返回true组件正常更新,如果返回false组件将不会更新;componentWillUpdate(): 组件被更新之前执行,如果shouldComponentUpdate()返回false,将不会被被执行;componentDidUpdate(): 组件更新完成之后执行;
componentWillReceiveProps() : props独有的生命周期,执行条件如下:
- 组件要从父组件接收参数;
- 只要父组件的
render()被执行了,子组件的该生命周期就会执行; - 如果这个组件第一次存在于父组件中,不会执行;
- 如果这个组件之前已经存在于父组件中,才会执行;
Unmounting 组件卸载
componentWillUnmount(): 当组件即将被从页面中剔除的时候,会被执行;
生命周期简单使用场景
- 使用
shouldComponentUpdate()防止页面进行不必要的渲染
# 用生命周期进行性能优化
shouldComponentUpdate () {
if (nextProps.content !== this.props.content) {
return true;
}
return false;
}
- Ajax 请求页面初始数据
componentDidMount()
不能写在render()之中,因为会重复调用,也不能写在componentWillMount()之中,会与RN等其它框架冲突,不然也可以写在这里面,同样是只执行一次。
同样也可以写在构造函数constructor()之中,但是不建议这样做。
import axios from 'axios'
componentDidMount () {
axios.get('/api/todolist').then((res) => {
console.log(res.data);
this.setState(() => ({
list: [...res.data]
}));
}).catch((err) => {
console.log(err);
});
}
React 的过渡动画
CSS过渡动画(transition & keyframes)
通过切换className,改变css效果,实现动画
<div className={this.state.show ? 'show' : 'hide'}>CssAnimation</div>
react-transition-group
官方文档:reactcommunity.org/react-trans…
当前版本react-transition-group包含4个组件:Transition, CSSTransition, SwitchTransition, TransitionGroup
CSSTransition
CSSTransition属性:
in: 判断进出场;timeout: 动画时间;unmountOnExit: 动画结束后移除元素;classNames: 动画的class;appear: 页面初始化时,是否需要进入效果;对应的class为.my-node-appear{}和.my-node-appear-active{}- 多种钩子函数 :
onEnter、onEntering、onEntered、onExit等;
npm install react-transition-group --save
import { CSSTransition } from 'react-transition-group';
------ JSX ------
<CSSTransition
in={this.state.show}
timeout={1000}
unmountOnExit
classNames="my-node"
onEntered={(element) => {element.style.color = 'red'}}
>
<div>react-transition-group CSSTransition</div>
</CSSTransition>
------ style.css ------
.my-node-enter, .my-node-appear {
opacity: 0;
}
.my-node-enter-active, .my-node-appear-active {
opacity: 1;
transition: opacity 1000ms;
}
.my-node-enter-done {
opacity: 1;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 1000ms;
}
.my-node-exit-done {
opacity: 0;
}
TransitionGroup
import { CSSTransition, TransitionGroup } from 'react-transition-group';
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
in={this.state.show}
timeout={1000}
unmountOnExit
appear={true}
classNames="my-node"
key={index}
>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
Redux
Redux = Reducer + Flux

Redux基本原则
Store必须是唯一,整个项目只能一个Store;- 只有
Store能够改变自己的内容,Store接收了action之后,将数据传给Reducer,Reducer将新的state传给Store,Store再更新自己的数据; Reducer必须是个传函数,即给定固定的输入,就一定会有固定的输出,而且不会有任何其它影响。例如函数里有Ajax请求、new Date()这种日期相关的内容之后,就不再是一个纯函数。
Redux常用API
createStore(): 创建Store对像;store.dispatch(): 派发action;store.getState(): 获取Store当中的数据;store.subscribe(): 订阅Store的改变;
Redux 工作流程
在
React中想要去改变Redux中store的数据,首先需要去开发一个action,action会通过store.dispatch(action)将数据传递给store,store收到dispatch之后,将action和旧的state转发给Reducer,Reducer是一个函数,在接收到了state和action之后,会进行一系列的处理,然后返回新的state给到store,store用新的state替换旧的state,在store数据发生改变之后,React的组件会感知到数据发生变化,这个时候组件会从store中重新取数据,更新组件的内容,页面即发生改变。

使用 Redux
安装 Redux
npm install redux --save
在项目中简单使用
1.在src下创建一个文件夹/src/store
2.在store目录下,创建文件index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer); // 创建存储仓库
export store;
3.在store目录下,创建文件reducer.js
const defaultState = {
inputValue: '',
list: []
};
export default (state = defaultState, action) => {
return state;
}
4.组件中调用
import store from './store/index';
// 构造函数
constructor (props) {
super(props); // 固定写法,调用父级的构造函数
this.state = store.getState();
}
使用 action 修改数据
页面上调用
// 构造函数
constructor (props) {
super(props); // 固定写法,调用父级的构造函数
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
// 订阅 store 的改变,只要store发生改变,该方法就会执行一次
store.subscribe(this.handleStoreChange);
}
// 调用
handleInputChange (e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
// store 变化时更新 state
handleStoreChange () {
this.setState(store.getState());
}
reducer.js中接收并获取
reducer 中很重要的一点,reducer可以接收state,但是觉得不能修改state,所以需要深拷贝
export default (state = defaultState, action) => {
// 根据不同的type,执行不一样的代码
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state)); // 深拷贝
newState.inputValue = action.value;
return newState
}
return state;
}
ActionTypes 和 ActionCreator
在 /src/store 下创建 actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_LIST_ITEM = 'add_list_item';
export const DELETE_LIST_ITEM = 'delete_list_item';
在 /src/store 下创建 actionCreator.js
import { CHANGE_INPUT_VALUE, ADD_LIST_ITEM, DELETE_LIST_ITEM } from './actionTypes'
export const getInputValueChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddListItemAction = () => ({
type: ADD_LIST_ITEM
});
export const getDeleteListItem = (itemIndex) => ({
type: DELETE_LIST_ITEM,
itemIndex
});
在组件中使用
import { handleInputChange } from './store/actionCreator'
handleInputChange (e) {
const action = getInputValueChangeAction(e.target.value);
store.dispatch(action);
}
Redux中发送http请求获取数据
// actionTypes.js
export const INIT_STORE_DATA = 'init_store_data';
// actionCreator.js
import { INIT_STORE_DATA } from './actionTypes';
export const getInitStoreData = (data) => ({
type: INIT_STORE_DATA,
data
});
// reducer.js
import { INIT_STORE_DATA } from './actionTypes';
export default (state = defaultState, action) => {
if (action.type === INIT_STORE_DATA) {
const newState = JSON.parse(JSON.stringify(state)); // 深拷贝
newState.list = action.data;
return newState;
}
return state;
}
// 组件中
import store from './store/index';
import { getInitStoreData } from './store/actionCreator';
componentDidMount () {
axios.get('/api/todolist').then((res) => {
const data = res.data;
const action = getInitStoreData(data);
store.dispatch(action);
});
}
Redux 中间件
Redux中间件Middleware,是对store.dispatch的封装、升级。

Redux-thunk 中间件进行异步请求
安装
npm install redux-thunk --save
在Store中的配置
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
// https://github.com/zalmoxisus/redux-devtools-extension
const middlewareList = [thunk]
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(...middlewareList));
const store = createStore(reducer, enhancer);
// 不需要使用 devtools 时的配置,devtools也是一个中间件
// const store = createStore(
// reducer,
// applyMiddleware(thunk),
// );
export default store;
在不使用redux-thunk的时候,action返回的是一个对象,在使用redux-thunk之后,action可以返回一个函数
在actionCreator.js中配置
import { INIT_STORE_DATA } from './actionTypes';
export const getInitStoreData = (data) => ({
type: INIT_STORE_DATA,
data
});
// 当使用 redux-thunk 返回一个函数时,函数可以接收到 store 的 dispatch 方法作为参数
export const getTodoList = () => {
return (dispatch) => {
axios.get('/api/todolist').then((res) => {
const data = res.data;
const action = getInitStoreData(data);
dispatch(action);
});
}
}
在组件中使用
import { getTodoList } from './store/actionCreator';
componentDidMount () {
const action = getTodoList();
store.dispatch(action);
}
Redux-saga 中间件进行异步请求
安装
npm install --save redux-saga
在Store中的配置
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import ToDoSagas from './sagas';
const sagaMiddleware = createSagaMiddleware();
const middlewareList = [sagaMiddleware];
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(...middlewareList));
const store = createStore(reducer, enhancer);
sagaMiddleware.run(ToDoSagas);
export default store;
在actionCreator.js中的配置
import { INIT_STORE_DATA } from './actionTypes';
export const getInitStoreData = (data) => ({
type: INIT_STORE_DATA,
data
});
在sagas.js中配置
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
import { INIT_STORE_DATA } from './actionTypes';
import { getInitStoreData } from './actionCreator';
import axios from 'axios';
// Generator 函数
function* getInitList () {
try {
// Generator 函数处理异步请求可以加 yield 等待执行完成,类似 async/await
const res = yield axios.get('/api/todolist');
const action = getInitStoreData(res.data);
yield put(action); // 执行 action,类似 store.dispatch
} catch (e) {
console.log('网络请求异常');
}
}
// Generator 函数
function* ToDoSagas () {
// takeEvery 第一个参数为:捕获的Action的类型
// takeEvery 第二个参数为:捕获成功后,需要执行的方法
yield takeEvery(INIT_STORE_DATA, getInitList);
}
export default ToDoSagas;
在actionCreator.js中配置
import { INIT_STORE_DATA } from './actionTypes'
export const getInitStoreData = () => ({
type: INIT_STORE_DATA
});
在组件中使用
import { getInitStoreData } from './store/actionCreator';
componentDidMount () {
const action = getInitStoreData();
store.dispatch(action);
}
Redux-thunk 和 Redux-saga 取舍
- 共同点:两者同样是都是Redux的中间件,同样是对
store.dispatch的封装,同样可以在封装之后使得提交action的时候可以使用一些异步方法及数据处理。 - 不同点:
Redux-thunk没有提供丰富的API,主要功能是使得action能够返回自动执行的函数 - 不同点:
Redux-saga提供了丰富的API,在dispatch之前可以先进入自己写的sagas.js执行异步方法及数据处理。 - 在超大型项目中,
Redux-saga适用于大型及超大型项目,因为其提供了丰富的API及单独拆分的sagas.js文件,可以方便运营维护,但是也使得项目变得更加复杂。其它情况下使用Redux-thunk都更加方便快捷。
React-redux
React-redux可以使,在 React 项目中使用 Redux 更加方便。
安装
npm install react-redux --save
Provider 组件
通过 Provider 将 Redux 的 store 提供给 Provider 下的所有组件。
import { Provider } from 'react-redux';
import store from './store';
import ReactRedux from './ReactRedux';
// 通过 Provider 将 Redux 的 store 提供给 Provider 下的所有组件
const App = (
<Provider store={store}>
<ReactRedux />,
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
connect()
使业务组件和store进行连接,该业务组件必须在Provider组件下
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
class ReactRedux extends Component {
constructor (props) {
super(props)
}
render () {
return (
<Fragment>
<div>
<input placeholder="你接下来想做什么" value={this.props.inputValue} onChange={this.props.changeInputValue}/>
<button onClick={this.props.handleClick}>提交</button>
</div>
<ul>
{this.props.list.map((item, index) => (<li key={index} onClick={() => {
return this.props.handleItemDelete(index)
}}>{item}</li>))}
</ul>
</Fragment>
)
}
}
// 将 Redux 的 store 挂载到 props
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}
// 将 Redux 的 dispatch 挂载到 props
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue (e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
dispatch(action)
},
handleClick () {
const action = {
type: 'add_list_item'
}
dispatch(action)
},
handleItemDelete (itemIndex) {
const action = {
type: 'delete_list_item',
itemIndex
}
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReactRedux);
组件化
- UI组件:只负责数据展示;
- 容器组件:负责业务逻辑;
无状态组件
当一个组件只有一个render()函数时,我们就可将这个组件定义为无状态组件,无状态组件只有一个函数。
无状态组件的性能比较高,因为它仅是一个函数,而普通组件是一个class.
import React, { Component, Fragment } from 'react';
import { Input, Button, Typography, List } from 'antd';
// 无状态组件
const TodoListUI = (props) => {
const { Title } = Typography;
return (
<div className="wrapper">
<Title>To Do List By Actoress</Title>
<div className="box">
<Input
placeholder="你接下来想做什么..."
value={props.inputValue}
style={{ width: '320px' }}
size="large"
onChange={props.handleInputChange}
/>
<Button type="primary" size="large" onClick={props.handleButtonClick}>提交</Button>
</div>
<List
size="large"
dataSource={props.list}
renderItem={(item, index) => <List.Item onClick={(index) => {
props.handleItemDelete(index);
}}>{item}</List.Item>}
style={{ width: '390px', margin: '18px auto 0' }}
/>
</div>
)
}
export default TodoListUI;