react-redux库的使用

目录

里边有用到的代码:

1react-redux 的思想——将所有组件分为两大类

2 容器组件的实现

2.1 redux 库下项目的数据流向

2.2 react 下使用 redux 插件

2.3 组织程序

2.4 action creator

2.5 mapDispatchToProps

2.6 组织 reducers 函数

2.7 store和Provider

2.8 mapStateToProps

3 react 中异步流的解决方案

3.1 使用redux-thunk

3.2 使用redux-promise-middleware


1 react-redux 的思想——将所有组件分为两大类

(1)UI 组件

  • 对应用的其他部分没有依赖关系
  • 只负UI 的呈现,不带有任何的逻辑
  • 通过 props 接受数据
  • 不使用任何的 Redux 的API
  • 一般保存在 components 文件夹下

具体见 https://www.cnblogs.com/pengshuo/p/6645573.html

(2)容器组件

  • 负责管理数据和业务逻辑,不负责 UI 呈现
  • 使用 Redux 的API
  • 一般保存在 containers 下
  • 容器组件常常不是自己手动写成的,而是通过 UI 组件 connect(mapStateToProps, mapDispatchToProps)(UiComponent) 而成为高级组件

高阶组件:高阶组件(Hoc, Higher-order components),也称为enhancer。接受一个已有组件组件作为参数,并返回一个新的组件,后者将前者封装于内部。一般使用高阶组件是为了对已有组件进行某些能力上的增强。

来自 《React 全栈》---张轩,杨寒星

因此,react项目结构 设计的一般思路是:每个路由 Route 对应的组件一般是容器组件,容器组件一般是 UI 组件的包装。容器组件负责接触 Store, 而 state 树应该是在引入 Provider 目录下定义。

问题1:如果一个组件即有业务逻辑,又有UI,怎么办?

回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它

-----转自 阮一峰  Redux 入门教程(三):React-Redux 的用法

我的理解,这里当一个组价既有业务逻辑,又有UI,应该就是指的是 上文所说的 容器组件,容器组件本就是 UI 组件的升级,也不就是一个组件既有逻辑(与store的直接访问),又有UI呈现。

2 容器组件的实现

2.1 redux 库下项目的数据流向

基本 react 项目(仅适用redux 组件)下,数据的流向如下,

但是仅仅使用结构组织项目的代码,会发现UI界面的代码与交互联系的代码中有 组织action 的代码,而要获取 store,就要一级级从祖先的 store 中通过属性传递得到,这样做在项目复杂时,必然会很繁琐而且难以维护。

而react-redux 解决的就是将业务逻辑这部分代码从 react项目UI部分的内容 中剥离出来,从而实现UI界面代码的解耦。具体做法,是将 state 保存在一个 store 的Object 前提下,新增 容器组件 ,它负责收集并发送 actions给 reducers ,再将reducer处理后的结果(state)返回给UI组件,UI组件只需要借助属性(props)即可去 dispatch(action) 并获得更新后的 state。总之,react-redux 引入了容器组件这个中介,去帮助UI组件实现代码解耦,示意图

2.2 react 下使用 redux 插件

首先需要安装的两个插件 redux 、react-redux ,但是理解和react他们三者的区别还是很重要的,这样便于我们以后理解记忆各个库的接口函数。

React:负责组件的UI界面渲染;
Redux:数据处理中心;
React-Redux:连接组件和数据中心,也就是把React和Redux联系起来。

因此,redux 中有 createStore 的API,而 react-redux 中有 Provider 和 connect 的常用API。所以 需要npm 安装这两个库。

npm install --save redux

npm install --save redux

这里需要提的一点:

action 是负责将数据从应用传送到 store 的有效载荷,action 本质是一个有 两个属性 的对象:

{
    type: 'go to school',
    data: {
        needs: ['书包', '课本'],
        howToGo: 'Bus',
        ...
    }
}

实际上,事件的处理大抵也是这种思路,这两天学习了的 AngularJS 中 $on/$emit/$broadcast 指令,它们的用法也是类似。也就是说,你要将行为逻辑从项目中剥离出来,就要弄清楚行为响应的原理,而这里 redux 就是采用这种方式实现的。

注意:这里 dispatch 的action 都是对象格式(有 type 和 content 两个属性),但是在也可以是一个函数,这里先埋一个伏笔,具体见下文(异步流解决方案内容)。

2.3 组织程序

我们可以按照数据流向去组织程序,也可以按照先从 createStore 入手,我这里以前者组织。

  1. 原型图,设计 UI 界面
  2. 划分页面的内容,整理各个部分可能出现的 actions ,实现 action creator,
  3. 设计 store state,实现 reducer,
  4. 通过容器组件,连接 store 和 展示组件,

2.4 action creator

这是我 todo-list 练习中的 action creator 部分的代码:

import * as actionsType from "./actionsType";

export function handleChange (event) {
	return {
		type: actionsType.CHANGE,
		text: event.target.value
	};	
}

export function add (event) {
	return {
		type: actionsType.ADD_ONE,
	};	
}

export function modify (id) {
	return {
		type: actionsType.MODIFY_ONE,
		text: id
	};
}

export function onToggle (id) {
	return {
		type: actionsType.DONE_ONE,
		text: id
	};
}

export function toggleAll () {
	return {
		type: actionsType.DONE_ALL
	};
}

export function clearCompleted () {
	return {
		type: actionsType.CLEAR_COMPLETED
	};
}

与上文提到的 action 对象的类型是一致,也就是说你要通过一定操作,将从 UI 组件中得来的数据(也可以没有数据如 clearCompleted 的action creator)封装为一个对象。

2.5 mapDispatchToProps

既然要从数据源说起,那么首先应该是容器组件的 mapDispatchToProps 的接口,它建立的是 从 UI组件store.dispatch 的映射。其中 mapDispatchToProps 可以是对象也可以是函数。

  • 当 mapDispathToProps 作为对象时,对象键名会作为对应UI组件的 props 下的方法,键值

An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.——官方文档

每个 action creator 会被包装成 dispatch(actionCreator()) 调用的形式,从而能在组件的 props 中直接触发 dispatch.

原理:

如果你传递了一个由 action creators 构成的对象,而不是函数,connect会在内部自动为你调用bindActionCreators。

bindActionCreators接收两个参数:

  1. 一个函数(action creator)或一个对象(每个属性为一个action creator)
  2. dispatch

转自:Marckon  发布于 前端小生的成长记录 关注专栏

React-Redux 中文文档

因此,可以直接引用 actioncreator 中产生的 action 并借助 ES6 对象解构赋值,可以像下边这样写。具体可以

import { add, modify, handleChange } from './actions';

const mapDispatchToProps = {
    add,
    modify,
    handleChange,
};


export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);

// 等价于
const mapDipatchToProps = {
    add: (...args) => dispatch(add(...args)),
    modify: (...args) => dispatch(modify(...args));
    handleChange: (...args) => dispatch(handleChange(...args));
};

但是如果直接将键值设置为对象(而不是函数),就不能向为 action 添加数据了。

注意:当你为 mapDispatchToProps 什么都不指定时(对象为null),你的 UI 组件会默认获得 dispatch 这个方法,并添加在props上。

  • 当 mapDispatchToProps 作为函数时,接收 dispatch ownProps(可选) 参数。函数需要返回一个对象,对象结构与上边对象类似。
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
    	handleChange: (event) => {
    		dispatch(Actions.handleChange(event));
    	},
        add: (event) => {
            if (event.keyCode !== Constants.ENTER_KEY) {
            	return;
            }
            event.preventDefault();
            dispatch(Actions.add(event));
        },
        modify: (id) => {
        	dispatch(Actions.modify(id));
        },
        onToggle: (id) => {
        	dispatch(Actions.onToggle(id));
        },
        toggleAll: () => {
        	dispatch(Actions.toggleAll());
        },
        clearCompleted: () => {
        	dispatch(Actions.clearCompleted());
        }
    };
};

这样,通过传入 dispatch 的参数,可以在自己决定在什么时候dispatch 什么样的 action ,比对象形式的 mapDispatchToProps 更加灵活。

但是仔细观察2.4、2.5中内容会发现,有些 action creator 函数其实都是只需要一步就可以完成的,所以,

我们建议适中使用这种“对象简写”形式的mapDispatchToProps,除非你有特殊理由需要自定义dispatching行为。

转自:Marckon  发布于 前端小生的成长记录 关注专栏

React-Redux 中文文档

2.6 组织 reducers 函数

reducer 函数的过程,我的理解就是,

reducer 函数接受两个参数,当前 reducer 函数执行前的 state (我习惯称他 old_state) 和 action 对象。

import {
    Action
} from 'path';

const defaultState = {
    prop1,
    prop2
};

const Reducer = (state = defaultState, action = {}) => {
    switch (action.type) {
        case type:
            return {
                ...state,
                prop2: action.content
            };

        default:
            return state;
    }
};

export default Reducer;

注意:

  • 必须要给出 state 默认值,

以为初次页面渲染提供数据源,上边代码使用了 ES6 中的解构赋值,当等号右边是 undefined 时,取默认值。

  • reducer 必须是纯函数

关于 reducer 函数,最重要的一点是要保证是纯函数,传入的参数 state 与 action 都不能改变,所以上边代码用到了 浅拷贝 的形式返回 新的state。

return {
           ...state,
            prop2: action.content
        };
//除此之外,还可以借助 assign 来实现浅拷贝

return Object.assign({}, state, {prop2: action.content});
  • 关于 reducer 的拆分

这我会另外总结关于目前遇到了提升 react 性能的方法汇总。

2.7 store和Provider

有了 reducer 当然就可以创造 store 对象了,有了 store 对象,我们就可以使用 Provider 组件来为容器组件提供 store 对象了。

创造 store 对象,前边讲到 redux 库管理数据,那么store的构建函数——createStore就是有redux库来提供。得到如下的代码。

import { createStore } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';

import reducer from "../reducers/reducer";

let store = createStore(reducer, composeWithDevTools());
//composeWithDevTools() 方便浏览器使用redux 插件所引入的库,与上边 'redux-devtools-extension'对应

export default store;

使用 Provider 

import React from 'react';
import './App.css';
import TodoApp_Can from "../containers/TodoApp_Can.jsx";
import Test from "./test"
import store from "../containers/store";

import { Provider } from "react-redux";


class  App extends React.Component {

  render() {
    return (
      <div className="App">
        <Provider store={store}>
          <TodoApp_Can className="todoapp" ></TodoApp_Can>
          <Test></Test>
        </Provider>
        <footer className="info">
          <p>Double-click to edit a todo</p>
          <p>Created by <a href="https://github.com/zhoushaokun/">ZSK</a></p>
          <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
        </footer>
      </div>
    );
  }
}

export default App;

2.8 mapStateToProps

这是最后一步,也是最关键一步,只有通过这里才能将新的state 渲染到UI上,用户才能感受到与机器的交互。

mapStateToProps 与 mapDispatchToProps 类似,都可以是一个函数,都可以接受 ownProps 这个可选参数,但是它建立的是从(外部的)state对象到UI组件的props的映射,而且用法更简单。

你可以简单的将mapStateToProps写成

const mapStateToProps = ( state ) => ({ state });

当 mapStateToProps 的函数中有 state 参数时,表示UI组件会订阅store,每当store更新,就会触发UI组件的重新渲染。当然可以接受第二个可选参数 ownProps, 表示容器组件自身的props对象,使用该参数后,若容器组件的参数发生改变,也会引起UI组件的重新渲染。

ownProps 指的是传递给容器组件,需要被透传给展示组件的属性。

当然这样写,当项目的结构越来越来复杂时,页面中一个小部分的组价更新会引起所有组件的更新,这样势必会影响用户体验。具体的性能提升方法是:计算记忆结果,使用 select 库。

他的实现原理是修改数据尽可能复用原来的数据,在整体状态的局部发生变化时,那些依赖未变更部分数据的组件所接触的数据保持不变,这在一定程度上会减少重复渲染。

关于记忆计算结构,原理还好理解,但具体怎么用我还没研究透彻。

3 react 中异步流的解决方案

3.1 使用redux-thunk

redux-thunk 允许 dispatch 的action是一个函数,而不是一个普通的对象。如果接受的action是一个函数,那么就会直接调用这个函数,并将 dispatch 与 getState(可选) 作为参数传入,这样可以在 action creator 返回的函数中,获取dispatch、getState或者根据条件选择性地dispatch、多次dispatch乃至实现异步dispatch。

使用 middleware 中间件

//store.js下
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
 const store = createStore(
  reducers, 
  applyMiddleware(thunk)
);

如下,fetchFriends() 是一个返回 匿名函数 的函数,这个匿名函数  接受两个参数(dispatch、getState)。因此,dispatch(fetchFriends ) 会直接导致 fetchFriends 函数执行,剩余的事情就是等待获取到 url 下的资源并 dispatch 给 reducer,reducer 按 type 判断并作用于 store,最终渲染新的页面。

 //actionCreators.js下文件     
 //fetchFriends是一个Action Creator(动作生成器),返回一个函数。
      export function fetchFriends() {
            return (dispatch, getState) => {
                return fetchHttpRequest("*****url****")
                        .then(data => {
                            dispatch({type:"error"})
                        })
                        .then(dataJson => {
                            dispatch({type:"success", dataJson});
                        });
            }
        
      } 

   
  // redux的connect作用文件下
import * as actionCreators from "./store/actionCreators";

mapDispatchToProps = (dispatch) => {
     return {
         getFriends: () => {
             dispatch(actionCreators.fetchFriends());              
         }
     };
}


并且对于异步的行为,为了安全起见,应该定义 3 个actionType (发起异步,异步成功,异步失败),并分别dispatch 三种 action,另外一种做法是,仅定义一个类型的action,只不过通过对象 data 中的 error 字段标识本次行为是否成功。

项目中使用的是 promise 对象,这样就可以很方便的指定在哪里去 dispatch 错误类型的action,在哪里去 dispatch 成功的 action 。

在 UI 组件中,logout 函数返回一个 Promise 对象,然后就可以指定该 Promise 对象中的 resolve 函数的具体内容(即异步成功要执行的下一步),data_resolve 通过上边 resolve 处传入的实参。

       this.props.logout({
           data
       })  
       .then((data_resolve) => {
             //....
        })
        .catch(data_error => {
             //.... 
         });

3.2 使用redux-promise-middleware

如下,

const mapDispatchToProps = (dispatch) => {
    return {
        login: (userInfo) => {
            return {
                    type: "async",
                    payload: new Promise(resolve, reject => {
                        //在这里执行异步操作,并指定何时 resolve ,何时 reject
                    });
            })
        },
    }
}

从 redux-promise 的源码可以看出,传过来的action,要检查 action 的payload,如果是promise对象,就再次 dispatch 一个普通的(与同步相同的)action,这样就可以解决 异步流了。

import isPromise from 'is-promise';
import { isFSA } from 'flux-standard-action';

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }

    return isPromise(action.payload)
      ? action.payload
          .then(result => dispatch({ ...action, payload: result }))
          .catch(error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          })
      : next(action);
  };
}

注意:

  • resolve、reject 都是指 接下来应该要做的工作,我们可以给它们传入异步操作后的数据结果,供以后继续操作
  • 其实可以看出,异步action 可以通过同步的action去实现。事实上,很多同步的action也可以通过其他的action帮助实现。因此, 容器的 dispatch 并非和 action type 一一对应的,可能有些交互行为需要组合好几个 dispatch(action) 去实现。

 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值