# react-dva-study **Repository Path**: goodjiang/react-dva-study ## Basic Information - **Project Name**: react-dva-study - **Description**: 学习基于react的dva框架 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-04-06 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # react-dva-study #### 介绍 学习基于react的dva框架 ### 1.安装 **1.1 安装 dva-cli** $ npm install dva-cli -g $ dva -v dva-cli version 0.9.1 **1.2 创建新应用** ``` dva new dva-quickstart ``` 这会创建 dva-quickstart 目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。 ``` $ cd dva-quickstart $ npm start ``` ### 2.使用antd 通过 npm 安装 antd 和 babel-plugin-import 。babel-plugin-import 是用来按需加载 antd 的脚本和样式的 ``` 如果使用antd出现以下报错的话,修改antd版本为3.26.14 TypeError: antd_es_form__WEBPACK_IMPORTED_MODULE_7__.default.create(...) is not a function ``` ``` $ npm install antd babel-plugin-import --save ``` 编辑 .webpackrc,使 babel-plugin-import 插件生效。 ``` { "extraBabelPlugins": [ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }] ] } ``` ### 3.定义路由 我们要写个应用来先显示产品列表。首先第一步是创建路由,路由可以想象成是组成应用的不同页面 **3.1 创建路由页面** 在routes下面创建了两个文件夹分别为 indexPage 和 indexRouter 两个文件夹,内容类似下面代码 ``` import React, { Component } from 'react' export default class IndexRouter extends Component { render() { return (
indexRouter
) } } ``` 在router.js里面分别把routes里面的路由页面引入进来,并且定义路由路径 ``` import React from 'react'; import { Router, Route, Switch } from 'dva/router'; import IndexPage from './routes/indexPage/IndexPage'; import IndexRouter from './routes/indexRouter/IndexRouter'; function RouterConfig({ history }) { return ( ); } export default RouterConfig; ``` **3.2 切换 history 为 browserHistory** 先安装 history 依赖 ``` $ npm install --save history ``` 然后修改入口文件(index.js) ``` import { createBrowserHistory as createHistory} from 'history' const app = dva({ history: createHistory(), }); ``` ### 4.使用组件 Components 在components文件夹中书写子组件 ----------子组件--------------- ``` import React, { Component } from 'react' export default class Example extends Component { render() { return (
商品详情:{this.props.title} //this.porps.title接受父组件传过来的title
) } } ``` ### 4.定义Model(类似react中的redux) **4.1 新建 model models/products.js :** ``` export default { namespace: 'product', //namespace - - 命名空间 state: { //state - - 数据状态 productList:[ { name:'豆子' }, { name:'玉米' } ] }, reducers: { //reducer - - 修改状态 updateList(state,action) { const productList = state.productList.concat(action.payload) return { ...state, productList }; }, }, }; ``` **4.2 connect连接model** ``` // 3. Model app.model(require('./models/example').default); app.model(require('./models/product').default); ``` **4.3 在父组件中** ``` import React from 'react' import {connect} from 'dva' //使用connect方法将model和组建绑定 import Example from '../../components/Example' //引入子组件 class indexRouter extends React.Component { render() { const { productList ,dispatch } = this.props; //dispatch是model中的方法 return (
) } } const mapStateToProps = (state) => { return { productList:state.product } } export default connect(mapStateToProps)(indexRouter) ``` **4.4 在子组件中** ``` import React, { Component } from 'react' export default class Example extends Component { clickProductList = (e) => { //点击的时候往state中的product数组添加一条数据 const currentProduct = { name:'玉米1' } this.props.dispatch({ //调用dispatch方法,type是namespace下的updateList方法,需要传一个参数写在payload中 type:'product/updateList', payload:currentProduct }) } render() { const {productList } = this.props.productList return (
) } } ``` ### 5.路由跳转 ``` 第一种方式 import {Link} from 'dva/router' 去首页 第二种方式 如果是在子组件内点击跳转,因为子组件没有被路由包裹,所有没有history参数。 在父组件页面把this.props.history传入子组件, history={this.props.history} 子组件在需要跳转的按钮添加点击事件 onClick={this.clickProductList} 通过this.props.history.push进行跳转 clickPushIndex = (e) =>{ this.props.history.push('/') } 第三种方式 如果在子组件里面,不通过父组件传递来过this.props.history的话 引入导出 withRouter import {withRouter} from 'dva/router' export default withRouter(Example) 第四种方式 import { routerRedux } from 'dva/router'; 子组件在需要跳转的按钮添加点击事件 onClick={this.clickProducReduxtList} clickProducReduxtList= (e) =>{ this.props.dispatch(routerRedux.push('/')) } ``` ### 6.Model异步操作 **6.1 在models下面的product.js中** 异步操作的方法可以看不懂可以查看dva官网知识地图中的Generators方法介绍,* 可以查看es6 的方法介绍 ``` // 异步操作 effects:{ *updateListAsync({payload},{call,put}){ yield put({ type:'updateList', payload //传的参数 }) } } ``` **6.2 给点击时间增加异步操作** ``` clickProductListAsync = (e) =>{ const currentProduct = { name:'玉米2' } this.props.dispatch({ type:'product/updateListAsync', payload:currentProduct }) } ``` ### 7.Mock数据处理 **7.1 在util把request进行封装一下** ``` import fetch from 'dva/fetch'; function parseJSON(response) { return response.json(); } function checkStatus(response) { if (response.status >= 200 && response.status < 300) { return response; } const error = new Error(response.statusText); error.response = response; throw error; } const parseQuery = (obj) => { let str = '' for (let key in obj) { const value = typeof obj[key] !== 'string' ? JSON.stringify(obj[key]) : obj[key] str += '&' + key + '=' + value } return str.substr(1) } /** * Requests a URL, returning a promise. * * @param {string} url The URL we want to request * @param {object} [options] The options we want to pass to "fetch" * @return {object} An object containing either "data" or "err" */ const request = (url, method = 'get', data) => { const options = { method: method, // HTTP请求方法,默认为GET headers: { // HTTP的请求头,默认为{} 'Content-Type': 'application/json' }, credentials: 'include' // 是否携带cookie,默认为omit,不携带; same-origi,同源携带; include,同源跨域都携带 } if (method === 'get') { url += '?' + parseQuery(data) } else { options.body = JSON.stringify(data) } return fetch(url, options) .then(checkStatus) .then(parseJSON) .then(data => ({ data })) .catch(err => ({ err })); } export default { get (url, data) { return request(url, 'get', data) }, post (url, data) { return request(url, 'post', data) } } ``` **7.2 在.roadhogrc.mock.js把mock文件夹下的所有js全部引入** ``` // 可以把mock里面所有的js都导出 import fs from 'fs' import path from 'path' const mock = {} fs.readdirSync(path.join(__dirname + '/mock')).forEach(function(file){ if(file.match(/\.js$/)){ Object.assign(mock,require('./mock/' +file)) } }); export default mock ``` **7.3 实例操作** 在mock文件夹下面创建一个product.js文件 ``` module.exports = { "get /api/product" : {"name":"高粱"} } ``` 在services文件夹下的example.js下把product方法导出 ``` import httpApi from '../utils/request'; export function getProduct(){ return httpApi.post("/api/product") } ``` 在组件中使用 ``` import * as api from '../services/example' api.getProduct() .then(res=>{ console.log(res) }) ``` ### 7.网络请求 **7.1 在models下面的product.js中api引入** ``` import * as api from '../services/example' // 网络请求方法 *updateListHttp({payload},{call,put}){ const result = yield call(api.getProduct,payload) const data = result.data; if(data){ yield put({ type:'updateList', payload:data //传的参数 }) } } ``` **7.2 页面对方法进行调用** ``` clickProductListHttp = (e) =>{ console.log('http') this.props.dispatch({ type:'product/updateListHttp', payload:{ id:1001 //传的参数 } }) } ``` **7.3 在services文件夹下的example.js中需要传一个data** ``` import httpApi from '../utils/request'; export function getProduct(data){ return httpApi.get("/api/product",data) } ``` **7.3 在mock文件夹下面的 product.js获取传入的参数** ``` module.exports = { "get /api/product" :(req,res)=>{ const params = req.query res.send({"name":"高粱"}) } } ``` ### 8.遍历Model 在model文件夹下面创建一个index.js ``` const context = require.context('./',false,/\.js$/) export default context .keys() .filter(item => item != './index.js') .map(key => context(key)) ``` 把主文件index.js下面的 3. Model文件可以替换一下 ``` require('./models').default.forEach(key => app.model(key.default)) ``` ### 结尾 ### 推荐大家可以查看redux-action