专题-React-Redux 深入
概念
关于派发器的由来:
在vue中强规范的template,script,style的固定写法,存在弱点,在method里的业务逻辑实现时写了大量的紧耦合的方法,利用派发器的机制横向切割的方式实现抽离script里的methods代码编写区域出去(松耦合)
派发器如何实现抽离?
派发器根据一个又一个的事情来指定一个type,type的作用是遇到type对应的事情就触发一个方法的执行,每件事所对应的方法集合是根据每件事情的type去触发的
派发器的作用:
派发器(dispatcher)根据action里面的type去调用相应的method方法
问题:redux和派发器有什么关系?
基本原理一致,但有一点不同,不同页面的组件想拿到数据状态是不同的,redux可以解决
问题:数据状态存放在localStorage有什么弊端?
localStorage是不联动的,状态变更也不会导致所有页面马上发生变更
问题:为什么大部分场景都没有必要用redux?
目前开发的所有单页面应用组件是属于重新加载组件的过程(componentDidMount),当组件关闭时,它不是alive的,并且有许多方法可以避免使用redux
注意:
尽可能避免使用
redux和vuex,包括单页面应用组件,它会增加程序的复杂性,和维护,适用中大型应用项目
问题:有什么情况必须要用到redux?
- 页面组件是
alive的,它不能触发组件加载(componentDidMount),就需要中央管理状态机制 - 在
react native开发中某些页面(alive)
使用
redux是什么?
它类似vue中的vuex,是用来实现状态管理工具
它并不集成于react中,而是需要单独引入redux依赖包
npm i -D redux@4.0.5
如何创建一个store仓库?
利用redux里面的一个函数createStore来创建一个存储数据用的store,该函数接受一个函数reducer作为参数传入
const store = createStore();
如何获取state数据呢?
它是通过仓库store底下的getState()方法去获取
store.getState();
action是什么?
它是一个对象,用来描述当前如何操作state状态的,对象里面必须带有type属性来描述当前动作(行为),还可以定义具体的值
const action = { type: 'ADD_ONE', num: 1 };
dispatch是什么?
是仓库store底下的方法,它是唯一可以更改当前state的唯一方法,接受一个action对象作为参数传入,它会返回一个action对象
store.dispatch(action);console.log(store.dispatch(ation));//{ type: 'ADD', num: 1 }
什么是reducer函数?
它是一个函数,用来访问一个新的state数据,即用reducer来更新当前的state,它接受state作为初始值(默认为 10)为第一个参数,接受action对象作为第二参数,
它通过action对象里type进行判断更新数据
const reducer = (state = 10, action) => {switch(action.type){case 'ADD_ONE'://初始值10 + action对象里定义的1return state + action.num;default:return state;}};
提取
提取action和提取reducer
定义变量
//目录constans/ActionTypes 定义action对象type属性需要的变量const ADD = 'ADD';
将原本action里面定义的散乱的action对象改写为一个生成action对象的函数
const addAction = (num) => {return {type: ADD,num: num}}const squareAction = () => {return {type: SQUARE}}
发送更改数据
store.dispatch(addAction(1));
提取reducer
const reducer = (state = 10, action)=>{switch(action.type){case 'ADD':return state + action.num;case 'SQUARE':return state * state;default:return state;}}export default reducer;
视图渲染
将state数据同步到视图中
//绑定视图<h1>一个基于Redux数据的计算器</h1><p>计算结果:{store.getState()}</p><button onClick={()=>{store.dispatch(addAction(1))}}>加1</button><button onClick={()=>{store.dispatch(addAction(2))}}>加2</button><button onClick={()=>{store.dispatch(squareAction())}}>乘方</button>
绑定视图后,点击按钮却没有更新视图上显示的数据,那是为什么呢?
经过检查发现,其实state数据是已经修改成功了,但是没有渲染更新视图,原因是还没有触发更新视图
如何触发更新视图?
通过redux提供的一个监听函数store.subscribe(),一旦监听到state发生更改时就会执行该函数,且通过渲染函数ReactDOM.render封装成一个新的渲染方法renders并作为参数传入监听函数,使其数据更新时渲染
function render(){ReactDOM.render(<App/>,document.getElementById('app'));}//首场渲染render();//更新渲染store.subscribe(render);
此时,绑定的state数据显示并可以同步更新视图
react-redux
它将所有的组件分为展示组件和容器组件
- 展示组件:负责 UI 显示,所有的数据由
props提供 - 容器组件:容器包裹,负责管理数据和业务逻辑相关的东西
如何用react-redux的方式实现页面响应?
//安装react-reduxnpm i -S react-redux//引入Provider//Provider作为容器将组将包裹起来import { Provider } from 'react-redux';import store from "./store";//包裹容器组件传入state使其逐级往下传递<Provider store={store}><Container /></Provider>
默认情况下,子组件通过props属性接收传递的state是拿不到的,想要拿到得通过react-redux里面的connect方法
它在子组件中定义connect方法
//1.引入connectimport {connect} from 'react-redux';//2.子组件中使用//展示组件Container的内容通过props进行传递//mapStateToProps将state映射到props//mapDispatchToProps将dispatch映射到propsconnect(mapStateToProps, mapDispatchToProps)(Container);//3.定义mapStateToProps方法//接收state状态(从Provider传入那拿到)const mapStateToProps = (state) =>{//通过对象的方式映射出去return {num: state}}//4.定义mapDispatchToProps方法const mapDispatchToProps = (dispatch) => {return {//定义add方法add: (value) => dispatch(addAction(value)),square:() => dispatch(squareAction())}}//5.从容器组件中的props获取映射定义的state数据和dispatch方法const {num,add,square} = props;//6.绑定视图<h1>一个基于React-Redux数据的计算器</h1><p>计算结果:{num}</p><button onClick={()=>add(1)}>加1</button><button onClick={()=>add(2)}>加2</button><button onClick={()=>square()}>乘方</button>//导出connect连接后的组件export default connect(mapStateToProps, mapDispatchToProps)(Container);
异步处理
目前redux只能处理同步,如果有异步 API 请求任务时,redux是无能为力的
//1.安装redux-thunknpm i -D redux-thunk//2.引入import thunk from 'redux-thunk';//3.引入react里面的中间件方法import { createStore, applyMiddleware } from 'redux';//所有action都需要通过中间件的方式去处理//4.创建store仓库的同时也创建中间件,// 它以createStore方法的第二个参数传入,并接受thunk作为参数const store = createStore(reducer, applyMiddleware(thunk));//5.actions文件里增加异步的方式去操作视图const getAction = () => {//这里异步不能返回一个对象//所以返回一个函数return (dispatch, getState) => {//请求数据fetch('./mock/data.json').then(res => res.json()).then(res => {//使用dispatch设置state数据dispatch({type: 'GET',num: Number(res[4])});});//console.log(res)//['React', 'Redux', 'Basic', 'Learning', '0']}}//6.mapDispatchToProps方法里面映射get方法const mapDispatchToProps = (dispatch) => {return {get: () => dispatch(getAction())}}//7.绑定视图<button onClick={()=>get()}>请求数据后修改计算结果</button>
案例
计算器
一个基于redux管理状态数据的计数器案例
实现:
加 1
加 2
乘方
异步数据请求
案例展示图:

写法:
- 纯
redux写法 react-redux容器组件和展示组件写法
目录:
├─Readme.md├─src| ├─App.jsx - App组件/Provider嵌套Container容器组件/传入store数据| ├─index.jsx - 入口文件/封装render函数/挂载组件| ├─store - 仓库文件/新建仓库/注入中间键(thunk)| | └index.js| ├─reducers| | └reducer.js - 定义reducer函数里的逻辑方法| ├─constans| | └ActionTypes.js - 定义变量名称| ├─components| | └Container.jsx - 容器组件/使用state数据/绑定视图/定义映射state/dispatch/connect组件连接| ├─actions| | └actions.js - 定义action对象描述行为和属性/定义异步加载行为├─mock| └data.json - 模拟后端数据
总结:
redux基本操作- 整个目录的拆分方式
- 对应的
react-redex会存在一个容器模板,展示模板,connect方法新建容器组件方式连接
源码地址:
https://gitee.com/kevinleeeee/react-redux-0to1-demo
todolist
派发器模式来改造组件的逻辑部分
如何的去根据需求去更改数据
methods操作数据data,更多的不想逻辑写在methods里面
因为一个组件它的逻辑部分非常多的话,methods里面就不好维护了
优点:给更多同步开发的开发者实现方法管理
问题:如何根据一个模式去设计不让method里面的逻辑变得臃肿?
抽离相应的部分,通过type找到一个对应事件,找到事件对应的逻辑,逻辑通过事件类型type去触发派发器,触发数据的改变,这样就横向的把methods拆分出来了
原理:
type -> 事件 -> 逻辑 -> type -> 派发器 -> 数据的更改
//项目结构├─src| ├─App.vue| ├─main.js| ├─reducers| | └todoList.js| ├─dispatchers| | └todoList.js| ├─components| | ├─TodoList| | | ├─index.vue| | | ├─TdForm.vue| | | ├─TdTitle.vue| | | ├─TdList| | | | ├─index.vue| | | | └ListItem.vue| ├─actions| | └todoList.js├─public| └index.html
源码地址:
https://gitee.com/kevinleeeee/dispatcher-todolist-vue2.x-demo3
课堂分类
实现点击tab栏课堂分类显示不同的课堂列表
技术:
reduxaxiosexpress后端接口
依赖:
redux@4.0.5单独的库react-redux@7.2.0:专门为react设计高阶组件,提供了connect方法,将组件和redux结合在一起redux-thunk@2.3.0:异步action需要使用的
项目目录:
├─src| ├─App.jsx - 应用组件/使用Provide组件包裹并传入store| ├─index.js - 入口文件/挂载DOM| ├─utils| | └http.js - 封装axios| ├─store| | ├─index.js - store入口文件/创建仓库/汇总reducers/将所有reducers,组合state,中间件传入仓库| | ├─states - 导出state对象| | | └courseTabList.js| | ├─reducers - 专门来操作state数据的函数| | | └courseTabList.js| | ├─actions - 保持改变state数据的方法和类型| | | └courseTabList.js| ├─pages| | └Index.jsx - 首页页面组件/connect绑定中央state和方法到props属性/将state数据传入子组件使用| ├─models| | └index.js - 封装请求接口| ├─configs| | └config.js - 配置请求路径| ├─components| | ├─CourseList| | | ├─CourseItem.jsx| | | └index.jsx| | ├─CourseField| | | ├─FieldItem.jsx| | | ├─index.css| | | └index.jsx├─serve| ├─index.js| ├─package-lock.json| ├─package.json| ├─data| | ├─courseField.json| | └courseList.json├─public| ├─favicon.ico| └index.html
redux注入使用步骤
//1.引入Providerimport { Provider } from 'react-redux';//2.包裹组件//该组件提供的属性,底下的子组件都能接收//store属性接收的store是自定义的store文件<Provider store={store}><App></App></Provider>//3.定义actions/reducers/states文件//4.组合所有reducers//5.引入connect 将redux跟组件相关联import { connect } from 'react-redux';//6.引入actions里定义的方法,传入到connectimport { changeCourseField } from '../store/actions/courseTabList';//7.使用connectexport default connect(//提供中央statefunction mapStateToProps(state) {return {curField: state.courseTabList.curField}},//提供中央state方法function mapDispatchToProps(dispatch) {return {changeCourseField: (field) => dispatch(changeCourseField(field))}})(IndexPage);
问题:为什么存在connect方法?
它的调用传入组件的写法实现将中央的state作为属性传入组件中,实现个个组件的state保持隔离
关于store文件目录:
//store目录─index.js - store入口文件/创建仓库/汇总reducers/将所有reducers,组合state,中间件传入仓库─states - 导出state对象─reducers - 专门来操作state数据的函数─actions - 保持改变state数据的方法和类型
总结:
学会redux数据状态管理是在项目中结合使用,把中央状态和状态的方法提供好,不能改变组件内部的state,只能把redux的状态的状态方法作为属性传递给组件
源码地址:
https://gitee.com/kevinleeeee/react-redux-courses-tablist-demo
