两周已过,小编携文来扰!!
最近小编委托组内小哥又给自己梳理了一遍react
结合redux
使用的知识点(因为懒,翻文档不如白嫖来的开心呀),主要涉及使用的注意事项和使用流程,涉及的中间件以及如何处理异步数据等。完后,小编觉得有必要对这次的知识点做一个系统的整理,造(wu)福(ren)大(zi)众(di)。
文中小编对于一些涉及流程的模块针对性的画了流程图,同时穿插了代码,方便理解(毕竟全是文字会显得文章很干)。若触到只是盲区想细品,小编建议从上往下看;若觉得自身掌握的可以,可直接跳至文尾,有惊喜!话不多说,安排起来!
首先,我们需要知道的是:
React
是一个声明式的,高效且灵活的用于构建用户界面的javascript
库。React
可以将一些简短,独立的代码片段(亦称‘组件’)组合成复杂的UI
界面。
注意: react
是一个库,而不是框架。
关于库: 库(Lab
)是将代码集合成的一个产品,以供研发人员调用。库为我们提供了很多封装好的函数,我们在使用时只需要提取自己需要的函数即可,使用起来也非常灵活。若是没有,我们也可以手动封装函数实现。像jQuery
、react
、underscore
就是库。
关于框架: 框架(Framework
)则是为解决一个(一类)问题而开发的产品。一般情况下,框架用户只需要使用框架提供的类或函数,即可实现全部功能。像angular
、backbone
、vue
等这些属于框架。
举个例子: 就比如你买了一辆小摩托,小摩托买回来就可以用了,这里小摩托就相当于一个框架。然后某天你骑着心爱的小摩托去溜达,发现有人跟你骑着一样的小摩托,你想让自己的小摩托变得跟别人不一样,就给自己小摩托换个外形或者某个好看的配件。这里换的配件就相当于库。
事实上,库的使用是非常灵活的,但是没有框架来的方便,小编认为这是两者间的主要区别。此外,框架本身是有一套属于自己的解决方案的,但是react
身为库的同时,其本身最大的作用就是用来写UI
组件,自身并没有具备异步处理机制,模块化以及表单验证等,主要充当一个前端渲染的库而已,只有将React
和react-router
,react-redux
,redux-saga
等结合起来使用才称得上框架。
上面已经提到,react
主要纯粹是用来写UI
组件的,它可以与任何web
程序一起使用。其中,最为常见的是使用react.js
进行单页面程序(SPA
)的开发。
virtual Dom
,大大提升了渲染性能;virtual Dom
解问决了跨浏览器问题,并提供了标准化的API
;上面也讲到,react
只是一个纯粹写UI
组件的库,并不是一个框架。在项目开发中,仅仅是使用react
明显是不够的(首先数据处理部分就很麻烦),此时需要结合react-router
,react-redux
,redux-saga
等(或者是ReactRouter
和Flux
)使用,才能开发一个项目。
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页面的实现路由跳转鞠躬尽瘁,保驾护航。
在讲react-router
的使用之前,想简单的讲解一下router
的history
。
Router
的history
有三种类型:
HashHistory
和HashRouter
BrowerHistory
和BrowerRouter
reateMemoryHistory
和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
版本的,童鞋们看的时候稍微注意一下。
我们知道,react
的数据流是自顶向下的单项数据流,数据间的传递是通过父组件传递给子组件这一方式传递的。父组件的state
可以作为子组件的props
来传递数据,当state
改变时,props
也随之改变,但是props
本身是不可改变的。
在项目当中,不同层级的页面之间往往需要传递数据。当需要传值的页面数量变多的情况下,传值关系可能会发生混乱。这时候要是有一个容器,能够帮助管理react
的state
的状态,那就很nice
。接下来就是redux
登场的时刻了!
redux
是javascript
状态容器,提供可预测化的状态管理。它可以构建一致化的应用,运用于不同的环境(客户端、服务器端、原生应用),并且易于测试。
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() 复制代码
小编给他们之间的关系画了个图,如下:
当项目较为简单,没有过多的交互,View
只从单一来源获取数据或不需要与服务器大量交互(或不使用websocket
)时,可以不使用redux
(使用了可能一定程度上会使项目变得更复杂)。
但是以下几种情况可以使用redux
:
WebSocket
View
要从多个来源获取数据从组件的角度看,以下几种情况可以使用redux
:
拓展:如果对为什么react
要使用redux
还有不了解的童鞋,可以去看看下面链接的内容,小编觉得看完肯定就明了了。
链接: segmentfault.com/a/119000001…
react-redux
是redux
的官方react
的绑定库。它能够使你的react
组件在redux
的store
中读取数据,并向store
分发actions
以便更新数据。
react-redux
将所有的组件分为两大类:分别是UI
组件(presentational component
,又称傻瓜组件/无状态组件)和容器组件(container component
)。
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
:connect
和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
的作用就是将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
是一个用于管理应用程序Side Effect
(副作用,例如异步获取数据,访问浏览器缓存等)的Library
,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
可以简单的理解为,redux-saga
在redux
中扮演着‘中间件’的角色,主要作用是用来执行redux
中数据的异步操作。在执行异步操作时,需要借助ES6
中的generator
函数和yield
关键字来以同步的方式实现异步操作。(它的功能有点像redux-thunk+async/await
,通过创建 Sagas
将所有的异步操作逻辑都存放在一个地方进行集中处理)
因为redux
中的action
需要redux-thunk
或者redux-saga
这样的‘中间件’去做异步处理。(就一句话,简洁明了吧)
流程图如下:
简单来说就是:ui
组件触发action
创建函数 -> action
创建函数返回一个action
-> action
被传入redux
中间件(被 saga
等中间件处理) ,产生新的action
,传入reducer
-> reducer
把数据传给ui
组件显示 ->mapStateToProps
-> ui
组件显示
call
异步阻塞调用put
相当于dispatch
,分发一个action
select
相当于getState
,用于从store
中获取响应的state
fork
异步非阻塞调用,无阻塞的执行fn
,执行fn
时,不会暂停Generator
tak
e监听action
,暂停Generator
,匹配的action
被发起时,恢复执行。take
结合fork
,可以实现takeEvery
和takeLatest
的效果takeEvery
监听监听action
,每监听到一个action
,就执行一次操作takeLatest
监听action
,监听到多个action
,只执行最近的一次cancel
指示middleware
取消之前的fork
任务,cancel
是一个无阻塞的Effect
。也就是说,Generator
将在取消异常被抛出后立即恢复race
竞速执行多个任务throttle
节流优点
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
的一个中间件(middleware
)。当dispatch
一个action
之后,到达reducer
之前,进行一些额外的操作时(处理action
副作用),就需要使用到redux-thunk
。它的作用跟redux-saga
类似,都是用来处理异步数据。
有关redux-thunk
,跟saga
相比,其实小编觉得并没有说哪个更好,或者哪个不好,主要是看个人更加擅长使用哪个吧。在这里小编就不多说了,有关这部分的知识点,小编建议可以去看看阮一峰老师写的文档。
为了省去文字,更加清晰的将文中所述的知识点连接起来,小编尝试画了个图,供理解。如下:
(若有疏漏,请留言指,手动笔芯~)
关于react
结合redux
以及react-router(react-router-dom)
,redux-saga
等,小编就总结到这儿吧。划重点:主要了解他们之间的联系并懂的使用。
唠嗑一下,最近小编发现,有的童鞋关注了,收藏了,但是就轻飘飘的溜了,这是咋回事儿?!
还是内句老话,若是发现小编哪儿梳理的有问题,欢迎下方留言,小编瞅到一定及时更正。若觉尚可,嘿嘿(整理不易,小编也需要鼓励)。