前言:上一篇将来redux的基础知识,这篇将以todoList的例子继续
1.区分UI组件和容器组件和无状态组件
1.1、容器组件负责逻辑处理(例如App.js)
1.2、ui组件主要负责渲染;
上文中APP.js中的逻辑和元素都在一个文件里,因此将jsx提取出来,在src目录下面新建一个component文件夹,下面新建一个todoListUI.js文件,将App.js中的render函数里面的内容提取出来成为一个组件。
App.js中引入todoListUI组件之后,在render函数里面直接渲染返回组件(一般以大写字母开头):
import TodoListUI from "./component/todoListUI"
render () {
return <TodoListUI
inputValue = {this.state.inputValue}
changeInputValue = {this.changeInputValue}
btnClick = {this.btnClick}
list = {this.state.list}
clickItem = {this.clickItem}
/>
};
todoListUI.js的代码:
import React, { Component } from "react"
import 'antd/dist/antd.css'
import { Input,Button,List } from 'antd';
class todoListUI extends Component {
render() {
return (<div>
<div>
<Input
value = {this.props.inputValue}
placeholder="请输入"
style={{width:'300px',marginRight:'10px'}}
onChange={this.props.changeInputValue}
/>
<Button type="primary" onClick={this.props.btnClick}>提交</Button>
<List
style={{width:'300px',marginTop:'10px'}}
size="small"
bordered
dataSource={this.props.list}
renderItem={(item,index) => <List.Item onClick={() => {this.props.clickItem(index)}}>{item}</List.Item>}
/>
</div>
</div>)
}
}
export default todoListUI
注意clickItem这个函数需要传递参数的写法。
1.3、无状态组件
可以看到上面todoListUI组件里面只有一个render函数的时候,这种时候一般都可以写成一个函数表示的无状态组件,渲染性能会更高,更快:
import React, { Component } from "react"
import 'antd/dist/antd.css'
import { Input,Button,List } from 'antd';
const todoListUI = (props) => {
return (<div>
<div>
<Input
value = {props.inputValue}
placeholder="请输入"
style={{width:'300px',marginRight:'10px'}}
onChange={props.changeInputValue}
/>
<Button type="primary" onClick={props.btnClick}>提交</Button>
<List
style={{width:'300px',marginTop:'10px'}}
size="small"
bordered
dataSource={props.list}
renderItem={(item,index) => <List.Item onClick={() => {props.clickItem(index)}}>{item}</List.Item>}
/>
</div>
</div>)
}
// class todoListUI extends Component {
// render() {
// return (<div>
// <div>
// <Input
// value = {this.props.inputValue}
// placeholder="请输入"
// style={{width:'300px',marginRight:'10px'}}
// onChange={this.props.changeInputValue}
// />
// <Button type="primary" onClick={this.props.btnClick}>提交</Button>
// <List
// style={{width:'300px',marginTop:'10px'}}
// size="small"
// bordered
// dataSource={this.props.list}
// renderItem={(item,index) => <List.Item onClick={() => {this.props.clickItem(index)}}>{item}</List.Item>}
// />
// </div>
// </div>)
// }
// }
export default todoListUI
2.redux中的中间件来执行异步操作
2.1.传统的要执行获取数据的请求可以放在声明周期函数componentDidMounted
方法里面,在App.js里面:
componentDidMount() {
axios.get("https://www.easy-mock.com/mock/5dab0c2f535c0573eacc1c3c/api/defalutList.json").then(res => {
console.log(res);
let listDefaultData = res.data;
let action = listDefaultDataAction(listDefaultData);
store.dispatch(action);
})
};
可以使用redux的中间件来请求数据,是页面更加清晰明了。
2.2、什么是中间件
首先:中间件一般是只action和store中间的中间件,中间件是封装的方法对action进行某些处理在传递给store.中间件扩展了action,使用的时候也需要遵循相应的api.
3、几种常用的中间件
3.1、中间件redux-thunk的安装和注册
安装:npm install redux-thunk
引入:import thunk from 'redux-thunk';
使用:const store = createStore(reducer, applyMiddleware(thunk));
注意此处我们因为用了redux-devTools这个调试工具,要添加一个参数window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
,因此加上中间件thunk,就是两个参数,不能直接写成const store = createStore(reducer, applyMiddleware([thunk,window.REDUX_DEVTOOLS_EXTENSION]));
查找文档。最终store/index.js的代码为
import { createStore,applyMiddleware,compose } from 'redux'
import thunk from 'redux-thunk';
import reducer from './reducer'
const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;
具体安装和引入可以参照github上面的redux-thunk,redux-devTools-extensions文档。
3.2、redux-thunk的使用
以前action只能返回一个对象,有了rex-thunk之后可以返回一个函数。用于处理逻辑或者异步请求。因此在actionCreators中可以写一个action为:
export const listDefaultDataAction = (data) => ({
type:LIST_DEFAULT_DATA,
data,
})
// 使用了redux-thunk之后可以action可以返回的是一个函数,
export const getDefaultList = () => {
return (dispatch) => { //会获取store.dispatch这个函数作为参数
axios.get("https://www.easy-mock.com/mock/5dab0c2f535c0573eacc1c3c/api/defalutList.json")
.then(res => {
console.log(res);
let listDefaultData = res.data;
let action = listDefaultDataAction(listDefaultData); //此处任然返回的是一个对象。
dispatch(action); //将返回的action(对象)提交给action,action会触发给reducer.js
})
}
}
在APP.js的componentDidMount声明周期函数中调用和触发执行返回的action函数。
componentDidMount() {
let action = getDefaultList();
store.dispatch(action) //注意此处是把action(一个函数)发给store,action这个函数会自动调用,
};
3.3、中间件redux-saga的安装和注册
redux-saga比redux-thunk复杂。除了能处理异步请求之外,很多复杂的逻辑也可以用redux-saga分离出来。这里只是简单说一下reux-saga处理异步
3.3.1.redux安装和注册store.index.js中的代码(具体看官网文档):
import { createStore,applyMiddleware,compose } from 'redux'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import todoSaga from './todoSaga.js'
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
)
sagaMiddleware.run(todoSaga)
export default store;
redux-saga要新建一个用于存储处理逻辑的文件,该文件里面必须是generator函数,sagaMiddleware.run(todoSaga)用于启动saga.
3.3.2.App.js中的声明周期函数还是跟原来一样,通过dispatch提交一个对象
componentDidMount() {
let action = initListAction();
store.dispatch(action)
};
// initListAction的代码:
export const initListAction = () => ({
type:INIT_LIST_DATA
})
3.3.3.todoSaga.js里面的代码
import { put, takeEvery } from 'redux-saga/effects'
import { INIT_LIST_DATA } from "./actionType"
import { listDefaultDataAction } from "./actionCreators"
import axios from "axios"
function* initList() {
try {
let res = yield axios.get("https://www.easy-mock.com/mock/5dab0c2f535c0573eacc1c3c/api/defalutList.json")
const actionObj = yield listDefaultDataAction(res.data); //返回的是一个对象
yield put(actionObj); //put方法用于将actionObj 提交给store处理。
}catch(e) {
const actionObj = yield listDefaultDataAction(["请求接口报错"]);
yield put(actionObj);
}
};
function* todoSaga() {
yield takeEvery(INIT_LIST_DATA, initList); //takeEvery用于捕获所有的通过dispatch提交过来的action.第一个函数是提交的action对应的type,第二个函数是对应的action要处理的逻辑。
}
export default todoSaga;
listDefaultDataAction这个action的代码:
export const listDefaultDataAction = (data) => ({
type:LIST_DEFAULT_DATA,
data,
})
3.3.4.最后在reducer.js中处理sagat提交过来的数据:
if(action.type === LIST_DEFAULT_DATA) {
let newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState;
}
3.3.5最后要说一下redux-saga另一个重要的方法 call(),我们将generate函数initList改变一下
function* initList() {
try {
let res = yield call(getList ) //call()方法用于调用异步函数,如果需要传递参数则call(函数名, 参数)
const actionObj = yield listDefaultDataAction(res.data); //返回的是一个对象
yield put(actionObj); //put方法用于将actionObj 提交给store处理。
}catch(e) {
const actionObj = yield listDefaultDataAction(["请求接口报错"]);
yield put(actionObj);
}
};
const getList = () => {
return axios.get("https://www.easy-mock.com/mock/5dab0c2f535c0573eacc1c3c/api/defalutList.json")
}
4.react-redux的使用
react-redux是专门为react封装的redux.
代码从头开始,以todoList为例:
index.js:
import React from 'react';
import ReactDom from "react-dom"
import { Provider } from "react-redux"
import TodoList from './App';
import store from "./store/index"
const App = (
<Provider store={store}>
<TodoList />
</Provider>
);
ReactDom.render(App,document.getElementById('root'));
react-redux提供了Provider 组件,将store传递进Provider后,里面的子组件中都可以拿到store对象。
App.js中:
import React,{ Component } from "react"
import { connect } from "react-redux"
const TodoList = (props) => {
let { inputValue,list, changeInputValue, handleClick, deleteItem} = props;
return (
<div>
<input value={inputValue} onChange={changeInputValue}></input>
<button onClick={handleClick}>提交</button>
<ul>
{
list.map((item,index) =>{
return (
<li key= {index} onClick={deleteItem.bind(this,index)}>{item}</li>
)
})
}
</ul>
</div>
)
}
const mapStateToProps = (state)=> {
return {
inputValue:state.inputValue,
list:state.list
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type:"change_input_value",
value:e.target.value
}
dispatch(action);
},
handleClick() {
const action = {
type:"add_item",
}
dispatch(action);
},
deleteItem(index) {
const action = {
type:"delete_item",
index,
}
dispatch(action);
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);
react-redux提供的另一个重要的api就是connect方法,用于将一个无状态组件和业务逻辑和数据处理连接起来生成一个容器组件。
上文中通过connect()()方法将无状态组件todoList与业务逻辑连接,生成一个容器组件导出去。connect有两个参数。
第一个参数是将store中的数据映射到页面中props中的属性的函数mapStateToProps:可以接收两个参数,一个是store的state,还可以使用第二个参数,代表容器组件的props对象。映射规则就是返回来的对象。
inputValue:state.inputValue,
list:state.list
就是将store中state的inputValue(list)映射到props的inputValue (list)中,state中的数据改变,props中的数据也会跟着改变。从而试图变化。
第二个参数mapDispatchToProps:
用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。DispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type:"change_input_value",
value:e.target.value
}
dispatch(action);
},
handleClick() {
const action = {
type:"add_item",
}
dispatch(action);
},
deleteItem(index) {
const action = {
type:"delete_item",
index,
}
dispatch(action);
}
}
store/index.js(创建store):
import { createStore } from "redux"
import reducer from "./reducer"
const store = createStore(reducer);
export default store
store/reducer.js:
const data= {
inputValue:"",
list:[],
}
export default (state = data, action) => {
if(action.type==="change_input_value") {
let newData = JSON.parse(JSON.stringify(state));
newData.inputValue = action.value;
return newData;
}
if(action.type==="add_item") {
let newData = JSON.parse(JSON.stringify(state));
newData.list.push(newData.inputValue);
newData.inputValue = "";
return newData;
}
if(action.type==="delete_item") {
let newData = JSON.parse(JSON.stringify(state));
newData.list.splice(action.index,1);
return newData;
}
return state;
}
5、React-Router 路由库
使用React-Router的项目,与其他项目没有不同之处,也是使用Provider在Router外面包一层,毕竟Provider的唯一功能就是传入store对象。
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
);
推荐阮一峰老师写的react-redux的一二三篇文章,清晰明了仔细http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html