索引:
UI组件和容器组件
无状态组件
Redux 发送AJAX请求
使用Redux-thunk中间件的使用
理解Redux中间件的工作原理
Redux-saga中间件的使用
React-Redux的使用
UI组件和容器组件
UI组件负责页面的渲染,容器组件负责页面的逻辑。我们可以对TodoList拆分成UI组件TodoListUI(只负责界面的显示,不负责业务逻辑)以及TodoList容器组件(只负责业务逻辑,不负责UI)。
TodoListUI.js
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 |
import React, {Component, Fragment} from 'react'; import 'antd/dist/antd.css'; import { List, Input, Button } from 'antd'; class TodoListUI extends Component{ render(){ return( <Fragment> <Input style={{marginTop:'20px',marginLeft:'20px',width:'300px'}} value={this.props.inputValue} onChange={this.props.handleInputChange}/> <Button type="primary" style={{marginLeft:'10px',marginTop:'20px'}} onClick={this.props.handleAddItem} >新增</Button> <List style={{marginTop:'10px',marginLeft:'20px',width:'350px'}} bordered dataSource={this.props.list} renderItem={(item) => (<List.Item onClick={(index)=>{this.props.handleDelete(index)}}>{item}</List.Item>)} /> </Fragment> ) } } export default TodoListUI; |
- 实际上是从父组件传过来参数给这个子组件(UI组件),使用props接收就好了
- 注意List.item上面的点击事件,由于点击的时候需要进行传参数,因此不能够写成和之前在.bind(this,index)这样传参的形式,我们可以使用一个箭头函数来进行传参。
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 |
import React,{ Component, Fragment } from 'react'; import store from "./store"; import {getInputChangeAction, getAddItemAction, getDelItemAction} from './store/actionCreator' import TodoListUI from './TodoListUI' class TodoList extends Component{ constructor(props){ super(props); this.handleInputChange = this.handleInputChange.bind(this); this.handleStoreChange = this.handleStoreChange.bind(this); this.handleAddItem = this.handleAddItem.bind(this); // 从store中获取初始数据 this.state = store.getState(); // 监听store的改变然后赋给state store.subscribe(this.handleStoreChange); } render(){ return( <TodoListUI inputValue = {this.state.inputValue} handleInputChange = {this.handleInputChange} handleAddItem = {this.handleAddItem} handleDelete = {this.handleDelete} list = {this.state.list} /> ) } handleAddItem(){ const action = getAddItemAction(); store.dispatch(action); } handleInputChange(e){ const action = getInputChangeAction(e.target.value); store.dispatch(action); } handleDelete(index){ const action = getDelItemAction(index); store.dispatch(action); } handleStoreChange(){ this.setState(store.getState()); } } export default TodoList; |
- 容器组件作为父组件传参、传方法给子组件
无状态组件
当一个组件只有render方法的时候就叫做无状态组件,无状态组件实际上是一个函数,之前的TodoListUI就是一个无状态组件,优势是性能很高,因为只是一个函数而不是类,一个类要执行生命周期函数等等消耗性能,因此如果看到一个组件只有一个render方法的时候就将其改写成无状态组件,下面对TodoListUI进行改写:
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 |
import React, {Component, Fragment} from 'react'; import 'antd/dist/antd.css'; import { List, Input, Button } from 'antd'; const TodoListUI = (props) => { return ( <Fragment> <Input style={{marginTop:'20px',marginLeft:'20px',width:'300px'}} value={props.inputValue} onChange={props.handleInputChange}/> <Button type="primary" style={{marginLeft:'10px',marginTop:'20px'}} onClick={props.handleAddItem} >新增</Button> <List style={{marginTop:'10px',marginLeft:'20px',width:'350px'}} bordered dataSource={props.list} renderItem={(item) => (<List.Item onClick={(index)=>{props.handleDelete(index)}}>{item}</List.Item>)} /> </Fragment> ); }; export default TodoListUI; |
- 注意,这个时候获取属性就直接props就可以了,不需要this
Redux 发送AJAX请求
实际上Redux发送AJAX请求的思路与之前是一样的,在componentDidMount中调用axios就可以获取到数据了,获取数据之后的操作流程与Redux常规的操作流程一致。
1 2 3 4 5 6 7 |
componentDidMount(){ axios.get('/api/todolist').then((res)=>{ const data = res.data; const action = initListAction(data); store.dispatch(action); }) } |
然后创建一个常量actionType
1 |
export const INIT_LIST_ACTION = 'init-list-action'; |
再创建一个actionCreator即可
1 2 3 4 |
export const initListAction = (data) =>({ type: INIT_LIST_ACTION, data }); |
使用Redux-thunk中间件进行ajax请求发送
之前发送的AJAX是在生命周期函数中进行的,这虽然很方便,但是随着项目的日益庞大可能导致组件的生命周期函数变得庞大起来,使用redux中间件直接的作用是可以将ajax请求在创建action的时候建立,大幅提升代码的可维护性,其原理主要是之前的dispatch的action只能够是一个对象,因此我们只能够通过ajax先获取了数据之后再把数据放入这个对象中交给store,使用了redux-thunk之后我们就可以是让dispatch的action是一个函数,但store接收到action是一个函数的时候就会去执行这个函数,因此我们可以把ajax请求写在这个函数中!这样就实现了一气呵成的创建action的时候返回一个ajax请求交给store,而不是先ajax请求数据然后把数据放进action中给store。
1 |
yarn add redux-thunk |
Redux-thunk的加载要在createStore的时候传入组件,但是由于之前使用了redux-devtools所以要这样的配置才行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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; |
之前的actionCreate都是函数返回一个对象,但使用了redux-thunk之后可以返回函数了,比较两种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 没有使用redux-thunk只能返回一个对象 export const initListAction = (data) =>({ type: INIT_LIST_ACTION, data }); // 使用redux-thunk还可以返回一个函数 export const getTodoList = () =>{ return(dispatch)=>{ // 在这个函数中可以做异步的操作 axios.get('/api/todolist').then((res)=>{ const data = res.data; const action = initListAction(data); // 有了数据之后创建一个action dispatch(action); }) } } |
因此在componentDidMount的生命周期函数之前是这样写的
1 2 3 4 5 6 7 |
componentDidMount(){ axios.get('/api/todolist').then((res)=>{ const data = res.data; const action = initListAction(data); store.dispatch(action); }) } |
现在就可以这样写
1 2 3 4 5 6 7 8 |
componentDidMount(){ // 这里的action是函数 // 之所以能够dispatch一个函数就是因为使用了redux-thunk // 如果没有使用redux-thunk,那么action必须是一个对象 // store发现是一个函数之后就会去执行这个函数getTodoList() const action = getTodoList(); store.dispatch(action); } |
理解Redux中间件的工作原理
首先理解Redux的工作流程(数据流)
- View在Redux中会派发一个action
- Action通过store的dispatch方法会将action派发给store
- Store接收到action连同之前的state一起传给Reducer
- Reducer返回一个新的数据给Store
- store改变state
中间件指的是Action和Store之间对dispatch方法的封装
在没有使用中间件的情况下,Action只能是一个对象,直接派发给store,因为dipatch方法只能是对象;
使用了redux-thunk之后Action就可以是一个函数了,因为redux-thunk将dispatch方法封装了(升级了),如果传过来的是函数的时候会先执行这个函数;
因此redux-thunk会根据参数的不同执行不同的事情,如果是对象就直接交给store,如果是函数就先执行然后交给store。
Redux-saga中间件的使用
目前异步处理一般使用的就是Redux-thunk和Redux-saga,saga在处理非常大型项目的时候由于redux-thunk,因为提供了非常多的API,要复杂也难得多,小一点的项目推荐使用redux-thunk
Redux-sage和Redux-thunk的异同
- 同样是解决react中异步问题的中间件
- redux-thunk是把异步操作放在action
- redux-saga是单独的把异步逻辑拆分出来在单独的一个文件进行管理
安装
1 |
yarn add redux-saga |
在创建store的时候引入redux saga,这里主要做了
- 引入createSagaMiddleware
- 创建createSagaMiddleware
- 通过applyMiddleware去使用这个中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import {createStore, applyMiddleware, compose} from 'redux'; import reducer from './reducer'; import createSagaMiddleware from 'redux-saga'; import todoSagas from './sagas'; 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(todoSagas); export default store; |
sagas.js
- 在sagas中一定要导出一个generator函数
- 在generator函数中,使用了takeEvery方法,这个方法的意思是但action.type是xxx的时候就会执行某个generator函数
- 在执行得这个gernerator函数中取数据,然后把数据结果创建一个action派发给store
- action给了store之后给了reducer,reducer接收到了之后就将数据进行替换
注意:使用了try catch来处理接口请求失败的情况,否则没有取到数据或者接口异常的时候就会报错。
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 |
import {takeEvery,put} from 'redux-saga/effects'; import { GET_INIT_LIST } from './actionTypes'; import axios from "axios"; import {initListAction} from "./actionCreator"; import store from "./index"; // ES6 generator函数 function* mySaga() { // 捕获GET_INIT_LIST这个action.type就会执行getInitList yield takeEvery(GET_INIT_LIST, getInitList); } function* getInitList(){ try{ const res = yield axios.get('/api/todolist'); const action = initListAction(res.data); yield put(action); }catch (e) { // 处理没有获取到数据情况 console.log('list.json 网络请求失败'); } } export default mySaga; |
todolist.js
1 2 3 4 |
componentDidMount(){ const action = getInitList(); store.dispatch(action); } |
actuonCreator.js
1 2 3 4 5 |
// 没有使用redux-thunk只能返回一个对象 export const initListAction = (data) =>({ type: INIT_LIST_ACTION, data }); |
React-Redux的使用
React-Redux是一个第三方的模块,它可以使我们更加方便的再react中使用redux。
核心API
- Provider: 提供器连接了store,那么Provider里面所有的组件都有能力获得store里面的内容了;
- connect: Provider里面所有的组件通都可以通过connnect这个方法来与store做关联;两个参数,
1. mapStateToProps:将store中的数据作为props绑定到组件中,这个时候就可以使用this.props.xxx来获取数据了而不是之前的this.state.xxx
2. matchDispatchToProps: 将store.dispatch这个方法映射到props上,在这个回调函数中进行action的分发
导出的是connect方法的执行结果,connect方法可以将UI组件和数据和业务逻辑相结合的时候返回的内容就是一个容器组件了。
提供器的使用index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; import { Provider } from 'react-redux'; import store from './store'; const App = ( <Provider store={store}> <TodoList /> </Provider> ); ReactDOM.render(App, document.getElementById('root')); |
- 将组件包裹在Provider提供器中,组件就具备了获取store里面内容的能力
核心部分TodoList.js
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 |
import React from 'react'; import { connect } from 'react-redux'; // UI组件,只负责页面渲染,不负责业务逻辑 // 由于只有render方法,因此可以写成无状态组件 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(index)}} key={index}>{item}</li> }) } </ul> </div> ) }; const mapStateToProps = (state) => { return { inputValue: state.inputValue, list: state.list } }; // dispatch指的是store.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_item' }; dispatch(action); }, handleDelete(index){ console.log(index); const action = { type: 'del_item', index:index }; dispatch(action); } } }; export default connect(mapStateToProps,mapDispatchToProps)(TodoList); |
- 这是一个UI组件,导出的时候使用了connect方法将这个UI组件和数据和业务逻辑相结合的时候返回的内容就是一个容器组件了;
- 注意,当一个组件只有render方法的时候就可以写成UI组件,这样会有更好的性能(避免了组件的生命周期函数);
- 事件的回调函数写在mapDispatchToProps中,这个方法会将store.dispatch这个方法映射到props上。
- mapStateToProps这个方法可以将store中的数据作为props绑定到组件中,就可以使用this.props.xxx来访问store中的数据了;
- 这里使用了
1 |
const { inputValue, list, changeInputValue, handleClick, handleDelete } = props; |
这样就可以就可以直接通过方法名来访问this.props.inputValue了。
数据操作的逻辑仍然在reducer.js中进行
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 |
const 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; 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 === 'del_item'){ const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index,1); return newState; } return state; } |
- 进一步的提升可维护性可以使用之前的actionType和actionCreator来讲action的类型以及创建action的过程放在这两个文件中。