什么是dva

dva 是一个基于 redux 和 redux-saga 的数据流方案.

为什么使用dva

因为它简化了react引入redux的过程。

传统redux与dva对比

redux:

clipboard.png

开发时,我们需要action,reducer等文件,并且需要自行分类,不太清晰。

dva:

clipboard.png

开发时,把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面,分类清晰。
增加了一个 Subscriptions, 用于收集其他来源的 action。

如何使用dva

函数式组件与非函数式组件

概要

HelloWorldModel.js

export default {
  namespace: 'HelloWorldModel',
  state: {str:"hi"},
  reducers: {
    
  },
};

这是react组件的创建:

class HelloWorld extends Component {
  constructor() {
    super()
    this.state = {  }
  }

  sayHi () {
    alert('this.props.HelloWorldModel.str)
  }

  render () {
    return (
      <div onClick={this.sayHi.bind(this)}>Hello World</div>
    )
  }
}

export default connect(({ HelloWorldModel }) => ({
  HelloWorldModel,
}))(HelloWorld);

函数式组件

const HelloWorld = (props) => {
  const sayHi = (event) => alert(this.props.HelloWorldModel.str)
  return (
    <div onClick={sayHi}>Hello World</div>
  )
}
export default connect(({ HelloWorldModel }) => ({
  HelloWorldModel,
}))(HelloWorld);

属性与状态

页面的数据分为2大类:
1.页面属性:Modal打开或者关闭,与用户行为相关,与后台数据无关
2.页面状态:Table展示的数据内容,与用户行为无关,与后台数据相关
我通常在react组件中直接使用state作为页面属性,将页面状态存在model中,
而函数式组件,则将页面属性和状态全部存放在model中。

生命周期

上面是react组件和函数式组件的写法区别,
通过react组件写法声明的组件继承了“react.Component”的一些属性及方法,如生命周期钩子函数等。
而函数式组件只是个返回dom节点的方法,所以不具备生命周期函数。
因为dva的数据流是单向的,
用户行为=》视图=》model.Effect=>model.Reducer=>store变化=>视图更新,
react组件可以在“componentWillReceiveProps”钩子中决定是否允许当前组件内的数据流动,
函数式组件只能被动接收数据流动。

页面与状态的绑定和流转

如上述代码,页面与model的绑定,是通过connect方法实现的,在connect方法中,入参可以抓取到所有导入的model文件,我们可以在每个组件中(需要关联model的)“重载”connect方法,只获取我们当前页面所需的model,展示在页面上。而页面的事件则可以使用dispatch触发model的Effect事件与服务端交互之后执行reducer,store(model数据)变化后,页面因为connect了model,相当于订阅了model的变化,数据流入,页面更新。

公共状态抽离

一些公用的状态是可以抽离到同一个model中的,如登录态,上一次查询参数等。

实际应用中的一些解决方案

动态组件

dva提供dynamic方法支持动态加载组件和model,

import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
  app,
  models: () => [
    import('./models/users'),
  ],
  component: () => import('./routes/UserPage'),
});

通过动态加载后,build出来的文件将index.js切割成index.js(体积变小) + 1.async.js等等,
但是index.js默认是通过"./"来加载1.async.js的,对于前端静态资源与服务端应用不在同一台服务器的情况,如
java boot应用 发布在http://xxx.com/a =》 a.ftl 中引用 dva build之后的静态资源,而静态资源发布却是在另一台服务器上http://zzz.com/dvaBuild/dist,
index.js还是去访问默认的"./1.async.js" 其实访问的是http://xxx.com/a/1.async.js,...
可以直接在页面script标签中,

window.__webpack_public_path__ = 'http://zzz.com/dvaBuild/dist';

路由跳转

权限判断(接入第三方登陆)

1.是否登陆
在GlobalModel中保存着登录态,路由中使用AuthComponent替换Route,在渲染路由组件的时候就会判断是否登陆,如果没有登陆渲染正在登陆loading页面,等待第三方登陆接口判断已经登陆,页面会自动更新为路由组件。
这是第三方登陆,如果是内部登陆使用react-router跳转。
AuthComponent

@connect(({GlobalModel}) => GlobalModel)
export default class AuthComponent extends PureComponent {
  render() {
    const { component: Component, isLogin, ...rest } = this.props;
    return  (
      <Route
      {...rest}
      render={props =>
        isLogin == 1 ? (
          <Component {...props}/>
        ) : (
          <Logining></Logining>
        )
      }
    />
    )
  }
}

GlobalModel

effects: {
    *CheckLogin({payload,callback}, {call,select,put}) {
        const loginfoResult = yield call(getLoginInfo);
        if(loginfoResult && loginfoResult.ResponseStatus && loginfoResult.ResponseStatus.Ack == "Success" && loginfoResult.userBasicInfoDTO){
          if(loginfoResult.userBasicInfoDTO && loginfoResult.userBasicInfoDTO.userAccount && checkPermission(loginfoResult.userBasicInfoDTO.userAccount)){
            yield put({type:"save",loginInfo:loginfoResult})
            callback();
          }else{
            yield put( routerRedux.push('/NoPermission') ); //没有权限
          }
        }else{
          window.location.href = getLoginUrl();
        }
    },

需要在各个路由页面绑定的model的路由切换订阅方法中做登陆判断,
页面切换就要判断是否登陆或鉴权

subscriptions: {
    setup({ dispatch, history }) {  // eslint-disable-line
      return history.listen((location) => {
        dispatch({
          type: "GlobalModel/onShow",
          callback: () => {

                dispatch({type:"BaseInfoQuery",payload:{}});
            }

            })
      })

    },

ThenMorning
190 声望18 粉丝

但行好事,莫问前程.