dva使用

2020/06/17 react

dva使用笔记

1. dva

  1. 基于 reduxredux-sagareact-router 的轻量级前端框架。
  2. dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作
  3. 集成封装了react、react-dom、react-router-dom、connected-react-router、redux、redux-saga

2、dva机制

dva是通过用户交互行为或者浏览器URL跳转行为来改变数据的。当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State

如图:

3、概念

3.1 state 状态

state和redux的状态一样,表示Model的状态数据,操作的时候不可改变元数据,保证每次都是全新对象。

3.2 action 动作

  1. 改变state的唯一途径
  2. 任何行为想改变state都需要dispatch一个action
  3. action必须包含一个type属性
    // dispatch(Action); 
    dispatch({ type: 'add', payload: 'Learn Dva' });
    

3.3 dispatch 派发

  1. dispatch是触发action的函数
  2. action只是一个行为,dispatch是触发这个行为的方式。
  3. 在dva中,connect Model的组件通过props可以访问到dispatch,可以调用Model中的reducer或者effect。
    dispatch({
      type: 'add', // 定义类型,进行判断然后操作
      other: {}, // 需要传递的其他的信息
    });
    

3.4 reducer 处理器

reducer是一个函数,接收state 和 action,返回老的或新的state。

reducers: {
  add(state, { payload: todo }) {
    const todos = state.todos.concat(todo);
    return { ...state, todos };
  },
}

3.5 Effect 副作用

  1. Effect通常用来处理异步操作。
  2. 之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。
  3. dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。

3.6 Subscription 订阅

Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action, 数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

3.7 Router 路由

就是前端路由

3.8 Route Components 路由组件

一般一个页面作为一个路由组件。

4、使用

$ create-react-app dva-app

yarn add dva

把src目录下的内容删了。

// src/index.js

import React from 'react';
import dva, { connect } from 'dva';
import { Router, Route } from 'dva/router';

//  监听键盘  需要安装 yarn add keymaster
import keymaster from 'keymaster';

let app = dva();

// 定义模型
app.model({
  // 每个模型都有自己的命名空间
  namespace: 'name1',
  // 每个模型都有自己的状态
  state: {
    num: 1
  },
  // 处理器
  reducers: {
    // 处理器的属性名就是action-type,值是一个函数,用来计算新状态
    // 例如:store.dispatch({type: 'name1/add'}), 这个时候就会派发给add()函数来执行
    add(state, action){
      return {num: statenum + 1};
    },
    // store.dispatch({type: 'name1/minus'})
    minus(state, action){
      return {num: statenum - 1};
    }
  },
  // 异步操作
  // 如上面的图所示,当派发阶段,会同时派发给reducer和effect,然后根据属性名来匹配要执行的方法
  effects: {
    *asyncAdd(action, effect){
      // put: 派发动作, call:调用方法  select是个函数  yield select( state => state.num );
      let { put, call, select } = effect;
      // fn是一个promise函数,需要自己实现,一般为调接口
      yield call(fn, 1000);
      // 给当前模型派发action不能加命名空间,给其他模型派发需要加命名空间
      yield put({type: 'add'});

    }
  },
  subscriptions: {
    change({history}){
      history.listen((location)=>{
        console.log(location);
      })
    },
    keyboard({dispatch}) {
      // keymaster('要监听的按键', 回调函数)
      keymaster('space', () => {
        dispatch({type: 'add'});
      })
    }
  }
})

app.model({
  namespace: 'name2',
   state: {
    num: 1
  },
  reducers: {
    // store.dispatch({type: 'name2/add'})
    add(state, action){
      return {num: statenum + 1};
    },
    minus(state, action){
      return {num: statenum - 1};
    }
  }
})

function Counter1(props){
  return {
    <div>
      <p>{props.num}</p>
      <button onClick={ () => props.dispatch({type: 'name1/add'})}> + </button>
      <button onClick={ () => props.dispatch({type: 'name1/asyncAdd'})}> + </button>
      <button onClick={ () => props.dispatch({type: 'name1/minus'})}> - </button>
    </div>
  }
}

function ConnectedCounter1 = connect(state => state.counter1)(Counter1);
function ConnectedCounter2 = connect(state => state.counter2)(Counter2);

app.router(({ history })=> (
  <Router history={history}>
    <>
      <Route path='/counter1' component={ConnectedCounter1}/>
      <Route path='/counter2' component={ConnectedCounter2}/>
    </>
  </Router>
) ); 
app.start('#root');

Search

    Table of Contents