专题-React-Redux 深入

概念

关于派发器的由来:

vue中强规范的template,script,style的固定写法,存在弱点,在method里的业务逻辑实现时写了大量的紧耦合的方法,利用派发器的机制横向切割的方式实现抽离script里的methods代码编写区域出去(松耦合)

派发器如何实现抽离?

派发器根据一个又一个的事情来指定一个typetype的作用是遇到type对应的事情就触发一个方法的执行,每件事所对应的方法集合是根据每件事情的type去触发的

派发器的作用:

派发器(dispatcher)根据action里面的type去调用相应的method方法

问题:redux和派发器有什么关系?

基本原理一致,但有一点不同,不同页面的组件想拿到数据状态是不同的,redux可以解决

问题:数据状态存放在localStorage有什么弊端?

localStorage是不联动的,状态变更也不会导致所有页面马上发生变更

问题:为什么大部分场景都没有必要用redux?

目前开发的所有单页面应用组件是属于重新加载组件的过程(componentDidMount),当组件关闭时,它不是alive的,并且有许多方法可以避免使用redux

注意:

尽可能避免使用reduxvuex,包括单页面应用组件,它会增加程序的复杂性,和维护,适用中大型应用项目

问题:有什么情况必须要用到redux?

  1. 页面组件是alive的,它不能触发组件加载(componentDidMount),就需要中央管理状态机制
  2. react native开发中某些页面(alive)

使用

redux是什么?

它类似vue中的vuex,是用来实现状态管理工具

它并不集成于react中,而是需要单独引入redux依赖包

  1. npm i -D redux@4.0.5

如何创建一个store仓库?

利用redux里面的一个函数createStore来创建一个存储数据用的store,该函数接受一个函数reducer作为参数传入

  1. const store = createStore();

如何获取state数据呢?

它是通过仓库store底下的getState()方法去获取

  1. store.getState();

action是什么?

它是一个对象,用来描述当前如何操作state状态的,对象里面必须带有type属性来描述当前动作(行为),还可以定义具体的值

  1. const action = { type: 'ADD_ONE', num: 1 };

dispatch是什么?

是仓库store底下的方法,它是唯一可以更改当前state的唯一方法,接受一个action对象作为参数传入,它会返回一个action对象

  1. store.dispatch(action);
  2. console.log(store.dispatch(ation));
  3. //{ type: 'ADD', num: 1 }

什么是reducer函数?

它是一个函数,用来访问一个新的state数据,即用reducer来更新当前的state,它接受state作为初始值(默认为 10)为第一个参数,接受action对象作为第二参数,

它通过action对象里type进行判断更新数据

  1. const reducer = (state = 10, action) => {
  2. switch(action.type){
  3. case 'ADD_ONE':
  4. //初始值10 + action对象里定义的1
  5. return state + action.num;
  6. default:
  7. return state;
  8. }
  9. };

提取

提取action和提取reducer

定义变量

  1. //目录constans/ActionTypes 定义action对象type属性需要的变量
  2. const ADD = 'ADD';

将原本action里面定义的散乱的action对象改写为一个生成action对象的函数

  1. const addAction = (num) => {
  2. return {
  3. type: ADD,
  4. num: num
  5. }
  6. }
  7. const squareAction = () => {
  8. return {
  9. type: SQUARE
  10. }
  11. }

发送更改数据

  1. store.dispatch(addAction(1));

提取reducer

  1. const reducer = (state = 10, action)=>{
  2. switch(action.type){
  3. case 'ADD':
  4. return state + action.num;
  5. case 'SQUARE':
  6. return state * state;
  7. default:
  8. return state;
  9. }
  10. }
  11. export default reducer;

视图渲染

state数据同步到视图中

  1. //绑定视图
  2. <h1>一个基于Redux数据的计算器</h1>
  3. <p>计算结果:{store.getState()}</p>
  4. <button onClick={()=>{store.dispatch(addAction(1))}}>加1</button>
  5. <button onClick={()=>{store.dispatch(addAction(2))}}>加2</button>
  6. <button onClick={()=>{store.dispatch(squareAction())}}>乘方</button>

绑定视图后,点击按钮却没有更新视图上显示的数据,那是为什么呢?

经过检查发现,其实state数据是已经修改成功了,但是没有渲染更新视图,原因是还没有触发更新视图

如何触发更新视图?

通过redux提供的一个监听函数store.subscribe(),一旦监听到state发生更改时就会执行该函数,且通过渲染函数ReactDOM.render封装成一个新的渲染方法renders并作为参数传入监听函数,使其数据更新时渲染

  1. function render(){
  2. ReactDOM.render(
  3. <App/>,
  4. document.getElementById('app')
  5. );
  6. }
  7. //首场渲染
  8. render();
  9. //更新渲染
  10. store.subscribe(render);

此时,绑定的state数据显示并可以同步更新视图

react-redux

它将所有的组件分为展示组件和容器组件

  • 展示组件:负责 UI 显示,所有的数据由props提供
  • 容器组件:容器包裹,负责管理数据和业务逻辑相关的东西

如何用react-redux的方式实现页面响应?

  1. //安装react-redux
  2. npm i -S react-redux
  3. //引入Provider
  4. //Provider作为容器将组将包裹起来
  5. import { Provider } from 'react-redux';
  6. import store from "./store";
  7. //包裹容器组件传入state使其逐级往下传递
  8. <Provider store={store}>
  9. <Container />
  10. </Provider>

默认情况下,子组件通过props属性接收传递的state是拿不到的,想要拿到得通过react-redux里面的connect方法

它在子组件中定义connect方法

  1. //1.引入connect
  2. import {connect} from 'react-redux';
  3. //2.子组件中使用
  4. //展示组件Container的内容通过props进行传递
  5. //mapStateToProps将state映射到props
  6. //mapDispatchToProps将dispatch映射到props
  7. connect(mapStateToProps, mapDispatchToProps)(Container);
  8. //3.定义mapStateToProps方法
  9. //接收state状态(从Provider传入那拿到)
  10. const mapStateToProps = (state) =>{
  11. //通过对象的方式映射出去
  12. return {
  13. num: state
  14. }
  15. }
  16. //4.定义mapDispatchToProps方法
  17. const mapDispatchToProps = (dispatch) => {
  18. return {
  19. //定义add方法
  20. add: (value) => dispatch(addAction(value)),
  21. square:() => dispatch(squareAction())
  22. }
  23. }
  24. //5.从容器组件中的props获取映射定义的state数据和dispatch方法
  25. const {num,add,square} = props;
  26. //6.绑定视图
  27. <h1>一个基于React-Redux数据的计算器</h1>
  28. <p>计算结果:{num}</p>
  29. <button onClick={()=>add(1)}>加1</button>
  30. <button onClick={()=>add(2)}>加2</button>
  31. <button onClick={()=>square()}>乘方</button>
  32. //导出connect连接后的组件
  33. export default connect(mapStateToProps, mapDispatchToProps)(Container);

异步处理

目前redux只能处理同步,如果有异步 API 请求任务时,redux是无能为力的

  1. //1.安装redux-thunk
  2. npm i -D redux-thunk
  3. //2.引入
  4. import thunk from 'redux-thunk';
  5. //3.引入react里面的中间件方法
  6. import { createStore, applyMiddleware } from 'redux';
  7. //所有action都需要通过中间件的方式去处理
  8. //4.创建store仓库的同时也创建中间件,
  9. // 它以createStore方法的第二个参数传入,并接受thunk作为参数
  10. const store = createStore(reducer, applyMiddleware(thunk));
  11. //5.actions文件里增加异步的方式去操作视图
  12. const getAction = () => {
  13. //这里异步不能返回一个对象
  14. //所以返回一个函数
  15. return (dispatch, getState) => {
  16. //请求数据
  17. fetch('./mock/data.json')
  18. .then(res => res.json())
  19. .then(res => {
  20. //使用dispatch设置state数据
  21. dispatch({
  22. type: 'GET',
  23. num: Number(res[4])
  24. });
  25. });
  26. //console.log(res)
  27. //['React', 'Redux', 'Basic', 'Learning', '0']
  28. }
  29. }
  30. //6.mapDispatchToProps方法里面映射get方法
  31. const mapDispatchToProps = (dispatch) => {
  32. return {
  33. get: () => dispatch(getAction())
  34. }
  35. }
  36. //7.绑定视图
  37. <button onClick={()=>get()}>请求数据后修改计算结果</button>

案例

计算器

一个基于redux管理状态数据的计数器案例

实现:

  • 加 1

  • 加 2

  • 乘方

  • 异步数据请求

案例展示图:

React-Redux深入 - 图1

写法:

  1. redux写法
  2. react-redux容器组件和展示组件写法

目录:

  1. ├─Readme.md
  2. ├─src
  3. | ├─App.jsx - App组件/Provider嵌套Container容器组件/传入store数据
  4. | ├─index.jsx - 入口文件/封装render函数/挂载组件
  5. | ├─store - 仓库文件/新建仓库/注入中间键(thunk)
  6. | | index.js
  7. | ├─reducers
  8. | | reducer.js - 定义reducer函数里的逻辑方法
  9. | ├─constans
  10. | | ActionTypes.js - 定义变量名称
  11. | ├─components
  12. | | Container.jsx - 容器组件/使用state数据/绑定视图/定义映射state/dispatch/connect组件连接
  13. | ├─actions
  14. | | actions.js - 定义action对象描述行为和属性/定义异步加载行为
  15. ├─mock
  16. | 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 -> 派发器 -> 数据的更改

  1. //项目结构
  2. ├─src
  3. | ├─App.vue
  4. | ├─main.js
  5. | ├─reducers
  6. | | todoList.js
  7. | ├─dispatchers
  8. | | todoList.js
  9. | ├─components
  10. | | ├─TodoList
  11. | | | ├─index.vue
  12. | | | ├─TdForm.vue
  13. | | | ├─TdTitle.vue
  14. | | | ├─TdList
  15. | | | | ├─index.vue
  16. | | | | ListItem.vue
  17. | ├─actions
  18. | | todoList.js
  19. ├─public
  20. | index.html

源码地址:

https://gitee.com/kevinleeeee/dispatcher-todolist-vue2.x-demo3

课堂分类

实现点击tab栏课堂分类显示不同的课堂列表

技术:

  • redux
  • axios
  • express后端接口

依赖:

  1. redux@4.0.5单独的库
  2. react-redux@7.2.0:专门为react设计高阶组件,提供了connect方法,将组件和redux结合在一起
  3. redux-thunk@2.3.0:异步action需要使用的

项目目录:

  1. ├─src
  2. | ├─App.jsx - 应用组件/使用Provide组件包裹并传入store
  3. | ├─index.js - 入口文件/挂载DOM
  4. | ├─utils
  5. | | http.js - 封装axios
  6. | ├─store
  7. | | ├─index.js - store入口文件/创建仓库/汇总reducers/将所有reducers,组合state,中间件传入仓库
  8. | | ├─states - 导出state对象
  9. | | | courseTabList.js
  10. | | ├─reducers - 专门来操作state数据的函数
  11. | | | courseTabList.js
  12. | | ├─actions - 保持改变state数据的方法和类型
  13. | | | courseTabList.js
  14. | ├─pages
  15. | | Index.jsx - 首页页面组件/connect绑定中央state和方法到props属性/将state数据传入子组件使用
  16. | ├─models
  17. | | index.js - 封装请求接口
  18. | ├─configs
  19. | | config.js - 配置请求路径
  20. | ├─components
  21. | | ├─CourseList
  22. | | | ├─CourseItem.jsx
  23. | | | index.jsx
  24. | | ├─CourseField
  25. | | | ├─FieldItem.jsx
  26. | | | ├─index.css
  27. | | | index.jsx
  28. ├─serve
  29. | ├─index.js
  30. | ├─package-lock.json
  31. | ├─package.json
  32. | ├─data
  33. | | ├─courseField.json
  34. | | courseList.json
  35. ├─public
  36. | ├─favicon.ico
  37. | index.html

redux注入使用步骤

  1. //1.引入Provider
  2. import { Provider } from 'react-redux';
  3. //2.包裹组件
  4. //该组件提供的属性,底下的子组件都能接收
  5. //store属性接收的store是自定义的store文件
  6. <Provider store={store}>
  7. <App></App>
  8. </Provider>
  9. //3.定义actions/reducers/states文件
  10. //4.组合所有reducers
  11. //5.引入connect 将redux跟组件相关联
  12. import { connect } from 'react-redux';
  13. //6.引入actions里定义的方法,传入到connect
  14. import { changeCourseField } from '../store/actions/courseTabList';
  15. //7.使用connect
  16. export default connect(
  17. //提供中央state
  18. function mapStateToProps(state) {
  19. return {
  20. curField: state.courseTabList.curField
  21. }
  22. },
  23. //提供中央state方法
  24. function mapDispatchToProps(dispatch) {
  25. return {
  26. changeCourseField: (field) => dispatch(changeCourseField(field))
  27. }
  28. }
  29. )(IndexPage);

问题:为什么存在connect方法?

它的调用传入组件的写法实现将中央的state作为属性传入组件中,实现个个组件的state保持隔离

关于store文件目录:

  1. //store目录
  2. index.js - store入口文件/创建仓库/汇总reducers/将所有reducers,组合state,中间件传入仓库
  3. states - 导出state对象
  4. reducers - 专门来操作state数据的函数
  5. actions - 保持改变state数据的方法和类型

总结:

学会redux数据状态管理是在项目中结合使用,把中央状态和状态的方法提供好,不能改变组件内部的state,只能把redux的状态的状态方法作为属性传递给组件

源码地址:

https://gitee.com/kevinleeeee/react-redux-courses-tablist-demo