进入目录安装依赖:
npm i 或者 yarn install
开发:
npm run dev
npm install 太慢,试试yarn吧。建议用npm install yarn -g进行安装。
Container Component
Container Component 一般指的是具有监听数据行为的组件,一般来说它们的职责是绑定相关联的 model 数据,以数据容器的角色包含其它子组件,通常在项目中表现出来的类型为:Layouts、Router Components 以及普通 Containers 组件。
通常的书写形式为:
import React, { Component, PropTypes } from 'react';
// dva 的 connect 方法可以将组件和数据关联在一起
import { connect } from 'dva';
// 组件本身
const MyComponent = (props)=>{};
MyComponent.propTypes = {};
// 监听属性,建立组件和数据的映射关系
function mapStateToProps(state) {
return {...state.data};
}
// 关联 model
export default connect(mapStateToProps)(MyComponent);
Presentational Component
Presentational Component 的名称已经说明了它的职责,展示形组件,一般也称作:Dumb Component,它不会关联订阅 model 上的数据,而所需数据的传递则是通过 props 传递到组件内部。
通常的书写形式:
import React, { Component, PropTypes } from 'react';
// 组件本身
// 所需要的数据通过 Container Component 通过 props 传递下来
const MyComponent = (props)=>{}
MyComponent.propTypes = {};
// 并不会监听数据
export default MyComponent;
Route Components
Route Components 是指 ./src/routes/ 目录下的文件,他们是 ./src/router.js 里匹配的 Component。
通过 connect 绑定数据
比如:
import { connect } from 'dva';
function App() {}
function mapStateToProps(state, ownProps) {
return {
users: state.users,
};
}
export default connect(mapStateToProps)(App);
然后在 App 里就有了 dispatch 和 users 两个属性。
Injected Props (e.g. location)
Route Component 会有额外的 props 用以获取路由信息。
location
params
children
五、Generator函数的概念
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
function* gen(x){
var y = yield x + 2;
return y;
}
上面代码就是一个 Generator 函数。它不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。Generator 函数的执行方法如下。
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 2 为止。
换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
http://www.ruanyifeng.com/blog/2015/04/generator.html
变量声明
const 和 let
不要用 var
,而是用 const
和 let
,分别表示常量和变量。不同于 var
的函数作用域,const
和 let
都是块级作用域。
箭头函数
函数的快捷写法,不需要通过 function
关键字创建函数,并且还可以省略 return
关键字。
同时,箭头函数还会继承当前上下文的 this
关键字。
比如:
等同于:
模块的 Import 和 Export
import
用于引入模块,export
用于导出模块。
比如:
对象字面量改进
这是析构的反向操作,用于重新组织一个 Object 。
定义对象方法时,还可以省去 function
关键字。
Spread Operator
Spread Operator 即 3 个点 ...
,有几种不同的使用方法。
可用于组装数组。
也可用于获取数组的部分项。
还可收集函数参数为数组。
代替 apply。
对于 Object 而言,用于组合成新的 Object 。(ES2017 stage-2 proposal)
此外,在 JSX 中 Spread Operator 还可用于扩展 props,详见 Spread Attributes。
定义全局 CSS
CSS Modules 默认是局部作用域的,想要声明一个全局规则,可用 :global 语法。
比如:
.title {
color: red;
}
:global(.title) {
color: green;
}
然后在引用的时候:
<App className={styles.title} /> // red
<App className="title" /> // green
classnames Package
在一些复杂的场景中,一个元素可能对应多个 className,而每个 className 又基于一些条件来决定是否出现。这时,classnames 这个库就非常有用。
import classnames from 'classnames';
const App = (props) => {
const cls = classnames({
btn: true,
btnLarge: props.type === 'submit',
btnSmall: props.type === 'edit',
});
return <div className={ cls } />;
}
这样,传入不同的 type 给 App 组件,就会返回不同的 className 组合:
<App type="submit" /> // btn btnLarge
<App type="edit" /> // btn btnSmall
https://github.com/dvajs/dva-knowledgemap
结构划分
很多同学在搭建项目的时候,往往忽略项目结构的划分,实际上合理的项目划分往往能够提供规范的项目搭建思路。 在 dva 架构的项目中,我们推荐的目录基本结构为:
大家可以发现,dva 将项目中所有可能出现的功能性都映射到了对应的目录当中,并且对整个项目的功能从目录上也有了清晰的体现,所以我们建议你的项目也按照这个目录来组织文件,如果你是用的是 dva-cli 工具生成的 dva 的脚手架模板,那么会帮你按照这样目录生成好。
Getting Started
This article will lead you to create dva app quickly, and learn all new concepts.
Final App.
This app is used to test click speed, by collecting click count within 1 second.
Some questions you may ask.
- How to create app?
- How to organize code after created app?
- How to build, deploy and publish after development?
And somethings about code organization.
- How to write Component?
- How to write CSS?
- How to write Model?
- How to connect Model and Component?
- How to update State after user interaction?
- How to handle async logic?
- How to config router?
And.
- If I want to use localStorage to save Highest Record, what to do?
- If we want to support keyboard click rate test, what to do?
We can takes these questions to read this article. But don't worry, all the code we need is about 70 lines.
Install dva-cli
dva-cli is the cli tool for dva, include init
, new
.
After installed, you can check version with dva -v
, and view help info with dva -h
.
Create new App
After installed dva-cli, we can create a new app with it, called myapp
.
Notice: --demo
option is only used for creating demo level app. If you want to create normal project, don't add this option.
cd
myapp, and start it.
After a few seconds, you will get these outputs:
(Press Ctrl-C
if you want to close server)
Open http://localhost:8989/ in browser. If success, you will see a page with "Hello Dva".
Define models
When get the task, you should not write code immediately. But recommend to do state design in god mode
.
- design models
- design components
- connect models and components
With this task, we define model in this:
namespace
is the key where model state is in global state. state
is the default data for model. Then record presents highest record
,and current
presents current click speed.
Write components
After designed model, we start to write component. Recommend to organize Component with stateless functions. Because we don't need state almost in dva architecture.
Notice:
-
import styles from './index.less';
, and then usestyles.xxx
to define css classname is the solution ofcss-modules - passes in two props,
count
anddispatch
.count
is the state in model, bind with connect. Anddispatch
is used to trigger an action -
dispatch({type: 'count/add'})
means trigger an action{type: 'count/add'}
. ViewActions@redux.js.org on what's an action.
Update state
reducer
is the only one which can update state, this make our app stable, all data modification is traceable. reducer
is pure function, accept arguments state
and action
, return new state
.
We need two reducers, add
and minus
. Please notice add
will only be recorded if it's highest.
Notice:
add
and minus
don't need to add namespace prefix in count
model. But if outside the model, action must prefix namespace separated with /
. e.g. count/add
.
Notice:
- Confused with
...
operator? It's used for extend Object, similar toObject.extend
-
add(state) {}
is equal toadd: function(state) {}
Bind Data
Remember
count
and dispatch
props used in the Component before? Where are them come from?
After define Model and Component, we need to connect them together. After connect, Component can use the data from Model, and Model can receive actions dispatched from Component.
In this task, we only need to bind count
.
Notice: connect
is from react-redux。
Define Router
Which Component should be rendered after receiving a url? It's defined by router.
This app has only one page, so we don't need to modify the router part.
Notice:
-
history
is default hashHistory with_k
params. It can be changed to browserHistory, or remove_k
params with extra configuration.
Refresh page in browser, if success, you will see page below.
Add StyleSheet
We define stylesheet in css modules
, which doesn't have many differences from normal css. Because we have already hooked className in Component, at this moment, we only need to replace index.less
with follow content:
Result.
Async Logic
Prior to this, all of our operations are synchronous. When clicking on the + button, the value is incremented by 1.
Now we have to dealing with async logic. dva process side effect( async logic ) with effects on model, which is executed based on redux-saga, with generator syntax.
In this app, when user clicked the + button, value will plus 1, and trigger a side effect, that is, minus 1 after 1 second.
Notice:
-
*add() {}
is equal toadd: function*(){}
-
call
andput
are effect commands from redux-saga.call
is for async logic, andput
is for dispatching actions. Besides, there are commands likeselect
,take
,fork
,cancel
, and so on. View more onredux-saga documatation
Refresh you browser, if success, it should have all the effects of beginning gif.
Subscribe Keyboard Event
After implemented mouse click speed test, how to implement keyboard click speed test?
There is a concept called Subscription
from dva, which is from elm 0.17.
Subscription is used for subscribe a data source, then dispatch action if needed. The data source could be current time, websocket connection from server, keyboard input, geolocation change, history router change, and so on.
Subscription is in model.
Here, we don't need to install keymaster
dependency manually. When we write import key from 'keymaster';
and save, dva-cli will install keymaster
and save to package.json
. Output like this:
All Code Together
index.js
Build
Now that we've written our application and verified that it works in development, it's time to get it ready to deploy to our users. To do so, run the following command:
Output.
After build success, you can find compiled files in dist
directory.
What's Next
After complete this app, do you have answer of all the questions in the beginning? Do you understand this concepts in dva, like model
, router
, reducers
, effects
and subscriptions
?
Next, you can view dva official library for more information.
https://github.com/dvajs/dva/blob/master/docs/GettingStarted.md