models
dva 是 : React+Redux+webpack+babel+es6,这是一个极具生产力的组合
源自flux架构的单向流动使得应用的逻辑和数据流动变得可控,当应用逻辑变得复杂的时候,其优势越加明显,开发越加高效
但是在开发的时候出现了一些问题,比如说概念太多,文件太松散等等问题
以及为了解决异步交互问题,会引用一些中间件来处理store,比较常见的是用thunk中间件+asysc或者用saga等等
models:
namespace: key值 对应唯一的state
不同的state可以对应不同组件的状态管理
这样的话 是便于按需加载管理状态的 dynamic 就可以让每个路由按需加载对应的state
dynamic 是接收三个参数的
第一个参数:是实例app 可以
RouterConfig 中结构出来的
第二个参数: 就是model状态了, 其实看看出是以函数形式返回一个数组,里面元素就是状态model 说明可以有多个状态的
第三个参数: 就是对应的组件了
import
React
from
'react'
;
import
{
Router
,
Route
,
Switch
,
Redirect
,
routerRedux
}
from
'dva/router'
;
import
Home
from
'./routes/Index/IndexPage'
;
// import app from './index'
import
List
from
"./routes/List/List.js"
;
import
Login
from
"./components/Login.js"
;
import
dynamic
from
'dva/dynamic'
//关键作用 用于每个路由模块的 按需加载 第一个参数 app : 是 需要挂载router的app实例 第二个参数是model : reducer仓库 第三个参数是component : 组件
const
{
ConnectedRouter
}=
routerRedux
//将histrory 分发到所有的组件上
const
routeArr
= [
{
path:
'/Home/List'
,
component
:
()
=>
List
,
//必须是函数 返回组件
models
:
()
=>
[
import
(
'./models/products'
)],
//必须是函数 返回一个数组
}, {
path:
'/Login'
,
component
:
()
=>
Login
,
models
:
()
=>
[
import
(
'./models/Login'
)],
}
]
function
RouterConfig
({
history
,
app
}) {
return
(
<
ConnectedRouter
history
=
{
history
}
>
<
Home
>
<
Router
history
=
{
history
}
>
<
Switch
>
<
Route
path
=
"/"
exact
render
=
{
()
=>
<
Redirect
to
=
"/Login"
/>
}
/>
{
routeArr
.
map
((
item
,
key
)
=>
{
return
<
Route
key
=
{
key
}
exact
path
=
{
item
.
path
}
component
=
{
dynamic
({
//保证路由的唯一性 exact key
app
,
model:
item
.
models
,
component:
item
.
component
,
})
}
/>
})
}
</
Switch
>
</
Router
>
</
Home
>
</
ConnectedRouter
>
);
}
export
default
RouterConfig
;
reducers : 其实 就是 对应reducer 同步改变 原state 只能在这里面改变state的状态值
和之前redux中的reducer是一样的
其实是省略了switch对于action.type的判断
以key的方式去对应不同方法对state的更新
reducers:
{
save
(
state
, {
payload
}) {
//action 的type 属性 对应的就是 方法名 {payload} 解构 action
return
{ ...
state
, ...
payload
}
}
},
也是接收了两个参数 原state 和一个action
effects : 异步操作 例如请求数据等, 其实就是简化了 saga 的使用
第一个参数是acion 第二个是 saga方法对象 里面可以结构出put call方法
effects:
{
*
fetchUser
(
action
, {
call
,
put
}) {
const
{
username
,
password
} =
action
.
values
const
json
=
yield
call
(
fetchUser
,
'http://localhost:8090/api/checkUser'
, {
//这个fetchUser 是引进来的方法 参数是对象的方式
method:
'POST'
,
headers:
{
'Content-Type'
:
'application/x-www-form-urlencoded'
,
//设置请求头
htoken:
localStorage
.
getItem
(
'htoken'
) ||
''
,
//
hlogintime:
localStorage
.
getItem
(
'hlogintime'
) ||
0
,
},
body:
`busername=
${
username
}
&password=
${
password
}
`
})
if
(
json
.
data
.
code
) {
localStorage
.
setItem
(
'htoken'
,
json
.
data
.
token
)
localStorage
.
setItem
(
'hlogintime'
,
json
.
data
.
loginTime
)
yield
put
({
//派发 action 让reducers 接收 存储 在model 里面 type 属性不需要加 Login/ 在组件中如果你dispatch派发action 需要加Login/
type:
'save'
,
payload:
json
.
data
})
}
return
Promise
.
resolve
(
json
.
data
)
//返回 可以在dispatch后.hen 二次操作 比如跳转路由 还有从生命周期中 判断接收到的新参数如何进行跳转路由
}
},
subscriptions :在 dom ready后执行的
如何在组件中派发 及取到state
props上面就有dispatch的 因为 你已经dynamic 给她绑定了state
所有可以派发事件
this
.
props
.
dispatch
({
type:
'Login/fetchUser'
, //值得注意的是 因为命名空间的不同,所有我们需要加上前缀
values
}).
then
((
res
)
=>
{ // .then的调用方式 是因为在effect中,最后我们返回了promise,这样我们就可以执行二次才做,例如跳转路由等
if
(
res
.
code
) {
// 我可以在fetchUser 后返回promise.resolve 然后 成功可以跳转路由
this
.
props
.
history
.
push
(
'/Home/List'
)
}
})
取到state值,还是老方法 mapStateToprops
通过connect()()
function
mapStateToProps
(
state
) {
return
{
Login:
state
.
Login
};
}
export
default
withRouter
(
connect
(
mapStateToProps
)(
WrappedNormalLoginForm
))
withRouter是用来练习routers的为props挂载router信息
example: 完整的model
import
{
fetchUser
}
from
'../services/Login'
// dva 给你封装好的 请求方式
// 这是一个登录的model 存储到了localStorage中
export
default
{
namespace:
'Login'
,
//命名空间
state:
{
//初始化state
id:
1
,
},
reducers:
{
//以key/value格式定义reducer,用于处理同步操作,唯一可以修改state的地方,由action触发
//格式为{state,action} => newState 或 [(state,action)=>newState,enhancer]
save
(
state
, {
payload
}) {
//action 的type 属性 对应的就是 方法名 {payload} 解构 action
return
{ ...
state
, ...
payload
}
}
},
//https://www.jianshu.com/p/69f13e9123d9
effects:
{
//用于处理异步操作和业务逻辑,不直接修改state,由action触发,可以触发actiob,可以与服务器交互,可以获取全局的state的数据等
//*(action,effects) => void 或 [*(action,effects) = >void,{type}]
//type类型 : takeEvery takeLatest trottle watcher
*
fetchUser
(
action
, {
call
,
put
}) {
const
{
username
,
password
} =
action
.
values
const
json
=
yield
call
(
fetchUser
,
'http://localhost:8090/api/checkUser'
, {
//这个fetchUser 是引进来的方法 参数是对象的方式
method:
'POST'
,
headers:
{
'Content-Type'
:
'application/x-www-form-urlencoded'
,
//设置请求头
htoken:
localStorage
.
getItem
(
'htoken'
) ||
''
,
//
hlogintime:
localStorage
.
getItem
(
'hlogintime'
) ||
0
,
},
body:
`busername=
${
username
}
&password=
${
password
}
`
})
if
(
json
.
data
.
code
) {
localStorage
.
setItem
(
'htoken'
,
json
.
data
.
token
)
localStorage
.
setItem
(
'hlogintime'
,
json
.
data
.
loginTime
)
yield
put
({
//派发 action 让reducers 接收 存储 在model 里面 type 属性不需要加 Login/ 在组件中如果你dispatch派发action 需要加Login/
type:
'save'
,
payload:
json
.
data
})
}
return
Promise
.
resolve
(
json
.
data
)
//返回 可以在dispatch后.hen 二次操作 比如跳转路由 还有从生命周期中 判断接收到的新参数如何进行跳转路由
}
},
//定义subscription,subscription是订阅,用于订阅一个数据源,然后根据需要dispatch相应,action
//在app.start() 时被执行,数据源可以是当前的时间,服务器的websocket连接,keyboard输入,Geolocation变化,history路由变化等
//({dispatch,history},done) => unlisenFunction
//注意:如果要使用app.unmodel() subscription 必须返回unlisten方法,用来取消数据订阅
subscriptions:
{},
};
Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等