Javascript

关于react结合redux使用,或许你还应该掌握这些(图文结合,悉心整理)

本文主要是介绍关于react结合redux使用,或许你还应该掌握这些(图文结合,悉心整理),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

两周已过,小编携文来扰!!

最近小编委托组内小哥又给自己梳理了一遍react结合redux使用的知识点(因为懒,翻文档不如白嫖来的开心呀),主要涉及使用的注意事项和使用流程,涉及的中间件以及如何处理异步数据等。完后,小编觉得有必要对这次的知识点做一个系统的整理,造(wu)福(ren)大(zi)众(di)。

文中小编对于一些涉及流程的模块针对性的画了流程图,同时穿插了代码,方便理解(毕竟全是文字会显得文章很干)。若触到只是盲区想细品,小编建议从上往下看;若觉得自身掌握的可以,可直接跳至文尾,有惊喜!话不多说,安排起来!


react

react概念

首先,我们需要知道的是:React是一个声明式的,高效且灵活的用于构建用户界面的javascript库。React可以将一些简短,独立的代码片段(亦称‘组件’)组合成复杂的UI界面。

注意: react是一个库,而不是框架。

拓展: 库和框架有什么区别?

关于库: 库(Lab)是将代码集合成的一个产品,以供研发人员调用。库为我们提供了很多封装好的函数,我们在使用时只需要提取自己需要的函数即可,使用起来也非常灵活。若是没有,我们也可以手动封装函数实现。像jQueryreactunderscore就是库。

关于框架: 框架(Framework)则是为解决一个(一类)问题而开发的产品。一般情况下,框架用户只需要使用框架提供的类或函数,即可实现全部功能。像angularbackbonevue等这些属于框架。

举个例子: 就比如你买了一辆小摩托,小摩托买回来就可以用了,这里小摩托就相当于一个框架。然后某天你骑着心爱的小摩托去溜达,发现有人跟你骑着一样的小摩托,你想让自己的小摩托变得跟别人不一样,就给自己小摩托换个外形或者某个好看的配件。这里换的配件就相当于库。

小结

事实上,库的使用是非常灵活的,但是没有框架来的方便,小编认为这是两者间的主要区别。此外,框架本身是有一套属于自己的解决方案的,但是react身为库的同时,其本身最大的作用就是用来写UI组件,自身并没有具备异步处理机制,模块化以及表单验证等,主要充当一个前端渲染的库而已,只有将Reactreact-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等(或者是ReactRouterFlux)使用,才能开发一个项目。


react-router

react-router概念

react-router简单来讲就是通过URLreact页面导航的路由,它通过管理 URL,实现组件的切换和状态的变化。react-router的核心概念是RouterRoute

在这里,我们需要明白的一点是,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-routerreact-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的使用之前,想简单的讲解一下routerhistory

Routerhistory有三种类型:

  • HashHistoryHashRouter
  • BrowerHistoryBrowerRouter
  • reateMemoryHistoryMemoryRouter

对于以上三种history,官方上看到推荐使用BrowerHistory。至于原因,可能是因为使用browserHistory时,url格式更加好看吧(小编瞎说的)。不过browserHistory使用下,浏览器表现的url的格式更加符合一般浏览器url的格式。例子如下:

  • 使用HashHistory,浏览器的url是这样的:/#/user/add?page=1

  • 使用BrowserHistory,浏览器的url是这样的:/user/add

相比之下,使用BrowserHistoryurl表现的形式可能更能被接受。但是需要注意,它需要server端支持。使用HashHistory的话,因为带有#的缘故,浏览器不会去发送requestreact-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本身是不可改变的。

在项目当中,不同层级的页面之间往往需要传递数据。当需要传值的页面数量变多的情况下,传值关系可能会发生混乱。这时候要是有一个容器,能够帮助管理reactstate的状态,那就很nice。接下来就是redux登场的时刻了!

什么是redux以及其作用?

reduxjavascript状态容器,提供可预测化的状态管理。它可以构建一致化的应用,运用于不同的环境(客户端、服务器端、原生应用),并且易于测试。

redux的几个核心概念

1.Action

action是唯一可以改变状态的途径,同时它也是store的唯一数据来源。一般情况下,通过dispatch触发相应的action,从而达到改变storestate的目的。(注意,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是把actionreducer联系起来的一个对象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-reduxredux的官方react的绑定库。它能够使你的react组件在reduxstore中读取数据,并向store分发actions以便更新数据。

react-redux的分类

react-redux将所有的组件分为两大类:分别是UI组件(presentational component,又称傻瓜组件/无状态组件)和容器组件(container component)。

UI组件

UI组件具有以下几个特征:

  • 只负责UI呈现,不带有任何的业务逻辑
  • 没有状态,UI的渲染只能通过外部传入props来改变(也就是不使用this.state
  • 所有的数据都由参数(this.props)对象提供
  • 不使用任何reduxAPI

简单的来说,UI组件就是负责页面的渲染。

举个例子:

const page = number => <p>this is {number} page </p>
复制代码

容器组件

容器组件和UI组件在一定程度上恰恰相反:

  • 负责管理数据和业务逻辑,不负责页面渲染
  • 带有内部状态
  • 使用reduxAPI

简单来说,容器组件就是负责管理数据以及处理页面的业务逻辑。

注意: 若是一个组件内既有UI组件又有逻辑,可以考虑将其拆分成外面是一个容器组件,里面包含一个UI组件的结构。前者负责与外部通信,将数据传给后者,后者负责页面的渲染。

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

react-redux两个重要的API

react-redux提供了两个重要的APIconnectProvider

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中选择被连接的组件所需要的数据。

注意:

  • 每当storestate改变时,就会被调用
  • 接收整个storestate,并且返回组件所需要的数据

举个例子:

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)

mergePropsconnect的第三个参数,可选。它将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, 我们需要重点掌握它提供的两个组件--providerconnect,它们的用法。同时对于其它相关概念也需要了解一下。


redux-saga

redux-saga概念及其作用

redux-saga是一个用于管理应用程序Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的Library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。

可以简单的理解为,redux-sagaredux中扮演着‘中间件’的角色,主要作用是用来执行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,分发一个action
  • select相当于getState,用于从store中获取响应的state
  • fork异步非阻塞调用,无阻塞的执行fn,执行fn时,不会暂停Generator
  • take监听action,暂停Generator,匹配的action被发起时,恢复执行。take结合fork,可以实现takeEverytakeLatest的效果
  • takeEvery监听监听action,每监听到一个action,就执行一次操作
  • takeLatest监听action,监听到多个action,只执行最近的一次
  • cancel指示middleware取消之前的fork任务,cancel是一个无阻塞的Effect。也就是说,Generator将在取消异常被抛出后立即恢复
  • race竞速执行多个任务
  • throttle节流

redux-saga的优缺点

优点

  • 可以集中处理异步操作,使得异步接口一目了然
  • action是个普通对象,与reduxaction保持一致
  • 通过副作用(Effect),方便异步接口的测试
  • 通过workerwatcher可以实现非阻塞异步调用,同时可以实现非阻塞调用下的事件监听

缺点:相对于新手来说,学习难度有点大,成本有点高(若是不考虑学习成本,建议用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等,小编就总结到这儿吧。划重点:主要了解他们之间的联系并懂的使用

唠嗑一下,最近小编发现,有的童鞋关注了,收藏了,但是就轻飘飘的溜了,这是咋回事儿?!

还是内句老话,若是发现小编哪儿梳理的有问题,欢迎下方留言,小编瞅到一定及时更正。若觉尚可,嘿嘿(整理不易,小编也需要鼓励)。

这篇关于关于react结合redux使用,或许你还应该掌握这些(图文结合,悉心整理)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!