两周已过,小编携文来扰!!
前段时间小编委托组内小哥又给自己梳理了一遍react结合redux使用的知识点(因为懒,翻文档不如白嫖来的开心呀),主要涉及使用的注意事项和使用流程,涉及的中间件以及如何处理异步数据等。完后,小编觉得有必要对这次的知识点做一个系统的整理,造(wu)福(ren)大(zi)众(di)。
文中小编对于一些涉及流程的模块针对性的画了流程图,同时穿插了代码,方便理解(毕竟全是文字会显得文章很干)。若触到知识盲区想细品,小编建议从上往下看;若觉得自身掌握的可以,可直接跳至文尾,有惊喜!话不多说,安排起来!
react
react概念
首先,我们需要知道的是:
React是一个声明式的,高效且灵活的用于构建用户界面的javascript库。React可以将一些简短,独立的代码片段(亦称‘组件’)组合成复杂的UI界面。
注意: react是一个库,而不是框架。
拓展: 库和框架有什么区别?
关于库: 库(Lab)是将代码集合成的一个产品,以供研发人员调用。库为我们提供了很多封装好的函数,我们在使用时只需要提取自己需要的函数即可,使用起来也非常灵活。若是没有,我们也可以手动封装函数实现。像jQuery、react、underscore就是库。
关于框架: 框架(Framework)则是为解决一个(一类)问题而开发的产品。一般情况下,框架用户只需要使用框架提供的类或函数,即可实现全部功能。像angular、backbone、vue等这些属于框架。
举个例子: 就比如你买了一辆小摩托,小摩托买回来就可以用了,这里小摩托就相当于一个框架。然后某天你骑着心爱的小摩托去溜达,发现有人跟你骑着一样的小摩托,你想让自己的小摩托变得跟别人不一样,就给自己小摩托换个外形或者某个好看的配件。这里换的配件就相当于库。
小结
事实上,库的使用是非常灵活的,但是没有框架来的方便,小编认为这是两者间的主要区别。此外,框架本身是有一套属于自己的解决方案的,但是react身为库的同时,其本身最大的作用就是用来写UI组件,自身并没有具备异步处理机制,模块化以及表单验证等,主要充当一个前端渲染的库而已,只有将React和react-router,react-redux,redux-saga等结合起来使用才称得上框架。
react作用
上面已经提到,react主要纯粹是用来写UI组件的,它可以与任何web程序一起使用。其中,最为常见的是使用react.js进行单页面程序(SPA)的开发。
react优点以及存在的不足
react的优点
- 使用了
virtual Dom,大大提升了渲染性能; - 代码组件化,便于复用,使用起来更加方便也更容易维护;
virtual Dom解问决了跨浏览器问题,并提供了标准化的API;- 能够很好的和现有代码结合使用;
- 易理解,易上手;
存在的不足
上面也讲到,react只是一个纯粹写UI组件的库,并不是一个框架。在项目开发中,仅仅是使用react明显是不够的(首先数据处理部分就很麻烦),此时需要结合react-router,react-redux,redux-saga等(或者是ReactRouter和Flux)使用,才能开发一个项目。
react-router
react-router概念
react-router简单来讲就是通过URL为react页面导航的路由,它通过管理 URL,实现组件的切换和状态的变化。react-router的核心概念是Router和Route。
在这里,我们需要明白的一点是,Router在这里作为一个容器,用于包裹Route。具体的路由跳转是由Route实现的。
举个栗子:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
ReactDOM.render(
<Router>
<Route path="/" component={App} />
<Route path="/user" component={User} />
...
</Router>,
node
);
说明:在这里引入了react-router-dom。其实react-router和react-router-dom的主要区别在于后者比前者多了<Link>, <BrowserRouter>这样的DOM类组件,其他没啥区别,引入时只需引入一个即可。当然,如果要搭配redux,还需要引入react-router-redux。
Router作为包裹Route的容器,当访问跟路由/时,组件APP就会加载到document.getElementById('app')。当访问路由/user时,将会呈现由组件User渲染构建的UI页面。
react-router作用
react-router最大的作用主要还是为react页面的实现路由跳转鞠躬尽瘁,保驾护航。
react-router如何使用
在讲react-router的使用之前,想简单的讲解一下router的history。
Router的history有三种类型:
HashHistory和HashRouterBrowerHistory和BrowerRoutercreateMemoryHistory和MemoryRouter
对于以上三种history,官方上看到推荐使用BrowerHistory。至于原因,可能是因为使用browserHistory时,url格式更加好看吧(小编瞎说的)。不过browserHistory使用下,浏览器表现的url的格式更加符合一般浏览器url的格式。例子如下:
-
使用
HashHistory,浏览器的url是这样的:/#/user/add?page=1; -
使用
BrowserHistory,浏览器的url是这样的:/user/add;
相比之下,使用BrowserHistory,url表现的形式可能更能被接受。但是需要注意,它需要server端支持。使用HashHistory的话,因为带有#的缘故,浏览器不会去发送request,react-router会自己根据路由去渲染相应的模块(适用于静态页面)。
关于react-router的使用,没有什么比官方文档讲解的更全面的吧。
附上链接:reacttraining.com/react-route…
不想看官网,可以,阮一峰老师讲解的也很不错呀。
链接:www.ruanyifeng.com/blog/2016/0…
不过阮老师写的这个只适合react-router 2.0版本的,童鞋们看的时候稍微注意一下。
redux
redux概念
我们知道,react的数据流是自顶向下的单项数据流,数据间的传递是通过父组件传递给子组件这一方式传递的。父组件的state可以作为子组件的props来传递数据,当state改变时,props也随之改变,但是props本身是不可改变的。
在项目当中,不同层级的页面之间往往需要传递数据。当需要传值的页面数量变多的情况下,传值关系可能会发生混乱。这时候要是有一个容器,能够帮助管理react的state的状态,那就很nice。接下来就是redux登场的时刻了!
什么是redux以及其作用?
redux是javascript状态容器,提供可预测化的状态管理。它可以构建一致化的应用,运用于不同的环境(客户端、服务器端、原生应用),并且易于测试。
redux的几个核心概念
1.Action
action是唯一可以改变状态的途径,同时它也是store的唯一数据来源。一般情况下,通过dispatch触发相应的action,从而达到改变store中state的目的。(注意,action是一个对象)
举个例子:
const action = {
type: 'xxx/add', // xxx是namespace名,type属性为必须
payload: {name: 'phoebe'},
... //可根据需求写callback()回调函数
}
2.Reducer
reducer,简单的说就是一个函数,它接受由dispatch触发的action和当前的state作为一个参数,返回一个新的state(简单的说就是根据action来更新state)。
举个例子:
const Reducer = ({state, action}) => {
...
return newState; //返回的新的state
}
3.Store
Store是把action和reducer联系起来的一个对象。Store可以理解为一个存储数据的仓库,管理着整个应用的状态。
注意: Redux 应用只有一个单一的 store。
Store的职责:
- 维持应用的
state;- 提供
getState()方法获取state;- 提供
dispatch(action)方法更新state;- 通过
subscribe(listener)注册监听器;- 通过
subscribe(listener)返回的函数注销监听器。
Redux通过 createStore 这个函数,来生成store对象:
import { createStore } from 'redux';
import todoApp from './reducers';
let store = createStore(todoApp)
同时,想获取到当前的state时,可以通过getState()这个方法来获取:
const state = store.getState()
小编给他们之间的关系画了个图,如下:
什么情况下需要redux?
当项目较为简单,没有过多的交互,View只从单一来源获取数据或不需要与服务器大量交互(或不使用websocket)时,可以不使用redux(使用了可能一定程度上会使项目变得更复杂)。
但是以下几种情况可以使用redux:
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了
WebSocket View要从多个来源获取数据
从组件的角度看,以下几种情况可以使用redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
拓展:如果对为什么react要使用redux还有不了解的童鞋,可以去看看下面链接的内容,小编觉得看完肯定就明了了。
链接: segmentfault.com/a/119000001…
react-redux
react-redux概念及作用
react-redux是redux的官方react的绑定库。它能够使你的react组件在redux的store中读取数据,并向store分发actions以便更新数据。
react-redux的分类
react-redux将所有的组件分为两大类:分别是UI组件(presentational component,又称傻瓜组件/无状态组件)和容器组件(container component)。
UI组件
UI组件具有以下几个特征:
- 只负责
UI呈现,不带有任何的业务逻辑 - 没有状态,
UI的渲染只能通过外部传入props来改变(也就是不使用this.state) - 所有的数据都由参数(
this.props)对象提供 - 不使用任何
redux的API
简单的来说,UI组件就是负责页面的渲染。
举个例子:
const page = number => <p>this is {number} page </p>
容器组件
容器组件和UI组件在一定程度上恰恰相反:
- 负责管理数据和业务逻辑,不负责页面渲染
- 带有内部状态
- 使用
redux的API
简单来说,容器组件就是负责管理数据以及处理页面的业务逻辑。
注意: 若是一个组件内既有UI组件又有逻辑,可以考虑将其拆分成外面是一个容器组件,里面包含一个UI组件的结构。前者负责与外部通信,将数据传给后者,后者负责页面的渲染。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
react-redux两个重要的API
react-redux提供了两个重要的API:connect和Provider。
Provider组件
react-redux提供了<Provider>组件,用于连接Store,把store提供给内部组件,内部组件接受store作为props,然后通过context往下传,这样react中任何组件都可以通过context获取store(简单来说就是使得我们的整个app都能访问到redux store中的数据)。
举个例子:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import store from './store';
import App from './App';
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App /> // Provider的子组件可以拿到store状态
</Provider>,
rootElement
);
同时,React-Redux提供一个connect方法,让我们可以把组件和store连接起来。
import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";
// const Counter = ...
const mapStateToProps = (state /*, ownProps*/) => {
return {
counter: state.counter
};
};
const mapDispatchToProps = { increment, decrement, reset };
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
这样,我们就能从store中获取相应的数据到Counter中。
connect()
connect的作用就是将UI组件和容器组件链接起来,本质的作用其实就是充当一个连接器。
connect()接受四个参数,但是一般情况下最常使用的是前两种:
connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(component)
1.mapStateToProps
作为connect的第一个参数,mapStateToProps用来从store中选择被连接的组件所需要的数据。
注意:
- 每当
store的state改变时,就会被调用 - 接收整个
store的state,并且返回组件所需要的数据
举个例子:
import React from 'react';
import { connect } from 'dva';
const Alarm = ({permission, ....}) => { // 定义的函数组件
//...
};
const mapStateToProps = ({ app }) => { //从store中摘出app
const { permission } = app; //从app中摘出组件需要的权限
return {
permission,
};
};
export default connect(mapStateToProps)(Alarm);
2.mapDispatchToProps
作为第二个传入connect的参数,mapDispatchToProps可以实现向store中分发acions。这也是唯一触发一个state变化的途径。它是用来建立 UI 组件的参数到store.dispatch方法的映射,可以是一个函数,也可以是一个对象。
React-Redux提供了两种可以分发actions的方式:
- 默认地,一个已连接组件可以接收
props.dispatch然后自己分发actions。 connect能够接收一个mapDispatchToProps作为第二个参数,这可以让我们能够创建dispatch调用方法,然后把这些方法作为props传递给我们的组件。
当我们不把mapDispatchToProps作为connect的第二个参数传入时,看下官方例子:
connect()(MyComponent);
// 与下面语句等价
connect(
null,
null
)(MyComponent);
// 或者
connect(mapStateToProps /** 没有第二个参数 */)(MyComponent);
若是我们使用这种方式,我们的组件就会接收props.dispatch,它可以用来分发组件中的actions。
看个更详细的例子:
import React from 'react';
import { connect } from 'dva';
const Alarm = ({dispatch, permission, ....}) => { // Alarm组件接收props的dispatch
const onAdd = () => {
dispatch({ //dispatch 用于触发onAdd方法的action
type: 'xxx',
payload: {...}
})
}
return(
<div onClick={onAdd}> //添加触发事件
//...
<div>
)
};
const mapStateToProps = ({ app }) => {
const { permission } = app;
return {
permission,
};
};
export default connect(mapStateToProps)(Alarm);
小结:有关mapDispatchToProps部分,小编主要结合所做项目做了相应的总结,关于它的函数形式和对象形式,有兴趣的童鞋可以点击此处了解详情。
3.mergeProps
mergeProps的格式为: mergeProps(stateProps, dispatchProps, ownProps)。
mergeProps是connect的第三个参数,可选。它将mapStateToProps()与mapDispatchToProps()返回的对象结果和组件自身的props合并成新的props,然后传入组件。默认返回Object.assign({}, ownProps, stateProps, dispatchProps)的结果。
写成例子如下:
const mergeProps = () => {
return Object.assign({}, ownProps, stateProps, dispatchProps)
}
4.options
作为connect的第四个参数,通过配置项可以更加详细的定义connect的行为,一般情况下只需要执行默认值。option有很多,举个官方例子:
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
小结:关于react-redux, 我们需要重点掌握它提供的两个组件--provider和connect,它们的用法。同时对于其它相关概念也需要了解一下。
redux-saga
redux-saga概念及其作用
redux-saga是一个用于管理应用程序Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的Library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
可以简单的理解为,redux-saga在redux中扮演着‘中间件’的角色,主要作用是用来执行redux中数据的异步操作。在执行异步操作时,需要借助ES6中的generator函数和yield关键字来以同步的方式实现异步操作。(它的功能有点像redux-thunk+async/await,通过创建 Sagas 将所有的异步操作逻辑都存放在一个地方进行集中处理)
为什么要使用redux-saga
因为redux中的action需要redux-thunk或者redux-saga这样的‘中间件’去做异步处理。(就一句话,简洁明了吧)
redux-saga的执行流程
流程图如下:
简单来说就是:ui组件触发action创建函数 -> action创建函数返回一个action -> action被传入redux中间件(被 saga等中间件处理) ,产生新的action,传入reducer -> reducer把数据传给ui组件显示 -> mapStateToProps -> ui组件显示
effect提供的常见的创建器及其用法有哪些
call异步阻塞调用put相当于dispatch,分发一个actionselect相当于getState,用于从store中获取响应的statefork异步非阻塞调用,无阻塞的执行fn,执行fn时,不会暂停Generatortake监听action,暂停Generator,匹配的action被发起时,恢复执行。take结合fork,可以实现takeEvery和takeLatest的效果takeEvery监听监听action,每监听到一个action,就执行一次操作takeLatest监听action,监听到多个action,只执行最近的一次cancel指示middleware取消之前的fork任务,cancel是一个无阻塞的Effect。也就是说,Generator将在取消异常被抛出后立即恢复race竞速执行多个任务throttle节流
redux-saga的优缺点
优点
- 可以集中处理异步操作,使得异步接口一目了然
action是个普通对象,与redux的action保持一致- 通过副作用(
Effect),方便异步接口的测试 - 通过
worker和watcher可以实现非阻塞异步调用,同时可以实现非阻塞调用下的事件监听
缺点:相对于新手来说,学习难度有点大,成本有点高(若是不考虑学习成本,建议用redux-saga)
结合示例
干讲可能有些童鞋会迷惑,下面小编举个代码例子讲一下redux-saga在代码中具体是怎样异步处理数据的。
注意,以下是小编从demo中抽取的一个页面代码,为了方便理解,所有组件都包含在<APP></APP>中,APP作为父组件,它 的state将作为所有子组件的props(能理解吧?)。
// magagement.js
import React from 'react';
import { connect } from 'dva';
import { Card, Select } from 'antd';
import Page from 'components/Page';
import Search from 'components/Search';
const Management = ({dispatch, management, permission }) = {
addClick = () => {
dispatch({ //当点击按钮时,触发action,就是文中所说的‘点击UI组件触发action’
type: 'management/add', // 触发之后将action中的type和当前payload传到reducer
payload: { name: phoebe },
});
};
return (
<Page title="xxx">
<Card>
<Search
extra={
permission.includes('xxx/xxxx') && (
<Button type="primary" icon="plus" onClick={addClick}> //为按钮添加一个触发事件
一个小可爱呀
</Button>
)
}
/>
</Card>
</Page>
)
};
const mapStateToProps = ({ app, management }) => { // 从store中抽出Managenent需要的数据
const { permission } = app;
return {
management,
permission,
};
};
export default connect(mapStateToProps)(Management); // 利用connect,将抽出的数据作为子组件的props传入
// Management.js的Models
import modelExtend from 'dva-model-extend';
import { pageModel } from 'models/common';
import { addManagement } from 'services/firmware'; // 接口
export default modelExtend(pageModel, {
namespace: 'management',
state: {},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (location.pathname === 'xxx/managenent') {
//...
}
})
}
},
effects: { //redux-saga管理副作用(effect),具体作用体现在这儿
//...
*add({ payload }, { call, put }) {
const data = yield call(xxx, payload); // 将处理的数据上传接口,之后UI更新显示
yield put({ // 创建并 yield 一个 dispatch Effect
type: 'updateState',
payload: {
name: 'Tins',
},
});
//...
},
},
});
//Management的路由
import React from 'react';
import { routerRedux, Route, Switch } from 'dva/router';
import App from 'routes/app';
import { LocaleProvider, Spin } from 'antd';
import zhCN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
const { ConnectedRouter } = routerRedux;
function RouterConfig({ history, app }) {
//....
const Management = dynamic({
app,
component: () => import('./xxx'),
});
return (
<ConnectedRouter history={history}>
<LocaleProvider locale={zhCN}>
<App>
<Switch>
//...
<Route path="/xxx" exact component={Management} /> // Mamagement作为APP的子组件,APP的store state将作为Management的props。
</Switch>
</App>
</LocaleProvider>
</ConnectedRouter>
);
}
小结: 关于redux-saga,使用的大致流程就是这样,觉得有不明白或者小编有描述不清晰的请留言。更加详细的知识点可以去redux-saga的官网瞅瞅。
redux-thunk
redux-thunk概念及其作用
redux-thunk也是redux的一个中间件(middleware)。当dispatch一个action之后,到达reducer之前,进行一些额外的操作时(处理action副作用),就需要使用到redux-thunk。它的作用跟redux-saga类似,都是用来处理异步数据。
有关redux-thunk,跟saga相比,其实小编觉得并没有说哪个更好,或者哪个不好,主要是看个人更加擅长使用哪个吧。在这里小编就不多说了,有关这部分的知识点,小编建议可以去看看阮一峰老师写的文档。
综合图解
为了省去文字,更加清晰的将文中所述的知识点连接起来,小编尝试画了个图,供理解。如下:
(若有疏漏,请留言指,手动笔芯~)
总结
关于react结合redux以及react-router(react-router-dom),redux-saga等,小编就总结到这儿吧。划重点:主要了解他们之间的联系并懂的使用。
唠嗑一下,最近小编发现,有的童鞋关注了,收藏了,但是就轻飘飘的溜了,这是咋回事儿?!
还是内句老话,若是发现小编哪儿梳理的有问题,欢迎下方留言,小编瞅到一定及时更正。若觉尚可,嘿嘿(整理不易,小编也需要鼓励)。