笔记继续…
一、在容器组件中拆分出UI组件
上一篇中说到的Todolist.js
就是容器组件,它的render方法
中返回了很多的UI组件
,所以有必要把他们拆分一下
src根目录下新建一个TodolistUI.js
文件,将所有的UI组件放在这里面
拆分后,UI组件里面的this.state
都无法获取到了,这时就需要父组件(即容器组件)将this.state
和方法
作为参数,传递给UI组件
Todolist.js:
render() {
return (
//将this.state、方法都作为参数传递给UI组件
<TodolistUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleDeleteClick={this.handleDeleteClick}
/>
);
}
TodolistUI.js:
import React, { Component, Fragment } from 'react';
import { Input, Button, List } from 'antd';
class TodolistUI extends Component {
render() {
return (
<Fragment>
<Input
placeholder='placeholder'
value={this.props.inputValue}
onChange={this.props.handleInputChange}
/>
<Button
type="primary"
onClick={this.props.handleBtnClick}
>提交</Button>
<List
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item
onClick={() => {
this.props.handleDeleteClick(index)
}}
>
{item}
</List.Item>
)}
/>
</Fragment>
);
}
}
export default TodolistUI;
UI组件中,需要将原先所有的this.state
换成this.props
,将原先的this.方法名
换成this.props.方法名
注意:如果此时仍需要向方法传递一个参数,比如:index,就不能直接写成这种形式:onClick={this.props.handleDeleteClick(index)}
,要改写成这种形式:
onClick={() => {
this.props.handleDeleteClick(index)
}}
二、无状态组件改写成函数
无状态组件
从字面来理解,就是没有state
的组件,这样的组件只有一个render
方法。
实际使用的时候最好改写成一个函数,将render
的return
改写成一个函数的return
。
这样改写的好处就是提高性能
,毕竟创建一个函数比创建一个类性能上要有所提高。
假设有一个无状态组件
:
class Todolist extends React.Component {
render() {
return (
//..
);
}
}
那么就可以将其改写成:
const Todolist = (props) => {
return (
//..
);
}
改写后的函数接收一个参数props
,这样原来组件里用到的this.props
换成props
就行了
三、redux发送异步请求获取数据
我的charles用不了,所以我还是用在线接口数据模拟吧: Mock your HTTP responses to test your REST API
第一篇笔记的最后说过:componentDidMount
这个钩子函数的一个用途就是:把只需要发送一次的ajax请求
的代码写在里面
所以redux发送异步请求获取数据的代码放在componentDidMount
函数里:
Todolist.js:
import { getInitialListItemAction } from './store/actionCreators';
componentDidMount() {
//下面这个地址就是在线模拟的一个接口的地址
axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json')
.then((res) => {
const data = res.data; //获取模拟的数据
//将数据传给getInitialListItemAction方法,来生成相应的action
//这个方法的实现在其他文件里
const action = getInitialListItemAction(data);
store.dispatch(action);
})
.catch((error) => {
alert(error);
})
}
先定义一下action的type:
actionTypes.js:
export const INITIAL_LIST_ITEM = 'initial_list_item';
再定义getInitialListItemAction 方法:
actionCreator.js:
import { INITIAL_LIST_ITEM } from './actionTypes';
export const getInitialListItemAction = (data) => ({
type: INITIAL_LIST_ITEM,
data
})
这样上面Todolist.js
里的:const action = getInitialListItemAction(data); 就能获取到action
了。
然后再使用:store.dispatch(action); 将action
传递给store
和reducer
。
最后reducer
根据action
对store
中的数据进行更新:
reducer.js:
import { INITIAL_LIST_ITEM } from './actionTypes';
const defaultState = {
inputValue: '',
list: []
};
export default (state = default, action) => {
if (action.type === INITIAL_LIST_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data; //将store中list的值更新为获取到的数据
return newState;
}
return state;
}
之后页面重新渲染。
四、redux-thunk处理异步请求
如果将redux发送异步请求获取数据
的代码都放在componentDidMount
钩子函数中,随着业务逻辑的增加,这个钩子函数可能会变的越来越臃肿。
所以可以利用redux-thunk
将redux发送异步请求获取数据
的代码放在其他函数中。
不使用
redux-thunk
时,action
只能是一个对象,有了redux-thunk
之后,action
就可以是一个函数了。
- 安装:
npm install redux-thunk --save
- redux-thunk项目的github地址:https://github.com/reduxjs/redux-thunk
- 使用
redux-thunk
中间件:
一些必须的配置,index.js(src目录下的):
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
由于已经使用了redux-devtools这个中间件,所以想要同时使用两个中间件,要使用下面的方法:
redux-devtools的github上有文档说明:redux-devtools-extension
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk)
);
const store = createStore(
reducer,
enhancer
);
之后就可以让action
是一个函数了。
如果不使用
redux-thunk
,并且让action
是一个函数的话,就会报以下错误:
Error: Actions must be plain objects. Use custom middleware for async actions.
就是说:action必须是一个对象
下面是将redux发送异步请求获取数据
的代码放在其他函数中的具体实现:
actionCreators.js:
import axios from 'axios';
export const getListItem = () => {
return (dispatch) => { //store的dispatch方法可以作为一个参数被接收
axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json')
.then((res) => {
const data = res.data;
const action = getInitialListItemAction(data);
dispatch(action);
})
.catch((error) => {
alert(error);
})
};
}
Todolist.js:
import getListItem from './store/actionCreators';
componentDidMount() {
const action = getListItem();
store.dispatch(action); //当dispatch接收到一个函数参数时,会自动执行这个函数
}
代码原理很简单,就是把异步请求数据的代码
放在其它函数中,然后在componentDidMount
中用dispatch
方法执行这个函数即可。
五、redux-saga处理异步请求
和redux-thunk
一样的是,redux-saga
都可用于处理异步请求的代码。redux-thunk
的原理是通过对dispatch方法
进行升级,使其能接受一个函数作为参数(即action
可以是一个函数了),然后将异步请求的代码写在一个非钩子函数中。而redux-saga
的原理就有点绕了:
- 安装:
npm install redux-saga --save
- redux-saga项目的github地址:https://github.com/redux-saga/redux-saga
- 使用:
一些必须的配置,index.js(src目录下的):
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga'; //saga具体的实现代码都单独放在一个文件中
const sagaMiddleware = createSagaMiddleware(); //生成saga中间件
const composeEnhancers = //redux-devtools中间件
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware)); //同时使用两个中间件
const store = createStore( reducer, enhancer ); //使用reducer和中间件创建store
sagaMiddleware.run(mySaga); //运行mySaga函数(这个函数定义在saga.js文件中)
export default store;
saga具体的实现代码都单独放在一个文件中,所以在store
文件夹下新建一个saga.js
文件
saga.js:
import { takeEvery } from 'redux-saga/effects'; //takeEvery是saga中的一个方法
function* getInitListItem() {
}
function* mySaga() {
//takeEvery方法:每当接收到INIT_LIST_TYPE参数,都会自动执行getInitListItem函数
//每当组件向store传递action时,saga 和 reducer都会默认接收到这个action
//所以让组件向store传递action,takeEvery就会自动执行getInitListItem函数
//然后将异步请求的代码放在getInitListItem函数中就能实现获取数据
yield takeEvery(INIT_LIST_TYPE, getInitListItem);
}
export default mySaga;
saga.js
文件里的大体框架如上,其中的函数都必须是ES6
的generator函数
上面的getInitListItem
函数具体实现如下:
import { put } from 'redux-saga/effects';
import { getInitListItemAction } from './actionCreators';
function* getInitListItem() {
const res = yield axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json');
const action = getInitListItemAction(res.data);
yield put(action); //put是saga中的一个方法
}
getInitListItemAction是生成action
的方法,它的定义如下:
actionCreators.js:
import { INITIAL_LIST_ITEM } from './actionTypes';
export const getInitListItemAction = (data) => ({
type: INITIAL_LIST_ITEM,
data
})
INITIAL_LIST_ITEM被定义在actionTypes.js
中:
actionTypes.js:
export const INITIAL_LIST_ITEM= 'initial_list_item';
getInitListItem
函数通过put方法
向store
发送action
,默认地reducer
也接收到action
。
通过reducer
更新store
的代码如下:
import { INITIAL_LIST_ITEM } from './actionTypes';
export default (state = defaultState, action) => {
if (action.type === INITIAL_LIST_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState;
}
}
saga.js中完整的代码如下:
import { takeEvery, put } from 'redux-saga/effects';
import { INIT_LIST_TYPE } from './actionTypes';
import { getInitListItemAction } from './actionCreators';
import axios from 'axios';
function* getInitListItem() {
const res = yield axios.get('http://www.mocky.io/v2/5ba773293200006900e2e95e/text/json');
const action = getInitListItemAction(res.data);
yield put(action);
}
function* mySaga() {
yield takeEvery(INIT_LIST_TYPE, getInitListItem);
}
export default mySaga;
做到这还不行,saga.js
的代码写完了,还要组件给saga
发送action
才行:
Todolist.js:
import { getInitListType } from './store/actionCreators';
componentDidMount() {
const action = getInitListType();
store.dispatch(action);
}
由于这个action
只起到传递信息的作用就行了,所以只需要getInitListType返回action
的type
:
actionCreators.js:
import { INIT_LIST_TYPE } from './actionTypes';
export const getInitListType = () => ({
type: INIT_LIST_TYPE
});
INIT_LIST_TYPE定义在:
actionTypes.js:
export const INIT_LIST_TYPE = 'init_list_type';
这样componentDidMount函数执行时,会向store
传递一个action
,saga
也会接收到这个action
,saga
接收到这个action
后通过takeEvery(INIT_LIST_TYPE,getInitListItem);
这段代码,自动执行getInitListItem函数
,getInitListItem函数
中写有获取数据并更新store
的代码,所以,就实现了:通过saga进行异步获取数据的ajax请求
。
到底用redux-thunk
还是redux-saga
来异步请求数据,还要视情况而定。
六、redux中间件
上面一直说的中间件指的是:redux中action和store之间的中间件。
七、react-redux
顾名思义,react-redux
就是用来将react
和redux
连接起来。
- 安装:
npm install react-redux --save
- 使用:
index.js(src根目录下的):
//import React from 'react';
//import ReactDOM from 'react-dom';
//import Todolist from './Todolist';
import { Provider } from 'react-redux';
const App = (
<Provider store={store}> //包裹在Provider中的容器组件,都将接收到store
//这样就将react和redux连接了起来
<Todolist /> //一个容器组件 //可以有多个容器组件,并且所有组件都将接收到store
</Provider>
);
ReactDOM.render(App, document.getElementById('root'));
容器组件中的代码:
Todolist.js:
//import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
class Todolist extends Component {
render() {
//...
}
}
const mapStateToProps = (state) => {
return {
//这里写state数据,比如:
inputValue: state.inputValue
}
}
const mapDispatchToProps = (dispatch) => {
return {
//这里写需要将action dispatch到store的方法,例如:
handleClickBtn(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
dispatch(action);
}
}
}
//通过react-redux中的connect方法,将组件Todolist中的state,dispatch映射到props
//mapStateToProps和mapDispatchToProps分别实现映射
export default connect(mapStateToProps, mapDispatchToProps)(Todolist);
映射完之后,Todolist组件
中所有的:this.state
和this.方法名
,都直接替换成:this.props
和this.props.方法名
有错误或不足,欢迎评论指正~
待续…