目录
1.UI组件(渲染)和容器组件(逻辑)的拆分
将 TodoList.js 拆分成 容器组件TodoList.js 和 UI组件TodoListUI.js 。
(1)TodoList.js
import React, {Component} from 'react';
import 'antd/dist/antd.css';
import ToDoListUI from './TodoListUI';
import store from './store/index';
import {getInputChangeAction, getAddItemAction, getDeleteItemAction} from './store/actionCreators';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
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.subscribe(this.handleStoreChange) //store中数据改变后执行的方法
}
render() {
return <ToDoListUI inputValue={this.state.inputValue} list={this.state.list}
handleInputChange = {this.handleInputChange}
handleButtonClick={this.handleButtonClick}
handleItemDelete={this.handleItemDelete}/>
}
handleInputChange(e) {
const action=getInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(store.getState()); //执行setState后,页面才会重新渲染
}
handleButtonClick(){
const action = getAddItemAction();
store.dispatch(action)
}
handleItemDelete(index){
const action = getDeleteItemAction(index)
store.dispatch(action)
}
}
export default TodoList;
(2)TodoListUI.js
import React,{Component} from 'react';
import {Button, Input, List} from "antd";
class TodoListUI extends Component{
render(){
return (
<div style={{marginTop: 10, marginLeft: 10}}>
<div>
<Input value={this.props.inputValue}
placeholder="todo info"
style={{width: 300, marginRight: '10px'}}
onChange={this.props.handleInputChange}/>
<Button type="primary" onClick={this.props.handleButtonClick}>提交</Button>
<List style={{marginTop: 10, width: 300}}
bordered
dataSource={this.props.list}
renderItem={(item,index) => (
<List.Item onClick={(index)=>{this.props.handleItemDelete(index)}}>{item}</List.Item>
)}
/>
</div>
</div>
)
}
}
export default TodoListUI;
2.无状态组件
(当一个组件内只有render()函数的时候,我们可以使用无状态组件替换之前的普通组件)
普通组件中包含render等生命周期函数,消耗性能;而无状态组件就是一个函数,因此性能高。
将上面的 UI组件TodoListUI.js 替换 为下面的无状态组件TodoListUI.js:
注:无状态组件(就是一个函数),需要接收容器组件传递来的参数props。
import React from 'react';
import {Button, Input, List} from "antd";
const TodoListUI = (props)=>{
return (
<div style={{marginTop: 10, marginLeft: 10}}>
<div>
<Input value={props.inputValue}
placeholder="todo info"
style={{width: 300, marginRight: '10px'}}
onChange={props.handleInputChange}/>
<Button type="primary" onClick={props.handleButtonClick}>提交</Button>
<List style={{marginTop: 10, width: 300}}
bordered
dataSource={props.list}
renderItem={(item,index) => (
<List.Item onClick={()=>{props.handleItemDelete(index)}}>{item}</List.Item>
//注意!!!外面加了个箭头函数
)}
/>
</div>
</div>
)
};
export default TodoListUI;
3.Redux中发送异步请求获取数据
(与react中的ajax请求类似,只不过是通过Redux Flow流程进行)
4.使用Redux-thunk中间件进行ajax请求发送
(1)安装redux-thunk中间件:npm install redux-thunk
(2)创建store时,使用thunk中间件(同时使用devtools开发者工具)
文档:https://github.com/zalmoxisus/redux-devtools-extension中的1.2 Advanced store setup。
redux-thunk中间件:使得可以在action中写异步代码。
(3)原理:使用redux-thunk后action可以返回对象,也可以返回函数;因此,可以将ajax异步操作(一个函数方法)整体放到actionCreator中当做一个action进行创建,并将action返回,再按照Redux流程继续执行。
但store接受的action只能是对象,当store检测到action是一个函数时,会自动的执行该函数。该函数首先进行ajax获取数据,然后改变store中的数据(遵循redux流程,创建action,dispatch(action)。)
()建议:开发复杂项目时,最好将异步操作、复杂的业务逻辑,不要放在生命周期函数、组件内,将其拆分到其他地方。比如可以借助redux-thunk将其放到actionCreator中进行管理,或者使用下面的redux-saga放到单独一个文件中。
1.TodoList.js
import {getInputChangeAction, getAddItemAction, getDeleteItemAction, getTodoList} from './store/actionCreators';
componentDidMount() {
const action = getTodoList();
store.dispatch(action); //action动作是一个函数,当处理这个action时,就会执行该函数
}
2.actionCreators.js
import axios from "axios";
//没有使用thunk中间件之前,action只能返回对象如上所示,使用thunk后可以返回函数
export const getTodoList = () => {
return (dispatch)=>{
axios.get('/list.json').then((res)=>{
const data=res.data;
const action = initAjaxListAction(data);
dispatch(action);
})
}
};
5.什么时Redux中间件
Redux流程:
- view组件派发action;
- action通过store的dispatch方法派发给store;
- store接收到action连同之前的state传给Reducer;
- Reducer处理后返回新的数据给store;
- store根据新数据改变自己的state。
中间件:action派发给store,使用的就是store的dispatch方法;当使用中间件之后,派发的action从只能是对象升级为可以是对象或函数了,因此中间件改变的就是dispatch方法,所以中间件就是对dispatch方法的一个升级(封装)。
中间件是在创建store时设置的。
设置redux-thunk中间件后:
- 如果action是一个对象,调用dispatch方法时,直接将action对象派发给store。
- 如果action是一个函数,调用dispatch方法时,先执行该函数,执行完后,若需要向store派发action对象时,再进行派发action。
中间件在action、store之间,所以是Redux的中间件;中间件有很多,功能不同;
redux-thunk:将异步操作放在action中,action可以是函数
6.Redux-saga中间件的使用
文档:https://github.com/redux-saga/redux-saga
redux-saga:将异步代码拆分到单独的一个sagas.js文件中。(可以完全替代redux-thunk)
使用saga后,ActionCreator派发action后,不仅reducer.js可以接收到action,sagas.js也能捕获到action,因此,在sagas文件中可以根据action中传递的类型进行相应的异步操作(与Reducer中进行操作类似)。
流程:
- 安装redux-saga:npm install --save redux-saga
- 创建store时,配置redux-saga(官方文档)
- 创建sagas.js文件(其中一定要导出一个generator函数)
(1)TodoList.js
import { getInputChangeAction, getAddItemAction, getDeleteItemAction, getInitList} from './store/actionCreators';
componentDidMount() {
const action =getInitList(); //ajax请求是异步的,所以使用redux-saga中间件
store.dispatch(action);
}
(2)store / index.js(创建store,配置中间件)
import {createStore, applyMiddleware, compose} from "redux";
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import todoSagas from './sagas';
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware)
);
const store = createStore(reducer, enhancer); //创建store时,使用redux的两个中间件
// then run the saga
sagaMiddleware.run(todoSagas);
export default store;
(3)store / actionTypes.js
export const GET_INIT_LIST = 'get_init_list';
(4)store / actionCreators.js
export const initAjaxListAction= (data) => ({
type: INIT_LIST_ITEM,
data
});
export const getInitList=()=>({
type: GET_INIT_LIST
});
(5)store / sagas.js
import { put, takeEvery } from 'redux-saga/effects';
import {GET_INIT_LIST} from './actionTypes';
import axios from "axios";
import {initAjaxListAction} from './actionCreators'
//进行ajax异步请求数据,并写入list中(同样遵循Redux Flow)。
function* getInitList(){
try{
const res = yield axios.get('/list.json');
const action = initAjaxListAction(res.data);
yield put(action);
}catch(e){
console.log('list.json网络请求失败');
}
};
//ES6中的generator函数
function* mySaga() {
yield takeEvery(GET_INIT_LIST, getInitList); //不仅reducer.js能就收到action,这里也能捕获到action
//接收到GET_INIT_LIST类型的action后,就执行getInitList方法
}
export default mySaga;