在react脚手架中应用dva

395 阅读4分钟

虽然我们可以直接安装dva脚手架,但是安装的dva脚手架和我们原来的react脚手架的目录结构还是不太一样的,这让我们有些小伙伴用的不太顺手,如果有需要在react中脚手架手搭dva的小伙伴,本文章应该可以帮到您
首先我们先安装react-cli:
npx create-react-app myapp
其次在安装dva-core(内部已提供了redux包):
npm i dva-core
最后需要在下载react-redux包(因为dva-core这个包并没有内置react-redux;因为版本的问题,我安装的是@7.2.6版本的):
npm i react-redux@7.2.6
好啦,基本的包咱们已经安装完毕了,开始搭建store吧!
1、创建store文件夹/index.js

import {create} from 'dva-core'//引入create
import models from './models'//引入模块:[]
import loading from 'dva-loading'//在开发异步加载的功能时,为提高用户体验一般会显示加载提示

const app = create()//创建应用实例

if(Array.isArray(models)) {//判断是否为数组
    models.forEach(model => {//遍历每一项
        app.model(model)//使用模块
    })
}

app.use(loading()) //注册插件

app.start()//应用开启

export default app._store;//暴露store

2.1、在store目录下创建models文件夹/login.js

import axios from 'axios//下载一下axios 引入
const asyncGetCode = (url, params) => (url, params) => {//封装get请求方法
    return new Promise((resolve) => {
        axios.get(url, {params}).then(resp => {
            resolve(resp)
        })
    })
}
const asyncLogin = (url, params) => {//封装post请求方法
    return new Promise((resolve) => {
        axios.post(url, params).then(resp => {
            resolve(resp)
        })
    })
}
export default {
    namespace: 'login',//类似于vuex的命名空间
    state: {//定义初始状态
        token: localStorage.getItem('token') || '',
        codeId: ''
    },
    reducers: {//定义更新状态的方法
        setToken(state, {data}) {
            return Object.assign({}, state, data)
        },
        setCodeId (state, {data}) {
            return Object.assign({}, state, data)
        }
    },
    effects: {//定义副作用方法 (异步) * 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试
        *getCodes(payload, {call, put}) {//获取验证码方法 
            let res = yield call(asyncGetCode, '/api/mobile/getCode') //用内置参数call调用获取验证码接口
            if(res.data.code === 200) {
                alert('验证码 =====> ' + res.data.data.text) //打印出来 
                yield put({type: 'setCodeId', data: {codeId: res.data.data.codeId}}) //用内置put分发action对象更改状态中的codeId
            }else {
                console.log(res.data.msg)
            }
        },
        *login(payload, {call, put, select}) {
            let state = yield select() //通过内置参数select获取状态state
            let loginInfo = Object.assign({}, payload.data, {codeId: state.login.codeId})//合并登陆字段
            let loginRes = yield call(asyncLogin, '/api/mobile/login', loginInfo) //用内置参数call调用登录接口
            if(loginRes.data.code === 200) {
                localStorage.setItem('token', loginRes.data.token) //存本地token
                yield put({type: 'setToken', data: {token: loginRes.data.token}})// 用内置参数put分发action 更新状态中的token
            }else {
                console.log(loginRes.data.msg)
            }
        },
    }
};

2.2、再创建models文件夹/home.js(创建这个文件虽然没啥作用,但主要是为了演示dva怎样实现模块化)

import axios from 'axios'
const getHomeList = (url, params) => {//封装post请求方法
    return new Promise((resolve) => {
        axios.post(url, params).then(resp => {
            resolve(resp)
        })
    })
}

export default {
    namespace: 'home',
    state: {
        referList: []
    },
    reducers: {
        SETREFERLIST(state, {data}) {
            return Object.assign({}, state, data)
        },
    },
    effects: {
        *asyncGetList (payload, {call, put}) {
            const resp = yield call(getHomeList, '/api/admin/getRefer')//调用方法
            if(resp.data.code === 200) {
                yield put({type: 'SETREFERLIST', data: {referList: resp.data.data}})//更改状态
            }
        },
    }
};

2.3、最后创建models文件夹/index.js(集中管理所有模块,最后抛出在store/index.js中接收)

import home from './home'//引入home模块
import login from './login'//引入login模块
const models = [home, login]

export default models //暴露所有模块  详见标题1

3、正常在脚手架入口文件index.js中引入store,并注入组件

import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import store from './store'
import App from './App'

const container = document.getElementById('root')

ReactDOM.render(
  <Provider store = {store} > 
    <App />
  </Provider>,
  container
)

4、在home组件中应用store(开发用的是类组件所以借助了connect高阶组件,函数组件的话还是那两hooks)

import React, { Component } from 'react';
import { connect } from 'react-redux'//引入connect

class Home extends Component {
    componentDidMount() {//在组件挂载完毕的钩子中调用redux 的异步方法
        this.props.asyncGetList()
    }

    render() {
        const {referList} = this.props.state//解构
        return (
            <div className="home-container">
               {referList.length > 0 && referList.map(item => {
                            return (
                                <dl key={item.id}>
                                    <dt>
                                        <img src={item.referimg} />
                                    </dt>
                                    <dd>
                                        <b>
                                            {item.title}
                                        </b>
                                        <p>
                                            <span>{item.username}</span>
                                            <span>{item.createtime}</span>
                                        </p>
                                    </dd>
                                </dl>
                            )
                        })}
            </div>
        )
    }
}

export default connect(//映射状态和方法
    (state) => ({state: state['home']}),
    {
        asyncGetList: () => ({type: 'home/asyncGetList'})
    }
)(Home);

4.1、在login组件中应用store
(这段代码有些地方和本文初衷有些相背,重点看login和getCode的代码即可,主要还是看dva如何实现redux和组件间如何交互的。借助了antd-mobile组件库实现的登录功能,感兴趣的可以安装一下
npm i antd-mobile

import React, { Component } from 'react'
import { Form, Button, Input, Dialog } from 'antd-mobile'
import { EyeInvisibleOutline, EyeOutline } from 'antd-mobile-icons'
import { connect } from 'react-redux'

class index extends Component<any> {
    state = {//控制弹框的显示隐藏的状态
        visible: false,
    }

    getCode() {//获取验证码的方法 *重要
        const { getCode } = this.props
        getCode()
    }

    selfMotionLogin() { //自动登录
        const { token } = this.props.state['login']
        if (token) {
            this.props.history.push('/layout/home')
        }
    }

    login(values) { //登录方法 *重要
        const { login } = this.props
        login(values)
    }

    shouldComponentUpdate(nextProps) {
        if (nextProps.state['login'].token) {
            this.props.history.push('/layout/home')
        }
        return true
    }

    componentWillMount() {
        this.selfMotionLogin()
    }

    render() {
        const { visible } = this.state
        return (
            <div className="login-container">
                <Form
                    layout='horizontal'
                    name='form'
                    onFinish={(e) => this.login(e)}
                    footer={
                        <Button block type='submit' color='primary' size='large'>
                            登陆
                        </Button>
                    }>
                    <Form.Item label='用户名' name='username'>
                        <Input placeholder='请输入用户名' clearable />
                    </Form.Item>
                    <Form.Item
                        label='密码'
                        name='password'
                        extra={
                            <div className='eye'>
                                {!visible ? (
                                    <EyeInvisibleOutline onClick={() => this.setState({ visible: true })} />
                                ) : (
                                    <EyeOutline onClick={() => this.setState({ visible: false })} />
                                )}
                            </div>
                        }
                    >
                        <Input
                            placeholder='请输入密码'
                            clearable
                            type={visible ? 'text' : 'password'}
                        />
                    </Form.Item>
                    <Form.Item
                        label='短信验证码'
                        name='code'
                        extra={
                            <div className='horizontal'>
                                <a onClick={() => this.getCode()}>发送验证码</a>
                            </div>
                        }
                    >
                        <Input placeholder='请输入验证码' clearable />
                    </Form.Item>
                </Form>
                {/*借助dva-loading实现的登陆时加载功能*/}
                <Dialog
                    visible={this.props.state.loading.effects['login/login']}
                    content='~~~~~ loading ~~~~~'
                    closeOnAction
                />
            </div>
        )
    }
}

export default connect(//映射状态和方法
    (state) => ({ state: state }),
    {
        login: (payload) => ({ type: 'login/login', data: payload }),//登陆
        getCode: () => ({ type: 'login/getCodes' })//获取验证码
    }
)(index)

总结:搭建的过程可能还是些许繁琐,但是也可以看到用的时候还是很惬意的,和vue的vuex对比也是不遑多让了。 对了,最后介绍一下dva吧:它首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。(来自dva官网,详情见dva官网) dva是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易