dva-taro 数据流方案 基于redux、redux-saga、react-router、fetch开发

链接: https://dvajs.com/knowledgemap/#effects

构成——通过model把一个领域的模型管理起来
  1. namespace 表示在全局state上的key
  2. state 初始值
  3. reducers 同步更新state
  4. effects 处理异步逻辑 常见数据请求
  5. subscriptions 订阅数据源
数据流向

通常通过交互行为 或者浏览器行为(比如路由跳转)触发 。当想通过此类行为改变数据的时候 首先可以通过dispatch发起一个action,如果是同步会直接通过reducers改变state, 如果是异步逻辑 则首先触发effcts 然后触发reducers改变state

单独使用
  • state:不可直接修改state的某个值 以此来保证每次都是全新对象 在dva中可以通过app._store 查看顶部的state数据
const app = dva()
console.log(app._store)
  • action、dispatch:改变state的唯一途径 触发action需要使用dispatch函数 必要带有type属性指明具体的行为 可通过props访问到dispatch
props.dispatch({
    type: '/user/remove', // user 指的是namespace remove指的是reducers或者effects中的方法
    payload: {} // 需要传递的参数
})
  • reducer: 接受两个参数,第一个是state 第二个是传进来的payload 他必须是纯函数 同样的输入必须得到同样的输出
reducers: {
    loginSuccess(state, {payload}) {
        return {
            ...state,
            user: payload
        }
    }
}
  • effect:处理异步逻辑 常见数据请求 第一个参数是携带的参数 第二个是函数内部的处理函数
effects: {
    *submit({ payload }, { call, put }) {
      console.log(payload)
      const response = yield call(register, payload); // call是用来发送数据请求的 第一个参数是关于api接口的回调函数 第二个参数是携带的参数
      yield put({
        type: 'registerHandle',
        payload: response,
      });
    },
  },

subscription: 是一种从源获取数据的方法 根据条件 dispatch 需要的 action 数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等

import key from 'keymaster';
...
app.model({
  namespace: 'count',
  subscriptions: {
    keyEvent({dispatch}) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  }
});
Router
import { Router, Route } from 'dva/router';
app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);
相关语法
  • yield call 执行异步函数

  • yield put 发出一个action 类似于dispatch

  • select 从state里获取数据。

  • es6的Generator 在effect中的应用

    • 一是在 function 后面,函数名之前有个 * ;

    • 函数内部有 yield 表达式。

    • effects: {
        // 进入页面加载
        *init({ payload }, { call, put, select }) {
          yield put({
            type: 'getPageInfo',
            payload: {
              pageData: pageData.startPage(1, 10)
            }
          });
        },
      },
      
项目例子
1. 项目入口文件 src/index.js
import React from 'react';
import dva, { dynamic, router } from 'dva';
import createLoading from 'dva-loading';
import { createHashHistory } from 'history';
import createRoutes from '@/routes';
import { ConfigProvider } from 'antd';


const { Router } = router;
// -> 初始化
const app = dva({
  history: createHashHistory({
    basename: homepage.startsWith('/') ? homepage : ''
  })
});
// -> 插件
app.use(createLoading());
app.use({ onError: config.exception.global });

// -> loading
dynamic.setDefaultLoadingComponent(() => config.router.loading);

// -> 注册全局模型
app.model(require('./models/global').default);

// -> 初始化路由
app.router(({ history, app }) => (
  <ConfigProvider>
    <Router history={history}>{createRoutes(app)}</Router> // 1
  </ConfigProvider>
));

// -> Start
app.start('#root');

// export global
export default {
  app,
  store: app._store,
  dispatch: app._store.dispatch
};

2. src/routes/index.js
import { createRoutes } from '@/utils/core';
import Login from './Login';
import Register from './Register';
import NotFound from './Pages/404';
import Dashboard from './Dashboard';

/**
 * 主路由配置
 * 
 * path 路由地址
 * component 组件
 * indexRoute 默认显示路由
 * childRoutes 所有子路由
 * NotFound 路由要放到最下面,当所有路由当没匹配到时会进入这个页面
 */
const routesConfig = app => [
  {
    path: '/sign',
    title: '登录',
    indexRoute: '/sign/login',
    component: UserLayout,
    childRoutes: [
      Login(app),
      Register(app),
      NotFound()
    ]
  },
  {
    path: '/',
    title: '系统中心',
    component: BasicLayout,
    indexRoute: '/dashboard',
    childRoutes: [
      Dashboard(app),
    ]
  }
];

export default app => createRoutes(app, routesConfig); // 2
3. src/utils/core.js
/**
 * 生成动态组件
 * @param {*} app
 * @param {*} models
 * @param {*} component
 */
export const dynamicWrapper = (app, models, component) =>
  dynamic({
    app,
    models: () => models,
    component
  });
/**
 * 生成一组路由
 * @param {*} app
 * @param {*} routesConfig
 */
export const createRoutes = (app, routesConfig) => { // 3
  const routes = routesConfig(app)
    .map(config => createRoute(app, () => config))
    .reduce((p, n) => {
      if (n.length) {
        return [...p, ...n];
      } else {
        return p.concat(n);
      }
    }, []);
  return <Switch>{routes}</Switch>;
};

/**
 * 生成单个路由
 * @param {*} app
 * @param {*} routesConfig
 */
export const createRoute = (app, routesConfig) => { // 4
  const {
    component: Comp,
    path,
    indexRoute,
    title,
    exact,
    ...otherProps
  } = routesConfig(app);

  if (path && path !== '/') {
    window.dva_router_pathMap[path] = { path, title, ...otherProps };
    // 为子路由增加parentPath
    if (otherProps.childRoutes && otherProps.childRoutes.length) {
      otherProps.childRoutes.forEach(item => {
        if (window.dva_router_pathMap[item.key]) {
          window.dva_router_pathMap[item.key].parentPath = path;
        }
      });
    }
  }

  // 把Redirect放到第一个
  if (indexRoute && $$.isArray(otherProps.childRoutes)) {
    otherProps.childRoutes.unshift(
      <Redirect key={path + '_redirect'} exact from={path} to={indexRoute} />
    );
  }

  const routeProps = {
    key: path || $$.randomStr(4),
    render: props => {
      // 此处可以做路由权限判断
      setDocumentTitle(title);
      return <Comp routerData={otherProps} {...props} />
    }
  };

  return <Route path={path} exact={!!exact} {...routeProps} />;
};

/**
 * 设置页面title
 * @param {*} title 
 */
function setDocumentTitle(title) {
  const documentTitle = config.htmlTitle ? config.htmlTitle.replace(/{.*}/gi, title) : title
  if (documentTitle !== document.title) {
    document.title = documentTitle;
  }
}
4. routes/login/index.js
import { dynamicWrapper, createRoute } from '@/utils/core';

const routesConfig = (app) => ({
  path: '/sign/login',
  title: 'Login',
  component: dynamicWrapper(app, [import('./model')], () => import('./components')) // 生成动态的组件
});

export default (app) => createRoute(app, routesConfig); // 导出当前路由react组件 以及model

5. routes/login/server/index.js
import axios from 'axios'

export async function member(payload) {
  return axios.get('接口',参数)
}

6. routes/login/model/index.js
import { member } from '../service'
export default {
  namespace: 'member',
  state: {
    productItem: {},
  },
  effects: {
    *init({ payload }, { call, put }) {
      try {
        const { data: { data }} = yield call(member, payload)
        yield put({
          type: 'setData',
          payload: {
            data: data[0]
          }
        })
      } catch(e) {
        console.log(e)
      }
    }
  },
  reducers: {
    setData(state, { payload }) {
      return {
        ...state,
        productItem: payload.data
      }
    }
  }
}

7. routes/login/components/index.js
import React, { Component } from 'react'
import Taro from '@tarojs/taro'
import { Image, View } from '@tarojs/components'
import { connect } from 'react-redux'
import './member.scss'
import { AtButton } from 'taro-ui'

interface ItemProps {
  dispatch: any,
  productItem: any,
  orderItem: any,
}
interface ItemState { }
class Member extends Component<ItemProps, ItemState> {

  componentDidMount() {
    this.props.dispatch({
      type: 'member/init',
      payload: {
        page: 1,
        perPage: 3,
        city_id: '257'
      }
    })
  }

  render() {
    const { productItem } = this.props
    return (
      <view className="member">
         {
            productItem && productItem.products && productItem.products.map(item => (
              <View className='vip-product'>
                <View>
                  {item.item_attribute_detail.attribute_value}
                </View>
                <View style="color:red">{item.price}</View>
              </View>
            ))
          }
        </View>
      </view>
    )
  }
}

function getState({ member: { productItem } }) {
  return {
    productItem
  }
}
export default connect(getState)(Member)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值