dva: dva 首先是一个基于redux和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
运行npx create-react-app dva_project
,
创建项目完成后 弹出配置npm run eject
安装dva
cnpm install dva --save
在views文件夹下, 新建一个Page1.jsx文件组件, 用来显示页面
里面代码:
import React, {
Component } from 'react';
class Page1 extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
page1
</div>
);
}
}
export default Page1;
在src新建model文件夹, 用来存放数据state
在model文件夹下新建 global.js 用来存放全局的state
, 这个全局的state
每个组件都可以用到
const initState = {
text: "我是全局的state" }
export default {
namespace: "global",
state: initState,
subscriptions : {
},
effects: {
},
reducers: {
},
}
同时新建一个page1.js 用来存放Page1组件的state
const initState = {
count: 1 }
export default {
namespace: 'page1',
state: initState,
subscriptions: {
},
effects: {
},
reducers: {
},
}
在一个model的文件中:
namespace
:model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 .
的方式创建多层命名空间。
state
: 初始值,优先级低于传给 dva()
的 opts.initialState
。
reducers
: 以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state
的地方。由 action
触发。格式为 (state, action) => newState
或 [(state, action) => newState, enhancer]
。
effects
: 以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state
。由 action
触发,可以触发 action
,可以和服务器交互,可以获取全局 state
的数据等等。
格式为 *(action, effects) => void
或 [*(action, effects) => void, { type }]
。
subscriptions
: 以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start()
时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
格式为 ({ dispatch, history }, done) => unlistenFunction
。
dva.js
中内置了react-router
, 所有我们不用再自己安装react-router
了, 但是我们会用到history
库.
history
库cnpm install history --save
在index中导入路由会用到
在route文件夹下新建router1.js文件, 为了适配多人开发, 可能会有多个路由配置文件
const Page1 = () => import('../views/Page1')
let route = [{
path: '/page1',
models: () => [import('../model/page1')], // 可以导入多个
component: Page1
}]
export default route
import React from 'react';
import {
Router, Route, Switch } from 'dva/router';
import dynamic from 'dva/dynamic'
import router1 from './router1'
const allRouter = [...router1] // 多个router可以在这个地方整合
function RouterCompontent({
history, app }) {
return (
<Router history={
history}>
<Switch>
{
allRouter.map(({
path, ...dynamics }, index) => (
<Route key={
index} path={
path} exact component={
dynamic({
app, ...dynamics})} />
))
}
</Switch>
</Router>
)
}
export default RouterCompontent
实现多层路由,子路由,嵌套路由请点击
index.js
import dva from 'dva';
import './index.css'
const createHistory = require("history").createHashHistory // 这里使用的hash路由模式
// 创建应用
const app = dva({
history: createHistory() })
// 注册model, 会这样会自动导入global的model
app.model(require('./model/global').default)
// 注册路由
app.router(require('./route/index').default)
// 启动应用
app.start('#root')
如果使用history模式路由, 上面第四行要改成这样
const createHistory=require("history").createBrowserHistory // 使用history路由模式
删除没有用的文件 app.js
、app.css
等, 这个时候基本的配置工作已经完成了
运行npm start
这个时候访问 http://localhost:3000/#/page1
可以看到 页面上 会有page1
componentDidMount() {
console.log(this)
}
并没有我们要的state
数据, 别急, 接下来我们操作,
在Page1.jsx中导入:
import {
connect } from 'dva'
然后
const mapStateToProps = state => state
export default connect(mapStateToProps)(Page1)
这样写
state
中可能会有上个页面或者组件用到的state
如果想明确的注入自己的state, 可以使用结构赋值这样写(推荐使用):
const mapStateToProps = ({
global, page1 }) => ({
global, page1 })
export default connect(mapStateToProps)(Page1);
这样写的好处是, 不会有其他的state干扰, 其中global
是默认全局的model
,page1
是组件自己的model
现在, Page1.jsx
是这样
import React, {
Component } from 'react';
import {
connect } from 'dva'
class Page1 extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(this)
}
render() {
return (
<div>
page1
</div>
);
}
}
const mapStateToProps = ({
global, page1 }) => ({
global, page1 })
export default connect(mapStateToProps)(Page1);
再次打印this
可以看到
这次 this.props
中有了我们想要的globa
l和page1
中的state
我们可以直接在page1
中使用上面传入的state
了, 当然这里只能使用, 不能直接修改state
中的数据
import React, {
Component } from 'react';
import {
connect } from 'dva'
class Page1 extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(this)
}
// 按钮点击事件
btnClickHandler() {
}
render() {
const {
global, page1 } = this.props
return (
<div>
{
/* 这里就是为了展示一下全局的state, 没啥特殊含义 */}
<div>{
global.text}</div>
{
/* 我们实现点击按钮给page1.count 加10 */}
<div>{
page1.count}</div>
<button onClick={
this.btnClickHandler.bind(this)}>点击+10</button>
</div>
);
}
}
const mapStateToProps = ({
global, page1 }) => ({
global, page1 })
export default connect(mapStateToProps)(Page1);
我们要实现一个点击按钮, 页面上显示的count加10的功能, 现在页面变成了这样
点击按钮还没有反应, 别急, 我们要给model
中的page1.js
添加一些事件:
我们要先在reducers中定义一个可以修改state
的方法
countIncrease(state, {
payload }) {
return {
...state, count: state.count + payload }
}
reducers
是唯一可以修改state
的地方。由action
触发。
effects中添加
* changeCount({
payload }, {
put }) {
yield put({
type: "countIncrease", payload})
}
effects
方法中的put
方法可以直接触发reducers
中的方法, 这个在发送异步请求是会经常用到, 如果只是单纯的改变数据, 没有异步操作, 也可以使用dispatch
触发reducers
中的方法来改变state
现在model
的page1.js
变成这样
const initState = {
count: 1 }
export default {
namespace: 'page1',
state: initState,
subscriptions: {
},
effects: {
* changeCount({
payload }, {
put }) {
yield put({
type: "countIncrease", payload})
}
},
reducers: {
countIncrease(state, {
payload }) {
return {
...state, count: state.count + payload }
}
}
}
在page1.jsx
中的btnClickHandler
中写:
btnClickHandler() {
const {
dispatch } = this.props
dispatch({
type: "page1/changeCount",
payload: 10
})
}
使用
dispatch
来触发一次action
, type的值是model命名空间/空间中的方法
, 方法可以是effects
或者reducers
中定义的方法
payload
是有效载荷, 也就是调用方法要传递的数据
再次打开页面
这次再点击就能发现页面的count发生了变化!
启动项目的时候页面会报一个警告
这个是由于引入的dva中的代码抛出的警告, 但是不影响使用, 想要消除这警告, 需要修改dva包中的源码, 具体方法可以百度一下 ,但是修改的意义不大,每次重新导入包的时候, 这个警告就会再次出来!
下一篇: 结合dva.js封装axios请求