dva/docs/GettingStarted.md

進入目錄安裝依賴:css

npm i 或者 yarn install
開發:html

npm run dev node

npm install 太慢,試試yarn吧。建議用npm install yarn -g進行安裝。react

 

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;
https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/04-%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%B3%95.md#container-component
 
 
 
 
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
 

 

 

5、Generator函數的概念
Generator 函數是協程在 ES6 的實現,最大特色就是能夠交出函數的執行權(即暫停執行)。webpack

function* gen(x){
var y = yield x + 2;
return y;
}
上面代碼就是一個 Generator 函數。它不一樣於普通函數,是能夠暫停執行的,因此函數名以前要加星號,以示區別。
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用 yield 語句註明。Generator 函數的執行方法以下。git

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 函數是否執行完畢,便是否還有下一個階段。github

http://www.ruanyifeng.com/blog/2015/04/generator.htmlweb

 

變量聲明

const 和 let

不要用 var,而是用 const 和 let,分別表示常量和變量。不一樣於 var 的函數做用域,const 和 let 都是塊級做用域。shell

const DELAY = 1000; let count = 0; count = count + 1;

箭頭函數

函數的快捷寫法,不須要經過 function 關鍵字建立函數,而且還能夠省略 return 關鍵字。npm

同時,箭頭函數還會繼承當前上下文的 this 關鍵字。

好比:

[1, 2, 3].map(x => x + 1); // [2, 3, 4]

等同於:

[1, 2, 3].map((function(x) { return x + 1; }).bind(this));

模塊的 Import 和 Export

import 用於引入模塊,export 用於導出模塊。

好比:

// 引入所有 import dva from 'dva'; // 引入部分 import { connect } from 'dva'; import { Link, Route } from 'dva/router'; // 引入所有並做爲 github 對象 import * as github from './services/github'; // 導出默認 export default App; // 部分導出,需 import { App } from './file'; 引入 export class App extend Component {};

對象字面量改進

這是析構的反向操做,用於從新組織一個 Object 。

const name = 'duoduo'; const age = 8; const user = { name, age }; // { name: 'duoduo', age: 8 }

定義對象方法時,還能夠省去 function 關鍵字。

app.model({ reducers: { add() {} // 等同於 add: function() {} }, effects: { *addRemote() {} // 等同於 addRemote: function*() {} }, });

Spread Operator

Spread Operator 即 3 個點 ...,有幾種不一樣的使用方法。

可用於組裝數組。

const todos = ['Learn dva']; [...todos, 'Learn antd']; // ['Learn dva', 'Learn antd']

也可用於獲取數組的部分項。

const arr = ['a', 'b', 'c']; const [first, ...rest] = arr; rest; // ['b', 'c'] // With ignore const [first, , ...rest] = arr; rest; // ['c']

還可收集函數參數爲數組。

function directions(first, ...rest) { console.log(rest); } directions('a', 'b', 'c'); // ['b', 'c'];

代替 apply。

function foo(x, y, z) {} const args = [1,2,3]; // 下面兩句效果相同 foo.apply(null, args); foo(...args);

區分 apply和call就一句話:

foo.call(this,arg1,arg2,arg3)==foo.apply(thsi,arguments)==this.foo(arg1,arg2,arg3);
二者區別

call 和 apply都屬於Function.prototype的一個方法,它是Javascript引擎內在實現的,
由於屬於Function.prototype對象的實例,也就是每一個方法都有call,apply屬性,這兩個方法很容易混淆,
由於他們的做用同樣,只是使用方式不一樣(傳遞的參數不一樣)。
不一樣點分析

咱們針對上面的foo.call(this,arg1,arg2,arg3)展開分析:<br/>
foo 是一個方法,this是方法執行時上下文相關對象,永遠指向當前代碼所處在的對象中。arg1,arg2,arg3是傳給foo方法的參數。
    function A(){
        var message="a";
        return{
            getMessage:function(){return this.message}
        }
    }
    function B(){
        var message="b";
        return{
            setMessage:function(message){this.message=message}
        }
    }
    var a=new A();
    var b=new B();
    b.setMessage.call(a,"a的消息");
    alert(a.getMessage()); //這裏將輸出「a的消息」
這就是動態語言Javascript call的威力所在,簡直是無中生有,對象的方法能夠任意指派,而對象自己是沒有這個方法的。注意,指派通俗地講就是把方法借給另外一個對象調用,原理上時方法執行時上下文的對象改變了。
因此,b.setMessage.call(a,"a 的消息");就等於用a作執行時上下文對象調用b對象的setMessage()方法,而這個過程與b一點關係都沒有。做用等效於a.setMessage()

下面說一下apply的使用場景

    function print(a,b,c,d){
        console.log(a+b+c+d);
    }
    function example(a,b,c,d){
        //用call方式借用print,參數顯式打散傳遞
        print.call(this,a,b,c,d);
        //apply方式借用print,參數做爲一個數組傳遞
        print.apply(this,arguments);
        //或者這樣寫
        print.apply(this,[a,b,c,d]);
    }
    example('我','不','開','心');
從上面的例子咱們發現,call和apply方法除了第一個參數相同,call方法的其餘參數一次傳遞給借用的方法作參數,而apply就兩個參數,第二個參數是借用方法的參數組成的數組。 
總結一下,當參數明確時可用call,當參數不明確時用apply並結合arguments
https://github.com/Jafeney/myBlog/blob/master/JavaScript%E6%A0%B8%E5%BF%83%E2%80%94%E2%80%94call%E5%92%8Capply%E7%9A%84%E5%8C%BA%E5%88%AB.md

對於 Object 而言,用於組合成新的 Object 。(ES2017 stage-2 proposal)

const foo = { a: 1, b: 2, }; const bar = { b: 3, c: 2, }; const d = 4; const ret = { ...foo, ...bar, d }; // { a:1, b:3, c:2, d:4 }

此外,在 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 架構的項目中,咱們推薦的目錄基本結構爲:

.
├── /mock/           # 數據mock的接口文件 ├── /src/ # 項目源碼目錄 │ ├── /components/ # 項目組件 │ ├── /routes/ # 路由組件(頁面維度) │ ├── /models/ # 數據模型 │ ├── /services/ # 數據接口 │ ├── /utils/ # 工具函數 │ ├── route.js # 路由配置 │ ├── index.js # 入口文件 │ ├── index.less │ └── index.html ├── package.json # 項目信息 └── proxy.config.js # 數據mock配置

你們能夠發現,dva 將項目中全部可能出現的功能性都映射到了對應的目錄當中,而且對整個項目的功能從目錄上也有了清晰的體現,因此咱們建議你的項目也按照這個目錄來組織文件,若是你是用的是 dva-cli 工具生成的 dva 的腳手架模板,那麼會幫你按照這樣目錄生成好。

https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/02-%E5%88%92%E5%88%86%E7%BB%93%E6%9E%84.md

 

 

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.

  1. How to create app?
  2. How to organize code after created app?
  3. How to build, deploy and publish after development?

And somethings about code organization.

  1. How to write Component?
  2. How to write CSS?
  3. How to write Model?
  4. How to connect Model and Component?
  5. How to update State after user interaction?
  6. How to handle async logic?
  7. How to config router?

And.

  1. If I want to use localStorage to save Highest Record, what to do?
  2. 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 initnew.

$ npm install -g dva-cli

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.

$ dva new myapp --demo

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.

$ cd myapp
$ npm start

After a few seconds, you will get these outputs:

          proxy: listened on 8989
     livereload: listening on 35729
📦  173/173 build modules
webpack: bundle build is now finished.

(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.

  1. design models
  2. design components
  3. connect models and components

With this task, we define model in this:

app.model({ namespace: 'count', state: { record : 0, current: 0, }, });

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.

import styles from './index.less'; const CountApp = ({count, dispatch}) => { return ( <div className={styles.normal}> <div className={styles.record}>Highest Record: {count.record}</div> <div className={styles.current}>{count.current}</div> <div className={styles.button}> <button onClick={() => { dispatch({type: 'count/add'}); }}>+</button> </div> </div> ); };

Notice:

  1. import styles from './index.less';, and then use styles.xxx to define css classname is the solution of css-modules
  2. passes in two props,count and dispatchcount is the state in model, bind with connect. And dispatch is used to trigger an action
  3. dispatch({type: 'count/add'}) means trigger an action {type: 'count/add'}. View Actions@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.

(state, action) => newState

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.

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
+ reducers: { + add(state) { + const newCurrent = state.current + 1; + return { ...state, + record: newCurrent > state.record ? newCurrent : state.record, + current: newCurrent, + }; + }, + minus(state) { + return { ...state, current: state.current - 1}; + }, + }, });

Notice:

  1. Confused with ... operator? It's used for extend Object, similar to Object.extend
  2. add(state) {} is equal to add: 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 .

function mapStateToProps(state) { return { count: state.count }; } const HomePage = connect(mapStateToProps)(CountApp);

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.

app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> );

Notice:

  1. 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:

.normal {
  width: 200px; margin: 100px auto; padding: 20px; border: 1px solid #ccc; box-shadow: 0 0 20px #ccc; } .record { border-bottom: 1px solid #ccc; padding-bottom: 8px; color: #ccc; } .current { text-align: center; font-size: 40px; padding: 40px 0; } .button { text-align: center; button { width: 100px; height: 40px; background: #aaa; color: #fff; } }

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.

app.model({
  namespace: 'count',
+ effects: { + *add(action, { call, put }) { + yield call(delay, 1000); + yield put({ type: 'minus' }); + }, + }, ... +function delay(timeout){ + return new Promise(resolve => { + setTimeout(resolve, timeout); + }); +}

Notice:

  1. *add() {} is equal to add: function*(){}
  2. call and put are effect commands from redux-saga. call is for async logic, and put is for dispatching actions. Besides, there are commands like selecttakeforkcancel, and so on. View more on redux-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.

+import key from 'keymaster'; ... app.model({ namespace: 'count', + subscriptions: { + keyboardWatcher({ dispatch }) { + key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); + }, + }, });

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:

use npm: tnpm
Installing `keymaster`... [keymaster@*] installed at node_modules/.npminstall/keymaster/1.6.2/keymaster (1 packages, use 745ms, speed 24.06kB/s, json 2.98kB, tarball 15.08kB) All packages installed (1 packages installed from npm registry, use 755ms, speed 23.93kB/s, json 1(2.98kB), tarball 15.08kB) 📦 2/2 build modules webpack: bundle build is now finished.

All Code Together

index.js

import dva, { connect } from 'dva'; import { Router, Route } from 'dva/router'; import React from 'react'; import styles from './index.less'; import key from 'keymaster'; const app = dva(); app.model({ namespace: 'count', state: { record: 0, current: 0, }, reducers: { add(state) { const newCurrent = state.current + 1; return { ...state, record: newCurrent > state.record ? newCurrent : state.record, current: newCurrent, }; }, minus(state) { return { ...state, current: state.current - 1}; }, }, effects: { *add(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'minus' }); }, }, subscriptions: { keyboardWatcher({ dispatch }) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, }, }); const CountApp = ({count, dispatch}) => { return ( <div className={styles.normal}> <div className={styles.record}>Highest Record: {count.record}</div> <div className={styles.current}>{count.current}</div> <div className={styles.button}> <button onClick={() => { dispatch({type: 'count/add'}); }}>+</button> </div> </div> ); }; function mapStateToProps(state) { return { count: state.count }; } const HomePage = connect(mapStateToProps)(CountApp); app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> ); app.start('#root'); // --------- // Helpers function delay(timeout){ return new Promise(resolve => { setTimeout(resolve, timeout); }); }

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:

$ npm run build

Output.

> @ build /private/tmp/dva-quickstart
> atool-build Child Time: 6891ms Asset Size Chunks Chunk Names common.js 1.18 kB 0 [emitted] common index.js 281 kB 1, 0 [emitted] index index.css 353 bytes 1, 0 [emitted] index

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 modelrouterreducerseffects and subscriptions ?

Next, you can view dva official library for more information.

https://github.com/dvajs/dva/blob/master/docs/GettingStarted.md

本站公眾號
   歡迎關注本站公眾號,獲取更多信息