Full Code of dvajs/dva for AI

master b60e6c0baf59 cached
170 files
275.7 KB
86.6k tokens
264 symbols
1 requests
Download .txt
Showing preview only (312K chars total). Download the full file or copy to clipboard to get everything.
Repository: dvajs/dva
Branch: master
Commit: b60e6c0baf59
Files: 170
Total size: 275.7 KB

Directory structure:
gitextract_61ggrlan/

├── .circleci/
│   └── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── bug_report_cn.md
│   │   ├── feature_request.md
│   │   └── rfc_cn.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── stale.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── README_zh-CN.md
├── docs/
│   ├── .vuepress/
│   │   ├── config.js
│   │   └── override.styl
│   ├── API.md
│   ├── Concepts.md
│   ├── GettingStarted.md
│   ├── README.md
│   ├── api/
│   │   └── README.md
│   ├── guide/
│   │   ├── README.md
│   │   ├── concepts.md
│   │   ├── develop-complex-spa.md
│   │   ├── examples-and-boilerplates.md
│   │   ├── fig-show.md
│   │   ├── getting-started.md
│   │   ├── introduce-class.md
│   │   └── source-code-explore.md
│   └── knowledgemap/
│       └── README.md
├── examples/
│   ├── func-test/
│   │   ├── .eslintrc
│   │   ├── .roadhogrc
│   │   ├── .roadhogrc.mock.js
│   │   ├── package.json
│   │   └── src/
│   │       ├── components/
│   │       │   └── Example.js
│   │       ├── index.css
│   │       ├── index.ejs
│   │       ├── index.js
│   │       ├── models/
│   │       │   └── example.js
│   │       ├── router.js
│   │       ├── routes/
│   │       │   ├── IndexPage.css
│   │       │   └── IndexPage.js
│   │       ├── services/
│   │       │   └── example.js
│   │       └── utils/
│   │           └── request.js
│   ├── user-dashboard/
│   │   ├── .editorconfig
│   │   ├── .eslintrc
│   │   ├── .gitignore
│   │   ├── .umirc.js
│   │   ├── .webpackrc
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src/
│   │       ├── constants.js
│   │       ├── global.css
│   │       ├── layouts/
│   │       │   ├── Header.js
│   │       │   ├── index.css
│   │       │   └── index.js
│   │       ├── pages/
│   │       │   ├── index.css
│   │       │   ├── index.js
│   │       │   └── users/
│   │       │       ├── components/
│   │       │       │   └── Users/
│   │       │       │       ├── UserModal.js
│   │       │       │       ├── Users.css
│   │       │       │       └── Users.js
│   │       │       ├── models/
│   │       │       │   └── users.js
│   │       │       ├── page.css
│   │       │       ├── page.js
│   │       │       └── services/
│   │       │           └── users.js
│   │       ├── plugins/
│   │       │   └── onError.js
│   │       └── utils/
│   │           └── request.js
│   ├── with-immer/
│   │   ├── .umirc.js
│   │   ├── dva.js
│   │   ├── model.js
│   │   ├── package.json
│   │   └── pages/
│   │       └── index.js
│   ├── with-nextjs/
│   │   ├── .babelrc
│   │   ├── .eslintignore
│   │   ├── .eslintrc
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── model/
│   │   │   ├── homepage.js
│   │   │   └── index.js
│   │   ├── package.json
│   │   ├── pages/
│   │   │   ├── index.js
│   │   │   └── users.js
│   │   └── utils/
│   │       └── store.js
│   ├── with-react-router-3/
│   │   ├── .eslintrc
│   │   ├── .roadhogrc
│   │   ├── .roadhogrc.mock.js
│   │   ├── package.json
│   │   └── src/
│   │       ├── components/
│   │       │   └── Example.js
│   │       ├── index.css
│   │       ├── index.ejs
│   │       ├── index.js
│   │       ├── models/
│   │       │   └── example.js
│   │       ├── router.js
│   │       ├── routes/
│   │       │   ├── IndexPage.css
│   │       │   └── IndexPage.js
│   │       ├── services/
│   │       │   └── example.js
│   │       └── utils/
│   │           └── request.js
│   └── with-redux-undo/
│       ├── .babelrc
│       ├── .gitignore
│       ├── .npmrc
│       ├── README.md
│       ├── package.json
│       └── src/
│           ├── index.html
│           ├── index.js
│           └── models/
│               └── counter.js
├── jest.config.js
├── lerna.json
├── package.json
├── packages/
│   ├── dva/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── dynamic.d.ts
│   │   ├── dynamic.js
│   │   ├── fetch.d.ts
│   │   ├── fetch.js
│   │   ├── index.d.ts
│   │   ├── package.json
│   │   ├── router.d.ts
│   │   ├── router.js
│   │   ├── saga.js
│   │   ├── src/
│   │   │   ├── dynamic.js
│   │   │   └── index.js
│   │   ├── test/
│   │   │   ├── index.e2e.js
│   │   │   └── index.test.js
│   │   └── warnAboutDeprecatedCJSRequire.js
│   ├── dva-core/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── saga.js
│   │   ├── src/
│   │   │   ├── Plugin.js
│   │   │   ├── checkModel.js
│   │   │   ├── constants.js
│   │   │   ├── createPromiseMiddleware.js
│   │   │   ├── createStore.js
│   │   │   ├── getReducer.js
│   │   │   ├── getSaga.js
│   │   │   ├── handleActions.js
│   │   │   ├── index.js
│   │   │   ├── prefixNamespace.js
│   │   │   ├── prefixType.js
│   │   │   ├── prefixedDispatch.js
│   │   │   ├── subscription.js
│   │   │   └── utils.js
│   │   └── test/
│   │       ├── checkModel.test.js
│   │       ├── effects.test.js
│   │       ├── handleActions.test.js
│   │       ├── model.test.js
│   │       ├── optsAndHooks.test.js
│   │       ├── plugin.test.js
│   │       ├── reducers.test.js
│   │       ├── repalceModel.test.js
│   │       ├── subscriptions.test.js
│   │       └── utils.test.js
│   ├── dva-immer/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── src/
│   │   │   └── index.js
│   │   └── test/
│   │       └── index.test.js
│   └── dva-loading/
│       ├── .fatherrc.js
│       ├── README.md
│       ├── index.d.ts
│       ├── package.json
│       ├── src/
│       │   └── index.js
│       └── test/
│           ├── core.test.js
│           └── index.test.js
├── scripts/
│   └── publish.js
└── website/
    ├── .gitignore
    ├── now.json
    └── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2.1
executors:
  node:
    docker:
      - image: circleci/node:10.13-browsers
    working_directory: ~/dva

environment:
  NODE_ENV: test
  NODE_OPTIONS: --max_old_space_size=4096
  NPM_CONFIG_LOGLEVEL: error
  JOBS: max # https://gist.github.com/ralphtheninja/f7c45bdee00784b41fed

jobs:
  yarn_build:
    executor: node
    steps:
      - checkout
      - run: yarn install
      - run: yarn bootstrap
      - run: yarn build
      - run:
          command: yarn test -- --forceExit --detectOpenHandles --runInBand --maxWorkers=2
          no_output_timeout: 300m
      - run: bash <(curl -s https://codecov.io/bash)
  cnpm_build:
    executor: node
    steps:
      - checkout
      - run: sudo npm install -g cnpm
      - run: cnpm install --registry=https://registry.npmjs.org
      - run: cnpm run bootstrap -- --npm-client=cnpm
      - run: cnpm run build
      - run:
          command: npm run test -- --forceExit --detectOpenHandles --runInBand --maxWorkers=2
          no_output_timeout: 300m
      - run: bash <(curl -s https://codecov.io/bash)
workflows:
  version: 2
  build-test:
    jobs:
      - yarn_build:
          filters:
            branches:
              ignore:
                - gh-pages
                - /release\/.*/
      - cnpm_build:
          filters:
            branches:
              ignore:
                - gh-pages
                - /release\/.*/


================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab


================================================
FILE: .eslintignore
================================================
node_modules/
lib/
es/
dist/
packages/dva-example/
packages/dva-example-react-router-3/
packages/dva-example-nextjs//
packages/dva-example-user-dashboard/
packages/dva/*.js
packages/dva-react-router-3/*.js
packages/dva-no-router/*.js
scripts/


================================================
FILE: .eslintrc
================================================
{
  "parser": "babel-eslint",
  "plugins": [
    "prettier"
  ],
  "extends": [
    "airbnb",
    "prettier"
  ],
  "env": {
    "browser": true,
    "jest": true
  },
  "rules": {
    "prettier/prettier": "error",
    "jsx-a11y/href-no-hash": [
      0
    ],
    "jsx-a11y/click-events-have-key-events": [
      0
    ],
    "jsx-a11y/anchor-is-valid": [
      "error",
      {
        "components": [
          "Link"
        ],
        "specialLink": [
          "to"
        ]
      }
    ],
    "generator-star-spacing": [
      0
    ],
    "consistent-return": [
      0
    ],
    "radix": [
      1
    ],
    "react/react-in-jsx-scope": [
      0
    ],
    "react/forbid-prop-types": [
      0
    ],
    "react/jsx-filename-extension": [
      1,
      {
        "extensions": [
          ".js"
        ]
      }
    ],
    "global-require": [
      0
    ],
    "import/prefer-default-export": [
      0
    ],
    "react/jsx-no-bind": [
      0
    ],
    "react/prop-types": [
      0
    ],
    "react/prefer-stateless-function": [
      0
    ],
    "react/jsx-one-expression-per-line": [
      0
    ],
    "react/button-has-type": [
      0
    ],
    "no-else-return": [
      0
    ],
    "no-restricted-syntax": [
      0
    ],
    "import/no-extraneous-dependencies": [
      0
    ],
    "no-use-before-define": [
      0
    ],
    "jsx-a11y/no-static-element-interactions": [
      0
    ],
    "no-nested-ternary": [
      0
    ],
    "arrow-body-style": [
      0
    ],
    "import/extensions": [
      0
    ],
    "no-bitwise": [
      0
    ],
    "no-cond-assign": [
      0
    ],
    "import/no-unresolved": [
      0
    ],
    "require-yield": [
      1
    ],
    "no-param-reassign": [
      0
    ],
    "no-shadow": [
      0
    ],
    "no-underscore-dangle": [
      0
    ],
    "spaced-comment": [
      0
    ],
    "indent": [
      0
    ],
    "quotes": [
      0
    ],
    "func-names": [
      0
    ],
    "arrow-parens": [
      0
    ],
    "space-before-function-paren": [
      0
    ],
    "no-useless-escape": [
      0
    ],
    "object-curly-newline": [
      0
    ],
    "function-paren-newline": [
      0
    ],
    "class-methods-use-this": [
      0
    ],
    "no-new": [
      0
    ],
    "import/newline-after-import": [
      0
    ],
    "no-console": [
      0
    ]
  },
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true
    }
  }
}


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: 'Bug report'
about: 'Report a bug to help us improve'
title: ''
labels: ''
assignees: ''

---

## What happens?
A clear and concise description of what the bug is.

## Mini Showcase Repository(REQUIRED)

> Provide a mini GitHub repository which can reproduce the issue.
> Use `yarn create umi`, select `app`, choose `dva`, then upload to your GitHub

<!-- https://github.com/YOUR_REPOSITORY_URL -->

## How To Reproduce
**Steps to reproduce the behavior:**
1.
2.

**Expected behavior**
1.
2.

## Context

- **Dva Version**:
- **Node Version**:
- **Platform**:


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report_cn.md
================================================
---
name: '缺陷问题反馈'
about: '反馈问题以帮助我们改进'
title: ''
labels: ''
assignees: ''

---

<!--
感谢您向我们反馈问题,为了高效的解决问题,我们期望你能提供以下信息:
-->

## What happens?

<!-- 清晰的描述下遇到的问题。-->

## 最小可复现仓库

> 请使用 `yarn create umi` 创建,选择 `app`,然后选上 `dva`,并上传到你的 GitHub 仓库

<!-- https://github.com/YOUR_REPOSITORY_URL -->

## 复现步骤,错误日志以及相关配置

<!-- 请提供复现步骤,错误日志以及相关配置 -->
<!-- 可以尝试不要锁版本,重新安装依赖试试先 -->


## 相关环境信息

- **Umi 版本**:
- **Node 版本**:
- **操作系统**:


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: 'Feature request'
about: 'Suggest an idea for this project'
title: '[Feature Request] say something'
labels: ''
assignees: ''

---

## Background

A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

## Proposal

Describe the solution you'd like, better to provide some pseudo code.

## Additional context

Add any other context or screenshots about the feature request here.


================================================
FILE: .github/ISSUE_TEMPLATE/rfc_cn.md
================================================
---
name: 'RFC Proposals'
about: 'Provide a solution for this project'
title: '[RFC] say something'
labels: 'type: proposals'
assignees: ''

---

## 背景

> 描述你希望解决的问题的现状,附上相关的 issue 地址

## 思路

> 描述大概的解决思路,可以包含 API 设计和伪代码等

## 跟进

- [ ] some task
- [ ] PR URL


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests.
Contributors guide: https://github.com/dvajs/dva/blob/master/CONTRIBUTING.md

感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试。
Contributors guide: https://github.com/dvajs/dva/blob/master/CONTRIBUTING.md
-->

##### Checklist

<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [ ] `npm test` passes
- [ ] tests are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines


##### Description of change

<!-- Provide a description of the change below this comment. -->

- any feature?
- close https://github.com/dvajs/dva/ISSUE_URL


================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
  - pinned
  - security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
  This issue has been automatically marked as stale because it has not had
  recent activity. It will be closed if no further activity occurs. Thank you
  for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false


================================================
FILE: .github/workflows/ci.yml
================================================
name: Node CI

on: [push]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        node_version: [10.x, 12.x]
        os: [ubuntu-latest]
    steps:
    - uses: actions/checkout@v1
    - name: Use Node.js ${{ matrix.node_version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node_version }}
    - run: npm install
    - run: npm run bootstrap
    - run: npm run build
    - run: npm run test -- --forceExit
      env:
        CI: true
        HEADLESS: false
        PROGRESS: none
        NODE_ENV: test
        NODE_OPTIONS: --max_old_space_size=4096



================================================
FILE: .gitignore
================================================
.DS_Store
/coverage
/.changelog
/examples/**/.umi
/examples/**/.umi-production
/website/dist
/node_modules
/packages/**/node_modules
/packages/**/dist
/lerna-debug.log



================================================
FILE: .prettierrc
================================================
{
  "printWidth": 100,
  "singleQuote": true,
  "trailingComma": "all"
}


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016-present ChenCheng (sorrycc@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
English | [简体中文](./README_zh-CN.md)

# dva

[![codecov](https://codecov.io/gh/dvajs/dva/branch/master/graph/badge.svg)](https://codecov.io/gh/dvajs/dva)
[![CircleCI](https://circleci.com/gh/dvajs/dva.svg?style=svg)](https://circleci.com/gh/dvajs/dva)
[![NPM version](https://img.shields.io/npm/v/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Build Status](https://img.shields.io/travis/dvajs/dva.svg?style=flat)](https://travis-ci.org/dvajs/dva)
[![Coverage Status](https://img.shields.io/coveralls/dvajs/dva.svg?style=flat)](https://coveralls.io/r/dvajs/dva)
[![NPM downloads](http://img.shields.io/npm/dm/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Dependencies](https://david-dm.org/dvajs/dva/status.svg)](https://david-dm.org/dvajs/dva)
[![Join the chat at https://gitter.im/dvajs/Lobby](https://img.shields.io/gitter/room/dvajs/Lobby.svg?style=flat)](https://gitter.im/dvajs/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)

Lightweight front-end framework based on [redux](https://github.com/reactjs/redux), [redux-saga](https://github.com/redux-saga/redux-saga) and [react-router](https://github.com/ReactTraining/react-router). (Inspired by [elm](http://elm-lang.org/) and [choo](https://github.com/yoshuawuyts/choo))

---

## Features

* **Easy to learn, easy to use**: only 6 apis, very friendly to redux users, and **API reduce to 0 when [use with umi](https://umijs.org/guide/with-dva.html)**
* **Elm concepts**: organize models with `reducers`, `effects` and `subscriptions`
* **Support HMR**: support HMR for components, routes and models with [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr)
* **Plugin system**: e.g. we have [dva-loading](https://github.com/dvajs/dva/tree/master/packages/dva-loading) plugin to handle loading state automatically

## Demos

* [Count](https://stackblitz.com/edit/dva-example-count): Simple count example
* [User Dashboard](https://github.com/dvajs/dva/tree/master/examples/user-dashboard): User management dashboard
* [AntDesign Pro](https://github.com/ant-design/ant-design-pro):([Demo](https://preview.pro.ant.design/)),out-of-box UI solution for enterprise applications
* [HackerNews](https://github.com/dvajs/dva-hackernews):  ([Demo](https://dvajs.github.io/dva-hackernews/)),HackerNews Clone
* [antd-admin](https://github.com/zuiidea/antd-admin): ([Demo](http://antd-admin.zuiidea.com/)),A admin dashboard application demo built upon Ant Design and Dva.js
* [github-stars](https://github.com/sorrycc/github-stars): ([Demo](http://sorrycc.github.io/github-stars/#/?_k=rmj86f)),Github star management application
* [Account System](https://github.com/yvanwangl/AccountSystem.git): A small inventory management system
* [react-native-dva-starter](https://github.com/nihgwu/react-native-dva-starter): react-native example integrated dva and react-navigation

## Quick Start

See the [docs directory](./docs) for guides and API references.

## FAQ

### Why is it called dva?

> D.Va’s mech is nimble and powerful — its twin Fusion Cannons blast away with autofire at short range, and she can use its Boosters to barrel over enemies and obstacles, or deflect attacks with her projectile-dismantling Defense Matrix.

—— From [OverWatch](http://ow.blizzard.cn/heroes/dva)

<img src="https://zos.alipayobjects.com/rmsportal/psagSCVHOKQVqqNjjMdf.jpg" width="200" height="200" />

### Is it production ready?

Sure! We have 1000+ projects using dva in Alibaba.

### Does it support IE8?

No.

## Next

Some basic articles.

* The [8 Concepts](https://github.com/dvajs/dva/blob/master/docs/Concepts.md), and know how they are connected together
* [dva APIs](https://github.com/dvajs/dva/blob/master/docs/API.md)
* Checkout [dva knowledgemap](https://github.com/dvajs/dva-knowledgemap), including all the basic knowledge with ES6, React, dva
* Checkout [more FAQ](https://github.com/dvajs/dva/issues?q=is%3Aissue+is%3Aclosed+label%3Afaq)
* If your project is created by [dva-cli](https://github.com/dvajs/dva-cli), checkout how to [Configure it](https://github.com/sorrycc/roadhog#configuration)

Want more?

* 看看 dva 的前身 [React + Redux 最佳实践](https://github.com/sorrycc/blog/issues/1),知道 dva 是怎么来的
* 在 gitc 分享 dva 的 PPT :[React 应用框架在蚂蚁金服的实践](http://slides.com/sorrycc/dva)
* 如果还在用 dva@1.x,请尽快 [升级到 2.x](https://github.com/sorrycc/blog/issues/48)

## Community

| Slack Group                                                  | Github Issue                                            | 钉钉群                                                       | 微信群                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [sorrycc.slack.com](https://join.slack.com/t/sorrycc/shared_invite/enQtNTUzMTYxNDQ5MzE4LTg1NjEzYWUwNDQzMWU3YjViYjcyM2RkZDdjMzE0NzIxMTg3MzIwMDM2YjUwNTZkNDdhNTY5ZTlhYzc1Nzk2NzI) | [umijs/umi/issues](https://github.com/umijs/umi/issues) | <img src="https://gw.alipayobjects.com/zos/rmsportal/jPXcQOlGLnylGMfrKdBz.jpg" width="60" /> | <img src="https://img.alicdn.com/tfs/TB13U6aF6DpK1RjSZFrXXa78VXa-752-974.jpg" width="60" /> |

## License

[MIT](https://tldrlegal.com/license/mit-license)


================================================
FILE: README_zh-CN.md
================================================
[English](./README.md) | 简体中文

# dva

[![codecov](https://codecov.io/gh/dvajs/dva/branch/master/graph/badge.svg)](https://codecov.io/gh/dvajs/dva)
[![CircleCI](https://circleci.com/gh/dvajs/dva.svg?style=svg)](https://circleci.com/gh/dvajs/dva)
[![NPM version](https://img.shields.io/npm/v/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Build Status](https://img.shields.io/travis/dvajs/dva.svg?style=flat)](https://travis-ci.org/dvajs/dva)
[![Coverage Status](https://img.shields.io/coveralls/dvajs/dva.svg?style=flat)](https://coveralls.io/r/dvajs/dva)
[![NPM downloads](http://img.shields.io/npm/dm/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Dependencies](https://david-dm.org/dvajs/dva/status.svg)](https://david-dm.org/dvajs/dva)
[![Join the chat at https://gitter.im/dvajs/Lobby](https://img.shields.io/gitter/room/dvajs/Lobby.svg?style=flat)](https://gitter.im/dvajs/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)

基于 [redux](https://github.com/reactjs/redux)、[redux-saga](https://github.com/redux-saga/redux-saga) 和 [react-router](https://github.com/ReactTraining/react-router) 的轻量级前端框架。(Inspired by [elm](http://elm-lang.org/) and [choo](https://github.com/yoshuawuyts/choo))

---

## 特性

* **易学易用**,仅有 6 个 api,对 redux 用户尤其友好,**[配合 umi 使用](https://umijs.org/guide/with-dva.html)后更是降低为 0 API**
* **elm 概念**,通过 reducers, effects 和 subscriptions 组织 model
* **插件机制**,比如 [dva-loading](https://github.com/dvajs/dva/tree/master/packages/dva-loading) 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
* **支持 HMR**,基于 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 实现 components、routes 和 models 的 HMR

## 快速上手
请参考 [docs 目录](./docs) 获取指南和 API 参考。

## 他是怎么来的?

* [Why dva and what's dva](https://github.com/dvajs/dva/issues/1)
* [支付宝前端应用架构的发展和选择](https://www.github.com/sorrycc/blog/issues/6)

## 例子

* [Count](https://stackblitz.com/edit/dva-example-count): 简单计数器
* [User Dashboard](https://github.com/dvajs/dva/tree/master/examples/user-dashboard): 用户管理
* [AntDesign Pro](https://github.com/ant-design/ant-design-pro):([Demo](https://preview.pro.ant.design/)),开箱即用的中台前端/设计解决方案
* [HackerNews](https://github.com/dvajs/dva-hackernews):  ([Demo](https://dvajs.github.io/dva-hackernews/)),HackerNews Clone
* [antd-admin](https://github.com/zuiidea/antd-admin): ([Demo](http://antd-admin.zuiidea.com/)),基于 antd 和 dva 的后台管理应用
* [github-stars](https://github.com/sorrycc/github-stars): ([Demo](http://sorrycc.github.io/github-stars/#/?_k=rmj86f)),Github Star 管理应用
* [Account System](https://github.com/yvanwangl/AccountSystem.git): 小型库存管理系统
* [react-native-dva-starter](https://github.com/nihgwu/react-native-dva-starter): 集成了 dva 和 react-navigation 典型应用场景的 React Native 实例

## FAQ

### 命名由来?

> D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵。

—— 来自 [守望先锋](http://ow.blizzard.cn/heroes/overwatch-dva) 。

<img src="https://zos.alipayobjects.com/rmsportal/psagSCVHOKQVqqNjjMdf.jpg" width="200" height="200" />

### 是否可用于生产环境?

当然!公司内用于生产环境的项目估计已经有 1000+ 。

### 是否支持 IE8 ?

不支持。

## 下一步

以下能帮你更好地理解和使用 dva :

* 理解 dva 的 [8 个概念](./docs/Concepts.md) ,以及他们是如何串起来的
* 掌握 dva 的[所有 API](./docs/API.md)
* 查看 [dva 知识地图](./docs/knowledgemap/README.md) ,包含 ES6, React, dva 等所有基础知识
* 查看 [更多 FAQ](https://github.com/dvajs/dva/issues?q=is%3Aissue+is%3Aclosed+label%3Afaq),看看别人通常会遇到什么问题
* 如果你基于 dva-cli 创建项目,最好了解他的 [配置方式](https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md#配置)

还要了解更多?

* 看看 dva 的前身 [React + Redux 最佳实践](https://github.com/sorrycc/blog/issues/1),知道 dva 是怎么来的
* 在 gitc 分享 dva 的 PPT :[React 应用框架在蚂蚁金服的实践](http://slides.com/sorrycc/dva)
* 如果还在用 dva@1.x,请尽快 [升级到 2.x](https://github.com/sorrycc/blog/issues/48)

## 社区

| Slack Group                                                  | Github Issue                                            | 钉钉群                                                       | 微信群                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [sorrycc.slack.com](https://join.slack.com/t/sorrycc/shared_invite/enQtNTUzMTYxNDQ5MzE4LTg1NjEzYWUwNDQzMWU3YjViYjcyM2RkZDdjMzE0NzIxMTg3MzIwMDM2YjUwNTZkNDdhNTY5ZTlhYzc1Nzk2NzI) | [umijs/umi/issues](https://github.com/umijs/umi/issues) | <img src="https://gw.alipayobjects.com/zos/rmsportal/jPXcQOlGLnylGMfrKdBz.jpg" width="60" /> | <img src="https://img.alicdn.com/tfs/TB13U6aF6DpK1RjSZFrXXa78VXa-752-974.jpg" width="60" /> |

## License

[MIT](https://tldrlegal.com/license/mit-license)


================================================
FILE: docs/.vuepress/config.js
================================================
module.exports = {
  title: 'DvaJS',
  description: 'React and redux based, lightweight and elm-style framework.',
  themeConfig: {
    repo: 'dvajs/dva',
    lastUpdated: 'Last Updated',
    editLinks: true,
    editLinkText: '在 GitHub 上编辑此页',
    docsDir: 'docs',
    nav: [
      { text: '指南', link: '/guide/' },
      { text: 'API', link: '/api/' },
      { text: '知识地图', link: '/knowledgemap/' },
      { text: '发布日志', link: 'https://github.com/dvajs/dva/releases' },
    ],
    sidebar: {
      '/guide/': [
        {
          title: '指南',
          collapsable: false,
          children: [
            '',
            'getting-started',
            'examples-and-boilerplates',
            'concepts',
            'introduce-class',
          ],
        },
        {
          title: '社区',
          collapsable: false,
          children: ['fig-show', 'develop-complex-spa', 'source-code-explore'],
        },
      ],
      '/api/': [''],
      '/knowledgemap/': [''],
    },
  },
};


================================================
FILE: docs/.vuepress/override.styl
================================================
$accentColor = #fc54c3
$textColor = #2c3e50
$borderColor = #eaecef
$codeBgColor = #282c34


================================================
FILE: docs/API.md
================================================
# API

## Export Files
### dva

Default export file.

### dva/router

Export the api of [react-router@4.x](https://github.com/ReactTraining/react-router), and also export [react-router-redux](https://github.com/reactjs/react-router-redux) with the `routerRedux` key.

e.g.

```js
import { Router, Route, routerRedux } from 'dva/router';
```

### dva/fetch

Async request library, export the api of [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch). It's just for convenience, you can choose other libraries for free.

### dva/saga

Export the api of [redux-saga](https://github.com/yelouafi/redux-saga).

### dva/dynamic

Util method to load React Component and dva model dynamically.

e.g.

```js
import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
  app,
  models: () => [
    import('./models/users'),
  ],
  component: () => import('./routes/UserPage'),
});
```

`opts` include:

* app: dva instance
* models: function which return promise, and the promise return dva model
* component:function which return promise, and the promise return React Component

## dva API
### `app = dva(opts)`

Create app, and return dva instance. (Notice: dva support multiple instances.)

`opts` includes:

* `history`: Specify the history for router, default `hashHistory`
* `initialState`: Specify the initial state, default `{}`, it's priority is higher then model state

e.g. use `browserHistory`:

```js
import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});
```

Besides, for convenience, we can configure [hooks](#appusehooks) in `opts`, like this:

```js
const app = dva({
  history,
  initialState,
  onError,
  onAction,
  onStateChange,
  onReducer,
  onEffect,
  onHmr,
  extraReducers,
  extraEnhancers,
});
```

### `app.use(hooks)`

Specify hooks or register plugin. (Plugin return hooks finally.)

e.g. register [dva-loading](https://github.com/dvajs/dva-loading) plugin:

```js
import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
```

`hooks` includes:

#### `onError((err, dispatch) => {})`

Triggered when `effect` has error or `subscription` throw error with `done`. Used for managing global error.

Notice: `subscription`'s error must be throw with the send argument `done`. e.g.

```js
app.model({
  subscriptions: {
    setup({ dispatch }, done) {
      done(e);
    },
  },
});
```

If we are using antd, the most simple error handle would be like this:

```js
import { message } from 'antd';
const app = dva({
  onError(e) {
    message.error(e.message, /* duration */3);
  },
});
```

#### `onAction(fn | fn[])`

Triggered when action is dispatched. Used for register redux middleware.

e.g. use [redux-logger](https://github.com/evgenyrodionov/redux-logger) to log actions:

```js
import createLogger from 'redux-logger';
const app = dva({
  onAction: createLogger(opts),
});
```

#### `onStateChange(fn)`

Triggered when `state` changes. Used for sync `state` to localStorage or server and so on.

#### `onReducer(fn)`

Wrap reducer execute.

e.g. use [redux-undo](https://github.com/omnidan/redux-undo) to implement redo/undo:

```js
import undoable from 'redux-undo';
const app = dva({
  onReducer: reducer => {
    return (state, action) => {
      const undoOpts = {};
      const newState = undoable(reducer, undoOpts)(state, action);
      // 由于 dva 同步了 routing 数据,所以需要把这部分还原
      return { ...newState, routing: newState.present.routing };
    },
  },
});
```

#### `onEffect(fn)`

Wrap effect execute.

e.g. [dva-loading](https://github.com/dvajs/dva-loading) has implement auto loading state with this hook.

#### `onHmr(fn)`

HMR(Hot Module Replacement) related, currently used in [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr).

#### `extraReducers`

Specify extra reducers.

e.g. [redux-form](https://github.com/erikras/redux-form) needs extra `form` reducer:

```js
import { reducer as formReducer } from 'redux-form'
const app = dva({
  extraReducers: {
    form: formReducer,
  },
});
```

#### `extraEnhancers`

Specify extra [StoreEnhancer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md#store-enhancer)s.

e.g. use dva with [redux-persist](https://github.com/rt2zz/redux-persist):

```js
import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
  extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);
```

### `app.model(model)`

Register model, view [#Model](#model)  for details.

### `app.unmodel(namespace)`

Unregister model.

### `app.replaceModel(model)`

> Only available after `app.start()` got called

Replace an existing model with a new one, comparing by the namespace. If no one matches, add the new one. 

After called, old `reducers`, `effects`, `subscription` will be replaced with the new ones, while original state is kept, which means it's useful for HMR.

### `app.router(({ history, app }) => RouterConfig)`

Register router config.

e.g.

```js
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
  return (
    <Router history={history}>
      <Route path="/" component={App} />
    <Router>
  );
});
```

Recommend using separate file to config router. Then we can do hmr with [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr). e.g.

```js
app.router(require('./router'));
```

Besides, if don't need router, like multiple-page application, react-native, we can pass in a function which return JSX Element. e.g.

```js
app.router(() => <App />);
```

### `app.start(selector?)`

Start application. `selector` is optionally, if no `selector`, it will return a function which return JSX element.

```js
app.start('#root');
```

e.g. implement i18n with react-intl:

```js
import { IntlProvider } from 'react-intl';
...
const App = app.start();
ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement);
```

## Model
model is the most important concept in dva.

e.g.

```js
app.model({
  namespace: 'todo',
	state: [],
  reducers: {
    add(state, { payload: todo }) {
      // Save data to state
      return [...state, todo];
    },
  },
  effects: {
    *save({ payload: todo }, { put, call }) {
      // Call saveTodoToServer, then trigger `add` action to save data
      yield call(saveTodoToServer, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
  subscriptions: {
    setup({ history, dispatch }) {
      // Subscribe history(url) change, trigger `load` action if pathname is `/`
      return history.listen(({ pathname }) => {
        if (pathname === '/') {
          dispatch({ type: 'load' });
        }
      });
    },
  },
});
```

model includes 5 properties:

### namespace

model's namespace.

### state

models's initial state, it's priority is lower than `opts.initialState` in `dva()`.

e.g.

```
const app = dva({
  initialState: { count: 1 },
});
app.model({
  namespace: 'count',
  state: 0,
});
```

Then, state.count is 1 after `app.start()`.

### reducers

Store reducers in key/value Object. reducer is the only place to modify `state`. Triggered by `action`.

`(state, action) => newState` or `[(state, action) => newState, enhancer]`

View https://github.com/dvajs/dva/blob/master/packages/dva-core/test/reducers.test.js for details.

### effects

Store effects in key/value Object. Used for do async operations and biz logic, don't modify `state` directly. Triggered by `action`, could trigger new `action`, communicate with server, select data from global `state` and so on.

`*(action, effects) => void` or `[*(action, effects) => void, { type }]`。

type includes:

* `takeEvery`
* `takeLatest`
* `throttle`
* `watcher`
* `poll`

View https://github.com/dvajs/dva/blob/master/packages/dva-core/test/effects.test.js for details.

### subscriptions

Store subscriptions in key/value Object. Subscription is used for subscribing data source, then trigger action by need. It's executed when `app.start()`.

`({ dispatch, history }, done) => unlistenFunction`

Notice: if we want to unregister a model with `app.unmodel()` or `app.replaceModel()`, it's subscriptions must return unsubscribe method.


================================================
FILE: docs/Concepts.md
================================================
# Concepts

## Data Flow

<img src="https://zos.alipayobjects.com/rmsportal/PPrerEAKbIoDZYr.png" width="807" />

## Models

### State

`type State = any`

The state tree of your models. Usually, the state is a JavaScript object (although technically it can be any type) which is immutable data.

In dva, you can access top state tree data by `_store`.

```javascript
const app = dva();
console.log(app._store); // top state
```

### Action

`type AsyncAction = any`

Just like Redux's Action, in dva, action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.action. (PS: dispatch is realized through props by connecting components.)

```javascript
dispatch({
  type: 'add',
});
```

### dispatch function

`type dispatch = (a: Action) => Action`

A dispatching function (or simply dispatch function) is a function that accepts an action or an async action; it then may or may not dispatch one or more actions to the store.

Dispatching function is a function for triggering action, action is the only way to change state, but it just describes an action. while dispatch can be regarded as a way to trigger this action, and Reducer is to describe how to change state.

```javascript
dispatch({
  type: 'user/add', // if in model outside, need to add namespace
  payload: {},
});
```

### Reducer

`type Reducer<S, A> = (state: S, action: A) => S`

Just like Redux's Reducer, a reducer (also called a reducing function) is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value.

Reducer's concepts from FP:

```javascript
[{x:1},{y:2},{z:3}].reduce(function(prev, next){
    return Object.assign({}, prev, next);
})
//return {x:1, y:2, z:3}
```

In dva, reducers accumulate current model's state. There are some things need to be notice that reducer must be [pure function](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md) and every calculated data must be [immutable data](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md#reasonable).

### Effect

In dva, we use [redux-sagas](https://redux-saga.js.org/) to control asynchronous flow.
You can learn more in [Mostly adequate guide to FP](https://github.com/MostlyAdequate/mostly-adequate-guide).

In our applications, the most well-known side effect is asynchronous operation, it comes from the conception of functional programing, it is called side effect because it makes our function impure, and the same input may not result in the same output.

### Subscription

Subscriptions is a way to get data from source, it is come from elm.

Data source can be: the current time, the websocket connection of server, keyboard input, geolocation change, history router change, etc..

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

## Router

Hereby router usually means frontend router. Because our current app is single page app, frontend codes are required to control the router logics. Through History API provided by the browser, we can monitor the change of the browser's url, so as to control the router.

dva provide `router` function to control router, based on [react-router](https://github.com/reactjs/react-router)。

```javascript
import { Router, Route } from 'dva/router';
app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);
```

## Route Components

In dva, we restrict container components to route components, because we use page dimension to design container components.

therefore, almost all connected model components are route components, route components in `/routes/` directory, presentational Components in `/components/` directory.

## References
- [redux docs](http://redux.js.org/docs/Glossary.html)
- [Mostly adequate guide to FP](https://github.com/MostlyAdequate/mostly-adequate-guide)
- [choo docs](https://github.com/yoshuawuyts/choo)
- [elm](http://elm-lang.org/blog/farewell-to-frp)


================================================
FILE: docs/GettingStarted.md
================================================
# Getting Started

> This article will lead you to create [dva](https://github.com/dvajs/dva) app quickly, and learn all new concepts.

Final App.

<p align="center">
  <img src="https://zos.alipayobjects.com/rmsportal/iYdclktvqzUHGBe.gif" />
</p>

This app is used to test click speed, by collecting click count within 1 second.

Some questions you may ask:

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

And somethings about code organization.

1. How to write the Component?
1. How to write CSS?
1. How to write the Model?
1. How to connect the Model and the Component?
1. How to update State after user interaction?
1. How to handle async logic?
1. How to config the router?

Also:

1. If I want to use localStorage to save Highest Record, what do I have to do?
2. If we want to support keyboard click rate tests, what do I have 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`.

```bash
$ npm install -g dva-cli
```

After installed, you can check the version with `dva -v`, and view help info with `dva -h`.

## Create new App

After installing dva-cli, we can create a new app with it, called `myapp`.

```bash
$ dva new myapp --demo
```

Notice: the `--demo` option is only used for creating demo level apps. If you want to create a normal project, don't add this option.

`cd` myapp, and start it.

```bash
$ cd myapp
$ npm start
```

After a few seconds, you will get the following output:

```bash
Compiled successfully!

The app is running at:

  http://localhost:8000/

Note that the development build is not optimized.
To create a production build, use npm run build.
```

(Press `Ctrl-C` if you want to close server)

Open http://localhost:8000/ in browser. If successful, you will see a page with "Hello Dva".

## Define models

When you get the task, you should not write code immediately. But it is recommended to do state design in `god mode`.

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

With this task, we define the model as this:

```javascript
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 the model. Then `record` presents `highest record`,and `current` presents current click speed.

## Write components

After designing the model, we begin to write the component. We recommend organizing the Component with [stateless functions](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions) because we don't need state almost at all in dva architecture.

```javascript
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](https://github.com/css-modules/css-modules)
2. Two props are passed in,`count` and `dispatch`. `count` is the state in the model, which is bound with [connect](https://redux.js.org/docs/api/bindActionCreators.html). `dispatch` is used to trigger an action.
3. `dispatch({type: 'count/add'})` means trigger an action `{type: 'count/add'}`. View [Actions@redux.js.org](http://redux.js.org/basics/Actions.html) on what an action is.

## Update state

`reducer` is the only one that can update state.  This makes our app stable, as all data modifications are traceable. `reducer` is a pure function, and accepts two arguments - `state` and an `action`, and returns a new `state`.

```javascript
(state, action) => newState
```

We need two reducers, `add` and `minus`. Please note that `add` will only be recorded if it is the highest.

> Note: `add` and `minus` don't need to add namespace prefixes in `count` model. But if it is outside the model, the action must prefix the namespace separated with `/`. e.g. `count/add`.

```diff
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};
+   },
+ },
});
```

Note:

1. Confused with the `...` [spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) operator? It's used to extend an 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 do they come from?

After defining Model and Component, we need to connect them together. After connecting them, the Component can use the data from Model, and Model can receive actions dispatched from Component.

In this task, we only need to bind `count` .

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

Note: `connect` is from [react-redux](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options)。

## Define Router

> Which Component should be rendered after receiving a url? It's defined by the router.

This app has only one page, so we don't need to modify the router part.

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

Note:

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.

<p align="center">
  <img src="https://zos.alipayobjects.com/rmsportal/EJWakirNlogUSSU.gif" />
</p>

## Add StyleSheet

We define stylesheet in `css modules`, which doesn't have many differences from normal css. Because we have already hooked up the className in Component, at this moment, we only need to replace `index.less` with the follow content:

```css
.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.

<p align="center">
  <img width="270" src="https://zos.alipayobjects.com/rmsportal/oMiYwVUzcIAgLei.png" />
</p>

## Async Logic

Prior to this, all of our operations were synchronous. When clicking on the + button, the value is incremented by 1.

Now we have to deal with async logic. dva processes side effects ( async logic ) with effects on model, which is executed based on [redux-saga](https://github.com/redux-saga/redux-saga), with generator syntax. [MDN documentation for generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators).

In this app, when the user clicks the + button, the value will increase by 1 and trigger a side effect, that is, subtract (decrease by) 1 after 1 second.

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

```diff
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>
+       <button onClick={() => { dispatch({type: 'count/addThenMinus'}); }}>+</button>
      </div>
    </div>
  );
};
```

Note:

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 these, there are commands such as `select`, `take`, `fork`, `cancel`, and so on. View more on [redux-saga documatation](http://redux-saga.github.io/redux-saga/docs/basics/DeclarativeEffects.html)

Refresh you browser, and if successful, it should have all the effects of the beginning gif.

## Subscribe Keyboard Event

> After implementing the mouse click speed test, how do you implement the keyboard click speed test?

There is a concept called `Subscription` from dva, which is from [elm 0.17](http://elm-lang.org/blog/farewell-to-frp).

Subscription is used to subscribe to a data source, then dispatch an action if needed. The data source could be current time, a websocket connection from server, a keyboard input, a geolocation change, a history router change, etc.

The subscription takes place in the model.

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

Here, we don't need to install the `keymaster` dependency manually. When we write `import key from 'keymaster';` and save, dva-cli will install `keymaster` and save to `package.json`. The output looks like this:

```bash
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

```javascript
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: {
    *addThenMinus(action, { call, put }) {
      yield put({ type: 'add' });
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'addThenMinus'}) });
    },
  },
});

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/addThenMinus'}); }}>+</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:

```bash
$ npm run build
```

Output.

```bash
> @ 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 completing this app, do you have answers to all of the questions in the beginning? Do you understand the concepts in dva, like `model`, `router`, `reducers`, `effects` and `subscriptions` ?

Next, you can view [dva official library](https://github.com/dvajs/dva) for more information.


================================================
FILE: docs/README.md
================================================
---
home: true
actionText: 快速上手 →
actionLink: /guide/
features:
- title: 易学易用
  details: 仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
- title: elm 概念
  details: 通过 reducers, effects 和 subscriptions 组织 model,简化 redux 和 redux-saga 引入的概念
- title: 插件机制
  details: 比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
footer: MIT Licensed | Copyright © 2017-present
---

## 社区

| Slack Group                                                  | Github Issue                                            | 钉钉群                                                       | 微信群                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [sorrycc.slack.com](https://join.slack.com/t/sorrycc/shared_invite/enQtNTUzMTYxNDQ5MzE4LTg1NjEzYWUwNDQzMWU3YjViYjcyM2RkZDdjMzE0NzIxMTg3MzIwMDM2YjUwNTZkNDdhNTY5ZTlhYzc1Nzk2NzI) | [umijs/umi/issues](https://github.com/umijs/umi/issues) | <img src="https://gw.alipayobjects.com/zos/rmsportal/jPXcQOlGLnylGMfrKdBz.jpg" width="60" /> | <img src="https://img.alicdn.com/tfs/TB13U6aF6DpK1RjSZFrXXa78VXa-752-974.jpg" width="60" /> |
[https://t.me/joinchat/G0DdHw9tDZC-_NmdKY2jYg](https://t.me/joinchat/G0DdHw9tDZC-_NmdKY2jYg)


================================================
FILE: docs/api/README.md
================================================
---
sidebarDepth: 2
---

# API

## 输出文件

### dva

默认输出文件。

### dva/router

默认输出 [react-router](https://github.com/ReactTraining/react-router) 接口, [react-router-redux](https://github.com/reactjs/react-router-redux) 的接口通过属性 routerRedux 输出。

比如:

```js
import { Router, Route, routerRedux } from 'dva/router';
```

### dva/fetch

异步请求库,输出 [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) 的接口。不和 dva 强绑定,可以选择任意的请求库。

### dva/saga

输出 [redux-saga](https://github.com/yelouafi/redux-saga) 的接口,主要用于用例的编写。(用例中需要用到 effects)

### dva/dynamic

解决组件动态加载问题的 util 方法。

比如:

```js
import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
  app,
  models: () => [
    import('./models/users'),
  ],
  component: () => import('./routes/UserPage'),
});
```

`opts` 包含:

* app: dva 实例,加载 models 时需要
* models: 返回 Promise 数组的函数,Promise 返回 dva model
* component:返回 Promise 的函数,Promise 返回 React Component

## dva API

### `app = dva(opts)`

创建应用,返回 dva 实例。(注:dva 支持多实例)

`opts` 包含:

* `history`:指定给路由用的 history,默认是 `hashHistory`
* `initialState`:指定初始数据,优先级高于 model 中的 state,默认是 `{}`

如果要配置 history 为 `browserHistory`,可以这样:

```js
import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});
```

另外,出于易用性的考虑,`opts` 里也可以配所有的 [hooks](#appusehooks) ,下面包含全部的可配属性:

```js
const app = dva({
  history,
  initialState,
  onError,
  onAction,
  onStateChange,
  onReducer,
  onEffect,
  onHmr,
  extraReducers,
  extraEnhancers,
});
```

### `app.use(hooks)`

配置 hooks 或者注册插件。(插件最终返回的是 hooks )

比如注册 [dva-loading](https://github.com/dvajs/dva-loading) 插件的例子:

```js
import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
```

`hooks` 包含:

#### `onError((err, dispatch) => {})`

`effect` 执行错误或 `subscription` 通过 `done` 主动抛错时触发,可用于管理全局出错状态。

注意:`subscription` 并没有加 `try...catch`,所以有错误时需通过第二个参数 `done` 主动抛错。例子:

```js
app.model({
  subscriptions: {
    setup({ dispatch }, done) {
      done(e);
    },
  },
});
```

如果我们用 antd,那么最简单的全局错误处理通常会这么做:

```js
import { message } from 'antd';
const app = dva({
  onError(e) {
    message.error(e.message, /* duration */3);
  },
});
```

#### `onAction(fn | fn[])`

在 action 被 dispatch 时触发,用于注册 redux 中间件。支持函数或函数数组格式。

例如我们要通过 [redux-logger](https://github.com/evgenyrodionov/redux-logger) 打印日志:

```js
import createLogger from 'redux-logger';
const app = dva({
  onAction: createLogger(opts),
});
```

#### `onStateChange(fn)`

`state` 改变时触发,可用于同步 `state` 到 localStorage,服务器端等。

#### `onReducer(fn)`

封装 reducer 执行。比如借助 [redux-undo](https://github.com/omnidan/redux-undo) 实现 redo/undo :

```js
import undoable from 'redux-undo';
const app = dva({
  onReducer: reducer => {
    return (state, action) => {
      const undoOpts = {};
      const newState = undoable(reducer, undoOpts)(state, action);
      // 由于 dva 同步了 routing 数据,所以需要把这部分还原
      return { ...newState, routing: newState.present.routing };
    },
  },
});
```

#### `onEffect(fn)`

封装 effect 执行。比如 [dva-loading](https://github.com/dvajs/dva-loading) 基于此实现了自动处理 loading 状态。

#### `onHmr(fn)`

热替换相关,目前用于 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 。

#### `extraReducers`

指定额外的 reducer,比如 [redux-form](https://github.com/erikras/redux-form) 需要指定额外的 `form` reducer:

```js
import { reducer as formReducer } from 'redux-form'
const app = dva({
  extraReducers: {
    form: formReducer,
  },
});
```

#### `extraEnhancers`

指定额外的 [StoreEnhancer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md#store-enhancer) ,比如结合 [redux-persist](https://github.com/rt2zz/redux-persist) 的使用:

```js
import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
  extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);
```

### `app.model(model)`

注册 model,详见  [#Model](#model)  部分。

### `app.unmodel(namespace)`

取消 model 注册,清理 reducers, effects 和 subscriptions。subscription 如果没有返回 unlisten 函数,使用 `app.unmodel` 会给予警告⚠️。

### `app.replaceModel(model)`

> 只在app.start()之后可用

替换model为新model,清理旧model的reducers, effects 和 subscriptions,但会保留旧的state状态,对于HMR非常有用。subscription 如果没有返回 unlisten 函数,使用 `app.unmodel` 会给予警告⚠️。 

如果原来不存在相同namespace的model,那么执行`app.model`操作

### `app.router(({ history, app }) => RouterConfig)`

注册路由表。

通常是这样的:

```js
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
  return (
    <Router history={history}>
      <Route path="/" component={App} />
    </Router>
  );
});
```

推荐把路由信息抽成一个单独的文件,这样结合 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 可实现路由和组件的热加载,比如:

```js
app.router(require('./router'));
```

而有些场景可能不使用路由,比如多页应用,所以也可以传入返回 JSX 元素的函数。比如:

```js
app.router(() => <App />);
```

### `app.start(selector?)`

启动应用。`selector` 可选,如果没有 `selector` 参数,会返回一个返回 JSX 元素的函数。

```js
app.start('#root');
```

那么什么时候不加 `selector`?常见场景有测试、node 端、react-native 和 i18n 国际化支持。

比如通过 react-intl 支持国际化的例子:

```js
import { IntlProvider } from 'react-intl';
...
const App = app.start();
ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement);
```

## Model
model 是 dva 中最重要的概念。以下是典型的例子:

```js
app.model({
  namespace: 'todo',
  state: [],
  reducers: {
    add(state, { payload: todo }) {
      // 保存数据到 state
      return [...state, todo];
    },
  },
  effects: {
    *save({ payload: todo }, { put, call }) {
      // 调用 saveTodoToServer,成功后触发 `add` action 保存到 state
      yield call(saveTodoToServer, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
  subscriptions: {
    setup({ history, dispatch }) {
      // 监听 history 变化,当进入 `/` 时触发 `load` action
      return history.listen(({ pathname }) => {
        if (pathname === '/') {
          dispatch({ type: 'load' });
        }
      });
    },
  },
});
```

model 包含 5 个属性:

### namespace

model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 `.` 的方式创建多层命名空间。

### state

初始值,优先级低于传给 `dva()` 的 `opts.initialState`。

比如:

```js
const app = dva({
  initialState: { count: 1 },
});
app.model({
  namespace: 'count',
  state: 0,
});
```

此时,在 `app.start()` 后 state.count 为 1 。

### reducers

以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 `state` 的地方。由 `action` 触发。

格式为 `(state, action) => newState` 或 `[(state, action) => newState, enhancer]`。

详见: https://github.com/dvajs/dva/blob/master/packages/dva-core/test/reducers.test.js

### effects

以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 `state`。由 `action` 触发,可以触发 `action`,可以和服务器交互,可以获取全局 `state` 的数据等等。

格式为 `*(action, effects) => void` 或 `[*(action, effects) => void, { type }]`。

type 类型有:

* `takeEvery`
* `takeLatest`
* `throttle`
* `watcher`
* `poll`

详见:https://github.com/dvajs/dva/blob/master/packages/dva-core/test/effects.test.js

### subscriptions

以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 `app.start()` 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

格式为 `({ dispatch, history }, done) => unlistenFunction`。

注意:如果要使用 `app.unmodel()`,subscription 必须返回 unlisten 方法,用于取消数据订阅。


================================================
FILE: docs/guide/README.md
================================================
# 介绍

dva 首先是一个基于 [redux](https://github.com/reduxjs/redux) 和 [redux-saga](https://github.com/redux-saga/redux-saga) 的数据流方案,然后为了简化开发体验,dva 还额外内置了 [react-router](https://github.com/ReactTraining/react-router) 和 [fetch](https://github.com/github/fetch),所以也可以理解为一个轻量级的应用框架。

## 特性

* **易学易用**,仅有 6 个 api,对 redux 用户尤其友好,[配合 umi 使用](https://umijs.org/guide/with-dva.html)后更是降低为 0 API
* **elm 概念**,通过 reducers, effects 和 subscriptions 组织 model
* **插件机制**,比如 [dva-loading](https://github.com/dvajs/dva/tree/master/packages/dva-loading) 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
* **支持 HMR**,基于 [babel-plugin-dva-hmr](https://github.com/dvajs/babel-plugin-dva-hmr) 实现 components、routes 和 models 的 HMR

## 他是如何工作的?

## 他是怎么来的?

* [Why dva and what's dva](https://github.com/dvajs/dva/issues/1)
* [支付宝前端应用架构的发展和选择](https://www.github.com/sorrycc/blog/issues/6)

## 谁在用?

## 为什么不是...?

### redux
### mobx

## 命名由来?

> D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵。

—— 来自 [守望先锋](https://ow.blizzard.cn/heroes/overwatch-dva) 。

<img src="https://zos.alipayobjects.com/rmsportal/psagSCVHOKQVqqNjjMdf.jpg" width="200" height="200" />


================================================
FILE: docs/guide/concepts.md
================================================
---
sidebarDepth: 2
---

# Dva 概念

## 数据流向

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 `dispatch` 发起一个 action,如果是同步行为会直接通过 `Reducers` 改变 `State` ,如果是异步行为(副作用)会先触发 `Effects` 然后流向 `Reducers` 最终改变 `State`,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。

<img src="https://zos.alipayobjects.com/rmsportal/PPrerEAKbIoDZYr.png" width="807" />

## Models

### State

`type State = any`

State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。

在 dva 中你可以通过 dva 的实例属性 `_store` 看到顶部的 state 数据,但是通常你很少会用到:

```javascript
const app = dva();
console.log(app._store); // 顶部的 state 数据
```

### Action

`type AsyncAction = any`

Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 `type` 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 `dispatch` 函数;需要注意的是 `dispatch` 是在组件 connect Models以后,通过 props 传入的。
```
dispatch({
  type: 'add',
});
```

### dispatch 函数

`type dispatch = (a: Action) => Action`

dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。

在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如:

```javascript
dispatch({
  type: 'user/add', // 如果在 model 外调用,需要添加 namespace
  payload: {}, // 需要传递的信息
});
```

### Reducer

`type Reducer<S, A> = (state: S, action: A) => S`

Reducer(也称为 reducing function)函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。

Reducer 的概念来自于是函数式编程,很多语言中都有 reduce API。如在 javascript 中:

```javascript
[{x:1},{y:2},{z:3}].reduce(function(prev, next){
    return Object.assign(prev, next);
})
//return {x:1, y:2, z:3}
```

在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是[纯函数](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md),所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用[immutable data](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md#reasonable),这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。

### Effect

Effect 被称为副作用,在我们的应用中,最常见的就是异步操作。它来自于函数编程的概念,之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。

dva 为了控制副作用的操作,底层引入了[redux-sagas](http://superraytin.github.io/redux-saga-in-chinese)做异步流程控制,由于采用了[generator的相关概念](http://www.ruanyifeng.com/blog/2015/04/generator.html),所以将异步转成同步写法,从而将effects转为纯函数。至于为什么我们这么纠结于 __纯函数__,如果你想了解更多可以阅读[Mostly adequate guide to FP](https://github.com/MostlyAdequate/mostly-adequate-guide),或者它的中文译本[JS函数式编程指南](https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details)。

### Subscription

Subscriptions 是一种从 __源__ 获取数据的方法,它来自于 elm。

Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

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

## Router

这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 [History API](http://mdn.beonex.com/en/DOM/window.history.html) 可以监听浏览器url的变化,从而控制路由相关操作。

dva 实例提供了 router 方法来控制路由,使用的是[react-router](https://github.com/reactjs/react-router)。

```javascript
import { Router, Route } from 'dva/router';
app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);
```

## Route Components

在[组件设计方法](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 Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。

所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在`/routes/`目录下,而`/components/`目录下则是纯组件(Presentational Components)。

## 参考

- [redux docs](http://redux.js.org/docs/Glossary.html)
- [redux docs 中文](http://cn.redux.js.org/index.html)
- [Mostly adequate guide to FP](https://github.com/MostlyAdequate/mostly-adequate-guide)
- [JS函数式编程指南](https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details)
- [choo docs](https://github.com/yoshuawuyts/choo)
- [elm](http://elm-lang.org/blog/farewell-to-frp)


================================================
FILE: docs/guide/develop-complex-spa.md
================================================
# 使用 Dva 开发复杂 SPA

> 作者:徐飞

在dva的官方仓库里,提供了上手教程,讲述了dva的一些基本概念。到了真实的业务开发过程中,会遇到许许多多不能用那些基本操作覆盖的场景,本文尝试列举一些常见的需求在dva中的实现方式。

## 动态加载model

有不少业务场景下,我们可能会定义出很多个model,但并不需要在应用启动的时候就全部加载,比较典型的是各类管理控制台。如果每个功能页面是通过路由切换,互相之间没有关系的话,通常会使用webpack的require.ensure来做代码模块的懒加载。

我们也可以利用这个特性来做model的动态加载。

```JavaScript
function RouterConfig({ history, app }) {
  const routes = [
    {
      path: '/',
      name: 'IndexPage',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          registerModel(app, require('./models/dashboard'));
          cb(null, require('./routes/IndexPage'));
        });
      },
    },
    {
      path: '/users',
      name: 'UsersPage',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          registerModel(app, require('./models/users'));
          cb(null, require('./routes/Users'));
        });
      },
    },
  ];

  return <Router history={history} routes={routes} />;
}
```

这样,在视图切换到这个路由的时候,对应的model就会被加载。同理,也可以做model的动态移除,不过,一般情况下是不需要移除的。

## 使用model共享全局信息

在上一节我们提到,可以动态加载model,也可以移除。从这个角度看,model是可以有不同生命周期的,有些可以与功能视图伴随,而有些可以贯穿整个应用的生命周期。

从业务场景来说,有不少场景是可以做全局model的,比如说,我们在路由之间前进后退,model可以用于在路由间共享数据,比较典型的,像列表页和详情页的互相跳转,就可以用同一份model去共享它们的数据。

注意,如果当前应用中加载了不止一个model,在其中一个的effect里面做select操作,是可以获取另外一个model中的state的:

```JavaScript
*foo(action, { select }) {
  const { a, b } = yield select();
}
```

这里,a,b可以分别是两个不同model的state。所以,借助这个特点,我们就不必非要把model按照视图的结构进行组织,可以适当按照业务分类,把一些数据存在对应业务的model中,分别通过不同的effect去更新,在获取的地方再去组合,这样可以使得model拥有更好的复用性。

## model的复用

有时候,业务上可能遇到期望把一些与外部关联较少的model拆出来的需求,我们可能会拆出这样的一个model,然后用不同的视图容器去connect它。

```JavaScript
export default {
  namespace: 'reusable',
  state: {},
  reducers: {},
  effects: {}
}
```

所以,在业务上,可能出现的使用情况就是:

```
              ContainerA <-- ModelA
                   |
    ------------------------------
    |                            |
ContainerB <-- reusable     ContainerC <-- reusable
```

这里面,ContainerB和ContainerC是ContainerA的下属,它们的逻辑结构一致,只是展现不同。我们可以让它们分别connect同一个model,注意,这个时候,model的修改会同时影响到两个视图,因为model在state中是直接以namespace作key存放的,实际上只有一份实例。

## 动态扩展model

在上一节中,我们提到可以把model进行分类,以实现在若干视图中的共享,但业务需求是比较多变的,很可能我们又会遇到这种情况:

`几个业务视图长得差不多,model也存在少量差别`

这个情况下,如果我们让它们复用同一个model也可以,但这么做,对维护是一种挑战,很可能改其中一个,对另外一些造成了影响,所以这种情况下,可能会期望能够对model进行扩展。

所谓扩展,通常是要做几个事情:

- 新增一些东西
- 覆盖一些原有的东西
- 根据条件动态创建一些东西

注意到dva中的每个model,实际上都是普通的JavaScript对象,包含

- namespace
- state
- reducers
- effects
- subscriptions

从这个角度看,我们要新增或者覆盖一些东西,都会是比较容易的,比如说,使用Object.assign来进行对象属性复制,就可以把新的内容添加或者覆盖到原有对象上。

注意这里有两级,model结构中的`state`,`reducers`,`effects`,`subscriptions`都是对象结构,需要分别在这一级去做assign。

可以借助dva社区的`dva-model-extend`库来做这件事。

换个角度,也可以通过工厂函数来生成model,比如:

```JavaScript
function createModel(options) {
  const { namespace, param } = options;
  return {
    namespace: `demo${namespace}`,
    states: {},
    reducers: {},
    effects: {
      *foo() {
        // 这里可以根据param来确定下面这个call的参数
        yield call()
      }
    }
  };
}

const modelA = createModel({ namespace: 'A', param: { type: 'A' } });
const modelB = createModel({ namespace: 'A', param: { type: 'B' } });
```

这样,也能够实现对model的扩展。

## 长流程的业务逻辑

在业务中,有时候会出现较长的流程,比如说,我们的一个复杂表单的提交,中间会需要去发起多种对视图状态的操作:

**这是一个真实业务**
```JavaScript
*submit(action, { put, call, select }) {
  const formData = yield select(state => {
    const buyModel = state.buy;
    const context = state.context;
    const { stock } = buyModel;
    return {
      uuid: context.uuid,
      market: stock && stock.market,
      stockCode: stock && stock.code,
      stockName: stock && stock.name,
      price: String(buyModel.price),
      // 委托数量
      entrustAmount: String(buyModel.count),
      totalBalance: buyModel.totalBalance,
      availableTzbBalance: buyModel.availableTzbBalance,
      availableDepositBalance: buyModel.availableDepositBalance,
    };
  });
  const result = yield call(post, '/h5/ajax/trade/entrust_buy', formData, { loading: true });

  if (result.success) {
    toast({
      type: 'success',
      content: '委托已受理',
    });
    // 成功之后再获取一次现价,并填入
    // yield put({type: 'fetchQuotation', payload: stock});

    yield put({ type: 'entrustNoChange', payload: result.result && result.result.entrustNo });
    // 清空输入框内容
    yield put({ type: 'searchQueryChange', value: '' });
  }

  // 403时,需要验证密码再重新提交
  if (!result.success && result.resultCode === 403) {
    yield put({ type: 'checkPassword', payload: {} });
    return;
  }

  // 失败之后也需要更新投资宝和保证金金额
  if (result.result) {
    yield put({ type: 'balanceChange', payload: result.result });
  }

  // 重新获取最新可撤单列表
  yield put({ type: 'fetchRevockList' });

  // 返回的结果里面如果有uuid, 用新的uuid替换
  if (result.uuid) {
    yield put({ type: 'context/updateUuid', payload: result.uuid });
  }
},
```

在一个effect中,可以使用多个put来分别调用reducer来更新状态。

存在另外一些流程,在effect中可能会存在多个异步的服务调用,比如说,要调用一次服务端的验证,成功之后再去提交数据,这时候,在一个effect中就会存在多个call操作了。

## 使用take操作进行事件监听

与上一节提到的情况相比,我们还可能遇到另外一些场景,比如:

`一个流程的变动,需要扩散到若干个其他model中`

这个需求其实也覆盖了上一节这种,但在这一节中,我们侧重讨论比较通用的这类需求的处理方式。

在redux-saga中,提供了take和takeLatest这两个操作,dva是redux-saga的封装,也是可以使用这种操作的。

要理解take操作的语义,可以参见这两种示例的对比:

假设我们有一个事件处理的代码:

```JavaScript
someSource.on('click', event => doSomething(event))
```

这段代码转成用generator来表达,就是下面这个形式:

```JavaScript
function* saga() {
  while(true) {
     const event = yield take('click');
     doSomething(event);
  }
}
```

所以,我们也可以在dva中使用take操作来监听action。

## 多任务调度

上一节我们提到的是多个任务的串行执行方式,这是业务中最常见的多任务执行方式,只需逐个yield call就可以了。

有的时候,我们可能会希望多个任务以另外一些方式执行,比如:

- 并行,若干个任务之间不存在依赖关系,并且后续操作对它们的结果无依赖
- 竞争,若干个任务之间,只要有一个执行完成,就进入下一个环节
- 子任务,若干个任务,并行执行,但必须全部做完之后,下一个环节才继续执行

### 任务的并行执行

如果想要让任务并行执行,可以通过下面这种方式:

```JavaScript
const [result1, result2]  = yield all([
  call(service1, param1),
  call(service2, param2)
])
```

把多个要并行执行的东西放在一个数组里,就可以并行执行,等所有的都结束之后,进入下个环节,类似promise.all的操作。一般有一些集成界面,比如dashboard,其中各组件之间业务关联较小,就可以用这种方式去分别加载数据,此时,整体加载时间只取决于时间最长的那个。

注意:上面代码中的那个:

```JavaScript
yield [];
```

不要写成:

```JavaScript
yield* [];
```

这两者含义是不同的,后者会顺序执行。

### 任务的竞争

如果多个任务之间存在竞争关系,可以通过下面这种方式:

```JavaScript
const { data, timeout } = yield race({
  data: call(service, 'some data'),
  timeout: call(delay, 1000)
});

if (data)
  put({type: 'DATA_RECEIVED', data});
else
  put({type: 'TIMEOUT_ERROR'});
```

这个例子比较巧妙地用一个延时一秒的空操作来跟一个网络请求竞争,如果到了一秒,请求还没结束,就让它超时。

这个类似于Promise.race的作用。

## 跨model的通信

当业务复杂的情况下,我们可能会对model进行拆分,但在这种情况下,往往又会遇到一些比较复杂的事情,比如:

`一个流程贯穿多个model`

对这个事情,我们可能有若干中不同的解决办法。假设有如下场景:

- 父容器A,子容器B,二者各自connect了不同的model A和B
- 父容器中有一个操作,分三个步骤:
  - model A中某个effect处理第一步
  - call model B中的某个effect去处理第二步
  - 第二步结束后,再返回model A中做第三步

在dva中,可以用namespace去指定接受action的model,所以可以通过类似这样的方式去组合:

```JavaScript
yield call({ type: 'a/foo' });
yield call({ type: 'b/foo' });
yield call({ type: 'a/bar' });
```

甚至,还可以利用take命令,在另外一个model的某个effect中插入逻辑:

```JavaScript
*effectA() {
  yield call(service1);
  yield put({ type: 'service1Success' });
  // 如果我们复用这个effect,但要在这里加一件事,怎么办?
  yield call(service2);
  yield put({ type: 'service2Success' });
}
```

可以利用之前我们说的take命令:

```JavaScript
yield take('a/service1Success');
```

这样,可以在外部往里面添加一个并行操作,通过这样的组合可以处理一些组合流程。但实际情况下,我们可能要处理的不仅仅是effect,很可能视图组件中还存在后续逻辑,在某个action执行之后,还需要再做某些事情。

比如:

```JavaScript
yield call({ type: 'a/foo' });
yield call({ type: 'b/foo' });
// 如果这里是要在组件里面做某些事情,怎么办?
```

可以利用一些特殊手段把流程延伸出来到组件里。比如说,我们通常在组件中dispatch一个action的时候,不会处理后续事情,但可以修改这个过程:

```JavaScript
new Promise((resolve, reject) => {
  dispatch({ type: 'reusable/addLog', payload: { data: 9527, resolve, reject } });
})
.then((data) => {
  console.log(`after a long time, ${data} returns`);
});
```

注意这里,我们是把resolve和reject传到action里面了,所以,只需在effect里面这样处理:

```JavaScript
try {
  const result = yield call(service1);
  yield put({ type: 'service1Success', payload: result });
  resolve(result);
}
catch (error) {
  yield put({ type: 'service1Fail', error });
  reject(ex);
}
```

这样,就实现了跨越组件、模型的复杂的长流程的调用。

## 为DVA应用编写测试

在比较追求稳定性的工程中,应当使用单元测试来保证代码质量。在Redux的各类中间件中,redux-saga应当是测试最简单的了,原因如下:

在一个应用中,除了视图组件之外,可能存在逻辑的地方主要是两种:reducer、effect。这两者中,reducer是普通函数,并且是纯函数,职责单一,对于固定输入,就有固定输出,所以很容易测试。而在effect中,我们所要测试的东西是什么呢?如何确保测试能够覆盖某个effect,是全部真实执行一遍吗?

所谓的单元测试,其实要测试的是某个函数自身的逻辑是否全被覆盖,像在一个effect中对外部服务(比如网络请求)的调用,这些外部服务的执行过程其实与本模块的单元测试无关,因此,我们只需要验证这件事:

`是否发起了对某个服务的调用`

至于说,这个服务是否在执行,无关于本模块的正确性,那是这个服务的单元测试要做的事。所以这么一来,一个effect实际上是转化为同步逻辑的测试,因为它是一个generator函数,只需对这个effect一路next,就能跑完整个逻辑。

对redux-saga的测试是这样的原理,而dva是对redux-saga的封装,这块的机制是一致的,所以我们可以用同样的方式,从model对象中获取reducer和effect,分别编写测试用例。 


================================================
FILE: docs/guide/examples-and-boilerplates.md
================================================
# 例子和脚手架

## 官方

* [Count](https://stackblitz.com/edit/dva-example-count): 简单计数器
* [User Dashboard](https://github.com/dvajs/dva/tree/master/examples/user-dashboard): 用户管理
* [AntDesign Pro](https://github.com/ant-design/ant-design-pro):([Demo](https://preview.pro.ant.design/)),开箱即用的中台前端/设计解决方案
* [HackerNews](https://github.com/dvajs/dva-hackernews):  ([Demo](https://dvajs.github.io/dva-hackernews/)),HackerNews Clone
* [antd-admin](https://github.com/zuiidea/antd-admin): ([Demo](http://antd-admin.zuiidea.com/)),基于 antd 和 dva 的后台管理应用
* [github-stars](https://github.com/sorrycc/github-stars): ([Demo](http://sorrycc.github.io/github-stars/#/?_k=rmj86f)),Github Star 管理应用

## 社区

* [umi-dva-antd-mobile](https://github.com/hqwlkj/umi-dva-antd-mobile),来自 @Yanghc 的 umi + dva + antd-mobile 的 mobile 版本脚手架,支持 TypeScript。
* [Account System](https://github.com/yvanwangl/AccountSystem.git): 小型库存管理系统
* [react-native-dva-starter](https://github.com/nihgwu/react-native-dva-starter): 集成了 dva 和 react-navigation 典型应用场景的 React Native 实例


================================================
FILE: docs/guide/fig-show.md
================================================
# Dva 图解

> 作者:至正<br />
> 原文链接:[https://yuque.com/flying.ni/the-tower/tvzasn](https://yuque.com/flying.ni/the-tower/tvzasn)

## 示例背景

最常见的 Web 类示例之一: TodoList = Todo list + Add todo button

## 图解一: React 表示法

![图片.png | left | 747x518](https://cdn.yuque.com/yuque/0/2018/png/103904/1528436560812-2586a0b5-7a6a-4a07-895c-f822fa85d5de.png "")

按照 React 官方指导意见, 如果多个 Component 之间要发生交互, 那么状态(即: 数据)就维护在这些 Component 的最小公约父节点上, 也即是 `<App/>`

`<TodoList/> <Todo/>` 以及`<AddTodoBtn/>` 本身不维持任何 state, 完全由父节点`<App/>` 传入 props 以决定其展现, 是一个纯函数的存在形式, 即: `Pure Component`

## 图解二: Redux 表示法

React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑可以从中单独抽取出来, 变成 store

![图片.png | left | 747x558](https://cdn.yuque.com/yuque/0/2018/png/103904/1528436134375-4c15f63d-72f1-4c73-94a6-55b220d2547c.png "")

与图一相比, 几个明显的改进点:

1. 状态及页面逻辑从 `<App/>`里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer
2. `<TodoList/> ` 及`<AddTodoBtn/>`都是 Pure Component, 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新
3. 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging

这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好

## 图解三: 加入 Saga

![图片.png | left | 747x504](https://cdn.yuque.com/yuque/0/2018/png/103904/1528436167824-7fa834ea-aa6c-4f9f-bab5-b8c5312bcf7e.png "")

上面说了, 可以使用 Middleware 拦截 action, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子:

1. 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action
2. saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可

## 图解四: Dva 表示法

![图片.png | left | 747x490](https://cdn.yuque.com/yuque/0/2018/png/103904/1528436195004-cd3800f2-f13d-40ba-bb1f-4efba99cfe0d.png "")

有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 做了 3 件很重要的事情, 大大提升了编码体验:

1. 把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面
2. 增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作
3. model 写法很简约, 类似于 DSL 或者 RoR, coding 快得飞起✈️

`约定优于配置, 总是好的`😆

```js
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'}) });
    },
  },
});
```


================================================
FILE: docs/guide/getting-started.md
================================================
# 快速上手

## 安装 dva-cli

通过 npm 安装 dva-cli 并确保版本是 `0.9.1` 或以上。

```bash
$ npm install dva-cli -g
$ dva -v
dva-cli version 0.9.1
```

## 创建新应用

安装完 dva-cli 之后,就可以在命令行里访问到 `dva` 命令([不能访问?](http://stackoverflow.com/questions/15054388/global-node-modules-not-installing-correctly-command-not-found))。现在,你可以通过 `dva new` 创建新应用。

```bash
$ dva new dva-quickstart
```

这会创建 `dva-quickstart` 目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。

然后我们 `cd` 进入 `dva-quickstart` 目录,并启动开发服务器:

```bash
$ cd dva-quickstart
$ npm start
```

几秒钟后,你会看到以下输出:

```bash
Compiled successfully!

The app is running at:

  http://localhost:8000/

Note that the development build is not optimized.
To create a production build, use npm run build.
```

在浏览器里打开 http://localhost:8000 ,你会看到 dva 的欢迎界面。

## 使用 antd

通过 npm 安装 `antd` 和 `babel-plugin-import` 。`babel-plugin-import` 是用来按需加载 antd 的脚本和样式的,详见 [repo](https://github.com/ant-design/babel-plugin-import) 。

```bash
$ npm install antd babel-plugin-import --save
```

编辑 `.webpackrc`,使 `babel-plugin-import` 插件生效。

```diff
{
+  "extraBabelPlugins": [
+    ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
+  ]
}
```

> 注:dva-cli 基于 roadhog 实现 build 和 dev,更多 `.webpackrc` 的配置详见 [roadhog#配置](https://github.com/sorrycc/roadhog#配置)

## 定义路由

我们要写个应用来先显示产品列表。首先第一步是创建路由,路由可以想象成是组成应用的不同页面。

新建 route component `routes/Products.js`,内容如下:

```javascript
import React from 'react';

const Products = (props) => (
  <h2>List of Products</h2>
);

export default Products;
```

添加路由信息到路由表,编辑 `router.js` :

```diff
+ import Products from './routes/Products';
...
+ <Route path="/products" exact component={Products} />
```

然后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的 `<h2>` 标签。

## 编写 UI Component

随着应用的发展,你会需要在多个页面分享 UI 元素 (或在一个页面使用多次),在 dva 里你可以把这部分抽成 component 。

我们来编写一个 `ProductList` component,这样就能在不同的地方显示产品列表了。

新建 `components/ProductList.js` 文件:

```javascript
import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd';

const ProductList = ({ onDelete, products }) => {
  const columns = [{
    title: 'Name',
    dataIndex: 'name',
  }, {
    title: 'Actions',
    render: (text, record) => {
      return (
        <Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
          <Button>Delete</Button>
        </Popconfirm>
      );
    },
  }];
  return (
    <Table
      dataSource={products}
      columns={columns}
    />
  );
};

ProductList.propTypes = {
  onDelete: PropTypes.func.isRequired,
  products: PropTypes.array.isRequired,
};

export default ProductList;
```

## 定义 Model

完成 UI 后,现在开始处理数据和逻辑。

dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。

新建 model `models/products.js` :

```javascript
export default {
  namespace: 'products',
  state: [],
  reducers: {
    'delete'(state, { payload: id }) {
      return state.filter(item => item.id !== id);
    },
  },
};
```

这个 model 里:

- `namespace` 表示在全局 state 上的 key
- `state` 是初始值,在这里是空数组
- `reducers` 等同于 redux 里的 reducer,接收 action,同步更新 state

然后别忘记在 `index.js` 里载入他:

```diff
// 3. Model
+ app.model(require('./models/products').default);
```

## connect 起来

到这里,我们已经单独完成了 model 和 component,那么他们如何串联起来呢?

dva 提供了 connect 方法。如果你熟悉 redux,这个 connect 就是 react-redux 的 connect 。

编辑 `routes/Products.js`,替换为以下内容:

```javascript
import React from 'react';
import { connect } from 'dva';
import ProductList from '../components/ProductList';

const Products = ({ dispatch, products }) => {
  function handleDelete(id) {
    dispatch({
      type: 'products/delete',
      payload: id,
    });
  }
  return (
    <div>
      <h2>List of Products</h2>
      <ProductList onDelete={handleDelete} products={products} />
    </div>
  );
};

// export default Products;
export default connect(({ products }) => ({
  products,
}))(Products);
```

最后,我们还需要一些初始数据让这个应用 run 起来。编辑 `index.js`:

```diff
- const app = dva();
+ const app = dva({
+   initialState: {
+     products: [
+       { name: 'dva', id: 1 },
+       { name: 'antd', id: 2 },
+     ],
+   },
+ });
```

刷新浏览器,应该能看到以下效果:

<p style="text-align: center">
  <img src="https://zos.alipayobjects.com/rmsportal/GQJeDDeUCSTRMMg.gif" />
</p>

## 构建应用

完成开发并且在开发环境验证之后,就需要部署给我们的用户了。先执行下面的命令:

```bash
$ npm run build
```

几秒后,输出应该如下:

```bash
> @ build /private/tmp/myapp
> roadhog build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  82.98 KB  dist/index.js
  270 B     dist/index.css
```

`build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。然后你可以在 `dist/` 目录下找到这些文件。


================================================
FILE: docs/guide/introduce-class.md
================================================
# 入门课

::: tip
内容来自之前为内部同学准备的入门课。
:::

## React 没有解决的问题

React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。

如果开发大应用,还需要解决一个问题。

* 通信:组件之间如何通信?
* 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等

## 通信问题
组件会发生三种通信。

* 向子组件发消息
* 向父组件发消息
* 向其他组件发消息

React 只提供了一种通信手段:传参。对于大应用,很不方便。

## 组件通信的例子

### 步骤1

```js
class Son extends React.Component {
  render() {
    return <input/>;
  }
}

class Father extends React.Component {
  render() {
    return <div>
      <Son/>
      <p>这里显示 Son 组件的内容</p>
    </div>;
  }
}

ReactDOM.render(<Father/>, mountNode);
```

看这个例子,想一想父组件如何拿到子组件的值。

### 步骤2

```js
class Son extends React.Component {
  render() {
    return <input onChange={this.props.onChange}/>;
  }
}

class Father extends React.Component {
  constructor() {
    super();
    this.state = {
      son: ""
    }
  }
  changeHandler(e) {
    this.setState({
      son: e.target.value
    });
  }
  render() {
    return <div>
      <Son onChange={this.changeHandler.bind(this)}/>
      <p>这里显示 Son 组件的内容:{this.state.son}</p>
    </div>;
  }
}

ReactDOM.render(<Father/>, mountNode);
```

看下这个例子,看懂源码,理解子组件如何通过父组件传入的函数,将自己的值再传回父组件。 

## 数据流问题

目前流行的数据流方案有:

* Flux,单向数据流方案,以 [Redux](https://github.com/reactjs/redux) 为代表
* Reactive,响应式数据流方案,以 [Mobx](https://github.com/mobxjs/mobx) 为代表
* 其他,比如 rxjs 等

到底哪一种架构最合适 React ?

## 目前最流行的数据流方案

截止 2017.1,最流行的社区 React 应用架构方案如下。

* 路由: [React-Router](https://github.com/ReactTraining/react-router/tree/v2.8.1)
* 架构: [Redux](https://github.com/reactjs/redux)
* 异步操作: [Redux-saga](https://github.com/yelouafi/redux-saga)

缺点:要引入多个库,项目结构复杂。

## dva 是什么

dva 是体验技术部开发的 React 应用框架,将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。

dva = React-Router + Redux + Redux-saga

## dva 应用的最简结构
```js
import dva from 'dva';
const App = () => <div>Hello dva</div>;

// 创建应用
const app = dva();
// 注册视图
app.router(() => <App />);
// 启动应用
app.start('#root');
```

## 数据流图

<img src="https://zos.alipayobjects.com/rmsportal/hUFIivoOFjVmwNXjjfPE.png" width="460" height="290" /> 

## 核心概念 
* State:一个对象,保存整个应用状态
* View:React 组件构成的视图层
* Action:一个对象,描述事件 
* connect 方法:一个函数,绑定 State 到 View
* dispatch 方法:一个函数,发送 Action 到 State

## State 和 View
State 是储存数据的地方,收到 Action 以后,会更新数据。

View 就是 React 组件构成的 UI 层,从 State 取数据后,渲染成 HTML 代码。只要 State 有变化,View 就会自动更新。

## Action
Action 是用来描述 UI 层事件的一个对象。

```js
{
  type: 'click-submit-button',
  payload: this.form.data
}
```

## connect 方法

connect 是一个函数,绑定 State 到 View。

```js
import { connect } from 'dva';

function mapStateToProps(state) {
  return { todos: state.todos };
}
connect(mapStateToProps)(App);
```

connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。

connect 方法传入的第一个参数是 mapStateToProps  函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。

## dispatch 方法
dispatch 是一个函数方法,用来将 Action 发送给 State。

```js
dispatch({
  type: 'click-submit-button',
  payload: this.form.data
})
```

dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。

> connect 的数据从哪里来? 

## dva 应用的最简结构(带 model)
```js
// 创建应用
const app = dva();

// 注册 Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1 },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
});

// 注册视图
app.router(() => <ConnectedApp />);

// 启动应用
app.start('#root');
```

## 数据流图 1

<img src="https://zos.alipayobjects.com/rmsportal/cyzvnIrRhJGOiLliwhcZ.png" width="450" height="380" />

## 数据流图 2

<img src="https://zos.alipayobjects.com/rmsportal/pHTYrKJxQHPyJGAYOzMu.png" width="607" height="464" />

## app.model

dva 提供 app.model 这个对象,所有的应用逻辑都定义在它上面。

```js
const app = dva();

// 新增这一行
app.model({ /**/ });

app.router(() => <App />);
app.start('#root');
```

## Model 对象的例子

```js
{
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1 },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
}
```

## Model 对象的属性

* namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
* state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
* reducers: Action 处理器,处理同步动作,用来算出最新的 State
* effects:Action 处理器,处理异步动作

## Reducer

Reducer 是 Action 处理器,用来处理同步操作,可以看做是 state 的计算器。它的作用是根据 Action,从上一个 State 算出当前 State。

一些例子:

```js
// count +1
function add(state) { return state + 1; }

// 往 [] 里添加一个新 todo
function addTodo(state, action) { return [...state, action.payload]; }

// 往 { todos: [], loading: true } 里添加一个新 todo,并标记 loading 为 false
function addTodo(state, action) {
  return {
    ...state,
    todos: state.todos.concat(action.payload),
    loading: false
  };
}
```

## Effect

Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。

```js
function *addAfter1Second(action, { put, call }) {
  yield call(delay, 1000);
  yield put({ type: 'add' });
}
```

## Generator 函数

Effect 是一个 Generator 函数,内部使用 yield 关键字,标识每一步的操作(不管是异步或同步)。

## call 和 put

dva 提供多个 effect 函数内部的处理函数,比较常用的是 `call` 和 `put`。

* call:执行异步函数
* put:发出一个 Action,类似于 dispatch

## 课堂实战
写一个列表,包含删除按钮,点删除按钮后延迟 1 秒执行删除。

<img src="https://zos.alipayobjects.com/rmsportal/qEVcuGVcKMGjlUNghHel.gif" />


================================================
FILE: docs/guide/source-code-explore.md
================================================
# Dva 源码解析

> 作者:杨光

## 隐藏在 package.json 里的秘密

随便哪个 dva 的项目,只要敲入 npm start 就可以运行启动。之前敲了无数次我都没有在意,直到我准备研究源码的时候才意识到:**在敲下这行命令的时候,到底发生了什么呢?**

答案要去 package.json 里去寻找。

>有位技术大牛曾经告诉过我:看源码之前,先去看 package.json 。看看项目的入口文件,翻翻它用了哪些依赖,对项目便有了大致的概念。

package.json 里是这么写的:

```json
 "scripts": {
    "start": "roadhog server"
  },
```

翻翻依赖,`"roadhog": "^0.5.2"`。


既然能在 devDependencies 找到,那么肯定也能在 [npm](https://www.npmjs.com/package/roadhog) 上找到。原来是个和 webpack 相似的库,而且作者看着有点眼熟...

如果说 dva 是亲女儿,那 [roadhog](https://github.com/sorrycc/roadhog.git) 就是亲哥哥了,起的是 webpack 自动打包和热更替的作用。

在 roadhog 的默认配置里有这么一条信息:

```json
{
  "entry": "src/index.js",
}
```

后转了一圈,启动的入口回到了 `src/index.js`。

## `src/index.js`

在 `src/index.js` 里,dva 一共做了这么几件事:

0. 从 'dva' 依赖中引入 dva :`import dva from 'dva'`; 

1. 通过函数生成一个 app 对象:`const app = dva()`; 

2. 加载插件:`app.use({})`;

3. 注入 model:`app.model(require('./models/example'))`;

4. 添加路由:`app.router(require('./routes/indexAnother'))`;

5. 启动:app.start('#root');

在这 6 步当中,dva 完成了 `使用 React 解决 view 层`、`redux 管理 model `、`saga 解决异步`的主要功能。事实上在我查阅资料以及回忆用过的脚手架时,发现目前端框架之所以被称为“框架”也就是解决了这些事情。前端工程师至今所做的事情都是在 **分离动态的 data 和静态的 view** ,只不过侧重点和实现方式也不同。

至今为止出了这么多框架,但是前端 MVX 的思想一直都没有改变。

# dva 

## 寻找 “dva”

既然 dva 是来自于 `dva`,那么 dva 是什么这个问题自然要去 dva 的[源码](https://github.com/dvajs/dva)中寻找了。

> 剧透:dva 是个函数,返回一了个 app 的对象。

> 剧透2:目前 dva 的源码核心部分包含两部分,`dva` 和 `dva-core`。前者用高阶组件 React-redux 实现了 view 层,后者是用 redux-saga 解决了 model 层。

老规矩,还是先翻 package.json 。

引用依赖很好的说明了 dva 的功能:统一 view 层。

```json
// dva 使用的依赖如下:

    "babel-runtime": "^6.26.0", // 一个编译后文件引用的公共库,可以有效减少编译后的文件体积
    "dva-core": "^1.1.0", // dva 另一个核心,用于处理数据层
    "global": "^4.3.2", // 用于提供全局函数的引用
    "history": "^4.6.3", // browserHistory 或者 hashHistory
    "invariant": "^2.2.2", // 一个有趣的断言库
    "isomorphic-fetch": "^2.2.1", // 方便请求异步的函数,dva 中的 fetch 来源
    "react-async-component": "^1.0.0-beta.3", // 组件懒加载
    "react-redux": "^5.0.5", // 提供了一个高阶组件,方便在各处调用 store
    "react-router-dom": "^4.1.2", // router4,终于可以像写组件一样写 router 了
    "react-router-redux": "5.0.0-alpha.6",// redux 的中间件,在 provider 里可以嵌套 router
    "redux": "^3.7.2" // 提供了 store、dispatch、reducer 
	
```
不过 script 没有给太多有用的信息,因为 `ruban build` 中的 `ruban` 显然是个私人库(虽然在 tnpm 上可以查到但是也是私人库)。但根据惯例,应该是 dva 包下的 `index.js` 文件提供了对外调用:
```js
Object.defineProperty(exports, "__esModule", {
  value: true
});

exports.default = require('./lib');
exports.connect = require('react-redux').connect;
```

显然这个 `exports.default` 就是我们要找的 dva,但是源码中没有 `./lib` 文件夹。当然直接看也应该看不懂,因为一般都是使用 babel 的命令 `babel src -d libs` 进行编译后生成的,所以直接去看 `src/index.js` 文件。


## `src/index.js`

`src/index.js`[在此](https://github.com/dvajs/dva/blob/master/packages/dva/src/index.js) :

在这里,dva 做了三件比较重要的事情:

1. 使用 call 给 dva-core 实例化的 app(这个时候还只有数据层) 的 start 方法增加了一些新功能(或者说,通过代理模式给 model 层增加了 view 层)。
2. 使用 react-redux 完成了 react 到 redux 的连接。
3. 添加了 redux 的中间件 react-redux-router,强化了 history 对象的功能。

### 使用 call 方法实现代理模式

dva 中实现代理模式的方式如下:

**1. 新建 function ,函数内实例化一个 app 对象。**
**2. 新建变量指向该对象希望代理的方法, `oldStart = app.start`。**
**3. 新建同名方法 start,在其中使用 call,指定 oldStart 的调用者为 app。**
**4. 令 app.start = start,完成对 app 对象的 start 方法的代理。**

上代码:

```js
export default function(opts = {}) {

  // ...初始化 route ,和添加 route 中间件的方法。

  /**
   * 1. 新建 function ,函数内实例化一个 app 对象。
   * 
   */
  const app = core.create(opts, createOpts);
  /**
   * 2. 新建变量指向该对象希望代理的方法
   * 
   */
  const oldAppStart = app.start;
  app.router = router;
  /**
   * 4. 令 app.start = start,完成对 app 对象的 start 方法的代理。
   * @type {[type]}
   */
  app.start = start;
  return app;

  // router 赋值

  /**
   * 3.1 新建同名方法 start,
   * 
   */
  function start(container) {
    // 合法性检测代码

    /**
     * 3.2 在其中使用 call,指定 oldStart 的调用者为 app。
     */
    oldAppStart.call(app);
	
	// 因为有 3.2 的执行才有现在的 store
    const store = app._store;

	// 使用高阶组件创建视图
  }
}
```  

> 为什么不直接在 start 方式中 oldAppStart ?
- 因为 dva-core 的 start 方法里有用到 this,不用 call 指定调用者为 app 的话,oldAppStart() 会找错对象。

> 实现代理模式一定要用到 call 吗?
- 不一定,看有没有 使用 this 或者代理的函数是不是箭头函数。从另一个角度来说,如果使用了 function 关键字又在内部使用了 this,那么一定要用 call/apply/bind 指定 this。

> 前端还有那里会用到 call ?
- 就实际开发来讲,因为已经使用了 es6 标准,基本和 this 没什么打交道的机会。使用 class 类型的组件中偶尔还会用到 this.xxx.bind(this),stateless 组件就洗洗睡吧(因为压根没有 this)。如果实现代理,可以使用继承/反向继承的方法 —— 比如高阶组件。


### 使用 react-redux 的高阶组件传递 store

经过 call 代理后的 start 方法的主要作用,便是使用 react-redux 的 provider 组件将数据与视图联系了起来,生成 React 元素呈现给使用者。

不多说,上代码。

```js
// 使用 querySelector 获得 dom
if (isString(container)) {
  container = document.querySelector(container);
  invariant(
    container,
    `[app.start] container ${container} not found`,
  );
}

// 其他代码

// 实例化 store
oldAppStart.call(app); 
const store = app._store;

// export _getProvider for HMR
// ref: https://github.com/dvajs/dva/issues/469
app._getProvider = getProvider.bind(null, store, app);

// If has container, render; else, return react component
// 如果有真实的 dom 对象就把 react 拍进去
if (container) {
  render(container, store, app, app._router);
  // 热加载在这里
  app._plugin.apply('onHmr')(render.bind(null, container, store, app));
} else {
  // 否则就生成一个 react ,供外界调用
  return getProvider(store, this, this._router);
}
  
 // 使用高阶组件包裹组件
function getProvider(store, app, router) {
  return extraProps => (
    <Provider store={store}>
      { router({ app, history: app._history, ...extraProps }) }
    </Provider>
  );
}

// 真正的 react 在这里
function render(container, store, app, router) {
  const ReactDOM = require('react-dom/client') // eslint-disable-line
  ReactDOM.createRoot(container).render(React.createElement(getProvider(store, app, router)));
}
```

> React.createElement(getProvider(store, app, router)) 怎么理解?
- getProvider 实际上返回的不单纯是函数,而是一个无状态的 React 组件。从这个角度理解的话,ReactElement.createElement(string/ReactClass type,[object props],[children ...]) 是可以这么写的。

> 怎么理解 React 的 stateless 组件和 class 组件?
- 你猜猜?
```
JavaScript 并不存在 class 这个东西,即便是 es6 引入了以后经过 babel 编译也会转换成函数。因此直接使用无状态组件,省去了将 class 实例化再调用 render 函数的过程,有效的加快了渲染速度。

即便是 class 组件,React.createElement 最终调用的也是 render 函数。不过这个目前只是我的推论,没有代码证据的证明。
```

#### react-redux 与 provider 

> provider 是个什么东西?

本质上是个高阶组件,也是代理模式的一种实践方式。接收 redux 生成的 store 做参数后,通过上下文 context 将 store 传递进被代理组件。在保留原组件的功能不变的同时,增加了 store 的 dispatch 等方法。

> connect 是个什么东西?

connect 也是一个代理模式实现的高阶组件,为被代理的组件实现了从 context 中获得 store 的方法。

> connect()(MyComponent) 时发生了什么?

只放关键部分代码,因为我也只看懂了关键部分(捂脸跑):

```js
import connectAdvanced from '../components/connectAdvanced' 
export function createConnect({
  connectHOC = connectAdvanced,
.... 其他初始值
} = {}) {
	
  return function connect( { // 0 号 connnect
    mapStateToProps,
    mapDispatchToProps,
   	... 其他初始值
    } = {}
  ) {
	....其他逻辑
    return connectHOC(selectorFactory, {//  1号 connect
		.... 默认参数
		selectorFactory 也是个默认参数
      })
  }
}

export default createConnect() // 这是 connect 的本体,导出时即生成 connect 0

```
```js
// hoist-non-react-statics,会自动把所有绑定在对象上的非React方法都绑定到新的对象上
import hoistStatics from 'hoist-non-react-statics'
// 1号 connect 的本体
export default function connectAdvanced() {
	// 逻辑处理

	// 1 号 connect 调用时生成 2 号 connect
  return function wrapWithConnect(WrappedComponent) {
   	// ... 逻辑处理

	// 在函数内定义了一个可以拿到上下文对象中 store 的组件
    class Connect extends Component {
      
      getChildContext() {
		// 上下文对象中获得 store
        const subscription = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }
		
		// 逻辑处理

      render() {

		  	// 	最终生成了新的 react 元素,并添加了新属性
          return createElement(WrappedComponent, this.addExtraProps(selector.props))

      }
    }

	// 逻辑处理
	
	// 最后用定义的 class 和 被代理的组件生成新的 react 组件
    return hoistStatics(Connect, WrappedComponent)  // 2 号函数调用后生成的对象是组件
  }
}


```
结论:对于 connect()(MyComponent)

1. connect 调用时生成 0 号 connect
2. connect()  0 号 connect 调用,返回 1 号 connect 的调用 `connectHOC()` ,生成 2 号 connect(也是个函数) 。
3. connect()(MyComponent) 等价于 connect2(MyComponent),返回值是一个新的组件


### redux 与 router

redux 是状态管理的库,router 是(唯一)控制页面跳转的库。两者都很美好,但是不美好的是两者无法协同工作。换句话说,当路由变化以后,store 无法感知到。

于是便有了 `react-router-redux`。

`react-router-redux` 是 redux 的一个中间件(中间件:JavaScript 代理模式的另一种实践 针对 dispatch 实现了方法的代理,在 dispatch action 的时候增加或者修改) ,主要作用是:

> 加强了React Router库中history这个实例,以允许将history中接受到的变化反应到state中去。

[github 在此](https://github.com/reactjs/react-router-redux)

从代码上讲,主要是监听了 history 的变化:

`history.listen(location => analyticsService.track(location.pathname))`

dva 在此基础上又进行了一层代理,把代理后的对象当作初始值传递给了 dva-core,方便其在 model 的 
subscriptions 中监听 router 变化。

看看 `index.js` 里 router 的实现:

1.在 createOpts 中初始化了添加 react-router-redux 中间件的方法和其 reducer ,方便 dva-core 在创建 store 的时候直接调用。

2. 使用 patchHistory 函数代理 history.linsten,增加了一个回调函数的做参数(也就是订阅)。

> subscriptions 的东西可以放在 dva-core 里再说,

```js
import createHashHistory from 'history/createHashHistory';
import {
  routerMiddleware,
  routerReducer as routing,
} from 'react-router-redux';
import * as core from 'dva-core';

export default function (opts = {}) {
  const history = opts.history || createHashHistory();
  const createOpts = {
  	// 	初始化 react-router-redux 的 router
    initialReducer: {
      routing,
    },
	// 初始化 react-router-redux 添加中间件的方法,放在所有中间件最前面
    setupMiddlewares(middlewares) {
      return [
        routerMiddleware(history),
        ...middlewares,
      ];
    },
	// 使用代理模式为 history 对象增加新功能,并赋给 app
    setupApp(app) {
      app._history = patchHistory(history);
    },
  };

  const app = core.create(opts, createOpts);
  const oldAppStart = app.start;
  app.router = router;
  app.start = start;
  return app;

  function router(router) {
    invariant(
      isFunction(router),
      `[app.router] router should be function, but got ${typeof router}`,
    );
    app._router = router;
  }


}

// 使用代理模式扩展 history 对象的 listen 方法,添加了一个回调函数做参数并在路由变化是主动调用
function patchHistory(history) {
  const oldListen = history.listen;
  history.listen = (callback) => {
    callback(history.location);
    return oldListen.call(history, callback);
  };
  return history;
}
```

> 剧透:redux 中创建 store 的方法为:

```js
// combineReducers 接收的参数是对象
// 所以 initialReducer 的类型是对象
// 作用:将对象中所有的 reducer 组合成一个大的 reducer
const reducers = {}; 
// applyMiddleware 接收的参数是可变参数
// 所以 middleware 是数组
// 作用:将所有中间件组成一个数组,依次执行
const middleware = []; 
const store = createStore(
  combineReducers(reducers),
  initial_state, // 设置 state 的初始值
  applyMiddleware(...middleware)
);
```

## 视图与数据(上)

`src/index.js` 主要实现了 dva 的 view 层,同时传递了一些初始化数据到 dva-core 所实现的 model 层。当然,还提供了一些 dva 中常用的方法函数:

- `dynamic` 动态加载(2.0 以后官方提供 1.x 自己手动实现吧)
- `fetch` 请求方法(其实 dva 只是做了一把搬运工)
- `saga`(数据层处理异步的方法)。

这么看 dva 真的是很薄的一层封装。

而 dva-core 主要解决了 model 的问题,包括 state 管理、数据的异步加载、订阅-发布模式的实现,可以作为数据层在别处使用(看 2.0 更新也确实是作者的意图)。使用的状体啊管理库还是 redux,异步加载的解决方案是 saga。当然,一切也都写在 index.js 和 package.json 里。

## 视图与数据(下)

处理 React 的 model 层问题有很多种办法,比如状态管理就不一定要用 Redux,也可以使用 Mobx(写法会更有 MVX 框架的感觉);异步数据流也未必使用 redux-saga,redux-thunk 或者 redux-promise 的解决方式也可以(不过目前看来 saga 是相对更优雅的)。

放两篇个人感觉比较全面的技术文档:

- 阮一峰前辈的 [redux 三部曲](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html)。
- redux-saga 的[中文文档](http://leonshi.com/redux-saga-in-chinese/docs/api/index.html)。

以及两者的 github:

- [redux](https://github.com/reactjs/redux)
- [redux-saga](https://github.com/redux-saga/redux-saga)

然后继续深扒 `dva-core`,还是先从 `package.json` 扒起。

## package.json

`dva-core` 的 `package.json` 中依赖包如下:

```json
    "babel-runtime": "^6.26.0",  // 一个编译后文件引用的公共库,可以有效减少编译后的文件体积
    "flatten": "^1.0.2", // 一个将多个数组值合并成一个数组的库
    "global": "^4.3.2",// 用于提供全局函数比如 document 的引用
    "invariant": "^2.2.1",// 一个有趣的断言库
    "is-plain-object": "^2.0.3", // 判断是否是一个对象
    "redux": "^3.7.1", // redux ,管理 react 状态的库
    "redux-saga": "^0.15.4", // 处理异步数据流
    "warning": "^3.0.0" // 同样是个断言库,不过输出的是警告
```

当然因为打包还是用的 `ruban`,script 里没有什么太多有用的东西。继续依循惯例,去翻 `src/index.js`。

## `src/index.js`

`src/index` 的源码在[这里](https://github.com/dvajs/dva/blob/master/packages/dva-core/src/index.js)

在 `dva` 的 `src/index.js` 里,通过传递 2 个变量 `opts` 和 `createOpts` 并调用 `core.create`,`dva` 创建了一个 app 对象。其中 `opts` 是使用者添加的控制选项,`createOpts` 则是初始化了 reducer 与 redux 的中间件。

`dva-core` 的 `src/index.js` 里便是这个 app 对象的具体创建过程以及包含的方法:

```js
export function create(hooksAndOpts = {}, createOpts = {}) {
  const {
    initialReducer,
    setupApp = noop,
  } = createOpts;

  const plugin = new Plugin();
  plugin.use(filterHooks(hooksAndOpts));

  const app = {
    _models: [
      prefixNamespace({ ...dvaModel }),
    ],
    _store: null,
    _plugin: plugin,
    use: plugin.use.bind(plugin),
    model,
    start,
  };
  return app;
  	// .... 方法的实现
	
	function model(){
		// model 方法
	}
	
	functoin start(){
		// Start 方法
	}
  }
  ```

> 我最开始很不习惯 JavaScript 就是因为 JavaScript 还是一个函数向的编程语言,也就是函数里可以定义函数,返回值也可以是函数,class 最后也是被解释成函数。在 dva-core 里创建了 app 对象,但是把 model 和 start 的定义放在了后面。一开始对这种简写没看懂,后来熟悉了以后发现确实好理解。一眼就可以看到 app 所包含的方法,如果需要研究具体方法的话才需要向后看。

[Plugin](https://github.com/dvajs/dva/blob/master/packages/dva-core/src/Plugin.js) 是作者设置的一堆**钩子**性监听函数——即是在符合某些条件的情况下下(dva 作者)进行手动调用。这样使用者只要按照作者设定过的关键词传递回调函数,在这些条件下便会自动触发。

> 有趣的是,我最初理解**钩子**的概念是在 Angular 里。为了能像 React 一样优雅的控制组件的生命周期,Angular 设置了一堆接口(因为使用的是 ts,所以 Angular 里有类和接口的区分)。只要组件实现(implements)对应的接口————或者称生命周期钩子,在对应的条件下就会运行接口的方法。 

#### Plugin 与 plugin.use

Plugin 与 plugin.use 都有使用数组的 reduce 方法的行为:
```js
const hooks = [
  'onError',
  'onStateChange',
  'onAction',
  'onHmr',
  'onReducer',
  'onEffect',
  'extraReducers',
  'extraEnhancers',
];

export function filterHooks(obj) {
  return Object.keys(obj).reduce((memo, key) => {
  // 如果对象的 key 在 hooks 数组中
  // 为 memo 对象添加新的 key,值为 obj 对应 key 的值
    if (hooks.indexOf(key) > -1) {
      memo[key] = obj[key];
    }
    return memo;
  }, {});
}

export default class Plugin {
  constructor() {
    this.hooks = hooks.reduce((memo, key) => {
      memo[key] = [];
      return memo;
    }, {});
	/*
		等同于
		
		this.hooks = {
			onError: [],
			onStateChange:[],
			....
			extraEnhancers: []
		}
	*/
  }

  use(plugin) {
    invariant(isPlainObject(plugin), 'plugin.use: plugin should be plain object');
    const hooks = this.hooks;
    for (const key in plugin) {
      if (Object.prototype.hasOwnProperty.call(plugin, key)) {
        invariant(hooks[key], `plugin.use: unknown plugin property: ${key}`);
        if (key === 'extraEnhancers') {
          hooks[key] = plugin[key];
        } else {
          hooks[key].push(plugin[key]);
        }
      }
    }
  }

  // 其他方法
}
```
- 构造器中的 `reduce` 初始化了一个以 `hooks` 数组所有元素为 key,值为空数组的对象,并赋给了 class 的私有变量 `this.hooks`。

- `filterHooks` 通过 `reduce` 过滤了 `hooks` 数组以外的钩子。

- `use` 中使用 `hasOwnProperty` 判断 `key` 是 `plugin` 的自身属性还是继承属性,使用原型链调用而不是 `plugin.hasOwnProperty()` 是防止使用者故意捣乱在 `plugin` 自己写一个 `hasOwnProperty = () => false // 这样无论如何调用 plugin.hasOwnProperty() 返回值都是 false`。

- `use` 中使用 `reduce` 为 `this.hooks` 添加了 `plugin[key]` 。 

## model 方法

`model` 是 app 添加 model 的方法,在 **dva 项目** 的 index.js 是这么用的。

> app.model(require('./models/example'));

在 `dva` 中没对 model 做任何处理,所以 `dva-core` 中的 model 就是 **dva 项目** 里调用的 model。

```js
  function model(m) {
    if (process.env.NODE_ENV !== 'production') {
      checkModel(m, app._models);
    }
    app._models.push(prefixNamespace(m));
  }
  
```

- `checkModel` 主要是用 `invariant` 对传入的 model 进行了合法性检查。

- `prefixNamespace` 又使用 reduce 对每一个 model 做处理,为 model 的 reducers 和 effects 中的方法添加了 `${namespace}/` 的前缀。

> Ever wonder why we dispatch the action like this in dva ? `dispatch({type: 'example/loadDashboard'` 

## start 方法

`start` 方法是 `dva-core` 的核心,在 `start` 方法里,dva 完成了 **`store` 初始化** 以及 **`redux-saga` 的调用**。比起 `dva` 的 `start`,它引入了更多的调用方式。

一步一步分析:

### `onError`

```js
    const onError = (err) => {
      if (err) {
        if (typeof err === 'string') err = new Error(err);
        err.preventDefault = () => {
          err._dontReject = true;
        };
        plugin.apply('onError', (err) => {
          throw new Error(err.stack || err);
        })(err, app._store.dispatch);
      }
    };
```
这是一个全局错误处理,返回了一个接收错误并处理的函数,并以 `err` 和 `app._store.dispatch` 为参数执行调用。

看一下 `plugin.apply` 的实现:

```js
  apply(key, defaultHandler) {
    const hooks = this.hooks;
	/* 通过 validApplyHooks 进行过滤, apply 方法只能应用在全局报错或者热更替上 */ 
    const  validApplyHooks = ['onError', 'onHmr'];
    invariant(validApplyHooks.indexOf(key) > -1, `plugin.apply: hook ${key} cannot be applied`);
	/* 从钩子中拿出挂载的回调函数 ,挂载动作见 use 部分*/
    const fns = hooks[key];

    return (...args) => {
		// 如果有回调执行回调
      if (fns.length) {
        for (const fn of fns) {
          fn(...args);
        }
		// 没有回调直接抛出错误
      } else if (defaultHandler) {
        defaultHandler(...args);
		
		/*
		这里 defaultHandler 为 (err) => {
          throw new Error(err.stack || err);
        }
		*/
      }
    };
  }
  ```

###  `sagaMiddleware`

下一行代码是:    

> `const sagaMiddleware = createSagaMiddleware();`

和 `redux-sagas` 的入门教程有点差异,因为正统的教程上添加 sagas 中间件的方法是: `createSagaMiddleware(...sagas)`

> sagas 为含有 saga 方法的 generator 函数数组。

但是 api 里确实还提到,还有一~~~招从天而降的掌法~~~种动态调用的方式:

>  `const task = sagaMiddleware.run(dynamicSaga)`

于是:

```js
	  const sagaMiddleware = createSagaMiddleware();
	  // ...
      const sagas = [];
      const reducers = {...initialReducer
      };
      for (const m of app._models) {
      	reducers[m.namespace] = getReducer(m.reducers, m.state);
      	if (m.effects) sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect')));
      }
      // ....

      store.runSaga = sagaMiddleware.run;
      // Run sagas
      sagas.forEach(sagaMiddleware.run);
```

### `sagas`

那么 sagas 是什么呢?

```js
    const {
      middleware: promiseMiddleware,
      resolve,
      reject,
    } = createPromiseMiddleware(app);
    app._getSaga = getSaga.bind(null, resolve, reject);

    const sagas = [];
 
    for (const m of app._models) {
      if (m.effects) sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect')));
    }
```

显然,sagas 是一个数组,里面的元素是用 `app._getSaga` 处理后的返回结果,而 `app._getSaga` 又和上面 createPromiseMiddleware 代理 app 后返回的对象有很大关系。

#### `createPromiseMiddleware`

createPromiseMiddleware 的代码[在此](https://github.com/dvajs/dva/blob/master/packages/dva-core/src/createPromiseMiddleware.js)。

如果看着觉得眼熟,那肯定不是因为看过 redux-promise 源码的缘故,:-p。

##### `middleware`

`middleware` 是一个 redux 的中间件,即在不影响 redux 本身功能的情况下为其添加了新特性的代码。redux 的中间件通过拦截 action 来实现其作用的。

```js
  const middleware = () => next => (action) => {
    const { type } = action;
    if (isEffect(type)) {
      return new Promise((resolve, reject) => {
		// .... resolve ,reject
      });
    } else {
      return next(action);
    }
  };
  
    function isEffect(type) {
		// dva 里 action 的 type 有固定格式: model.namespace/model.effects
		// const [namespace] = type.split(NAMESPACE_SEP); 是 es6 解构的写法
		// 等同于 const namespace = type.split(NAMESPACE_SEP)[0];
		// NAMESPACE_SEP 的值是 `/`
    	const [namespace] = type.split(NAMESPACE_SEP);
		// 根据 namespace 过滤出对应的 model
    	const model = app._models.filter(m => m.namespace === namespace)[0];
		// 如果 model 存在并且 model.effects[type] 也存在,那必然是 effects
    	if (model) {
    		if (model.effects && model.effects[type]) {
    			return true;
    		}
    	}

    	return false;
    }
  ```

>  const middleware = ({dispatch}) => next => (action) => {... return next(action)} 基本上是一个标准的中间件写法。在 return next(action) 之前可以对 action 做各种各样的操作。因为此中间件没用到 dispatch 方法,所以省略了。

本段代码的意思是,如果 dispatch 的 action 指向的是 model 里的 effects,那么返回一个 Promise 对象。此 Promise 的对象的解决( resolve )或者驳回方法 ( reject ) 放在 map 对象中。如果是非 effects (那就是 action 了),放行。

换句话说,middleware 拦截了指向 effects 的 action。

##### 神奇的 bind

bind 的作用是绑定新的对象,生成新函数是大家都知道概念。但是 bind 也可以提前设定好函数的某些参数生成新函数,等到最后一个参数确定时直接调用。

> JavaScript 的参数是怎么被调用的?[JavaScript 专题之函数柯里化](https://juejin.im/post/598d0b7ff265da3e1727c491)。作者:[冴羽](https://juejin.im/user/58e4b9b261ff4b006b3227f4)。文章来源:[掘金](https://juejin.im/timeline)

这段代码恰好就是 bind 的一种实践方式。

```js
  const map = {};

  const middleware = () => next => (action) => {
    const { type } = action;
    // ...
      return new Promise((resolve, reject) => {
        map[type] = {
          resolve: wrapped.bind(null, type, resolve),
          reject: wrapped.bind(null, type, reject),
        };
      });
	// ....
  };
  
  function wrapped(type, fn, args) {
    if (map[type]) delete map[type];
    fn(args);
  }

  function resolve(type, args) {
    if (map[type]) {
      map[type].resolve(args);
    }
  }

  function reject(type, args) {
    if (map[type]) {
      map[type].reject(args);
    }
  }
  
   return {
    middleware,
    resolve,
    reject,
  };
```
分析这段代码,dva 是这样做的:

1. 通过 `wrapped.bind(null, type, resolve)` 产生了一个新函数,并且赋值给匿名对象的 resolve 属性(reject 同理)。

> 1.1 wrap 接收三个参数,通过 bind 已经设定好了两个。`wrapped.bind(null, type, resolve)` 等同于 `wrap(type, resolve, xxx)`(**此处  `resolve` 是 Promise 对象中的**)。 

> 1.2 通过 bind 赋给匿名对象的 resolve 属性后,匿名对象.resolve(xxxx) 等同于 wrap(type, resolve, xxx),即 reslove(xxx)。

2. 使用 type 在 map 对象中保存此匿名对象,而 type 是 action 的 type,即 namespace/effects 的形式,方便之后进行调用。

3. return 出的 resolve 接收 type 和 args 两个参数。type 用来在 map 中寻找 1 里的匿名函数,args 用来像 1.2 里那样执行。

> 这样做的作用是:分离了 promise 与 promise 的执行。在函数的作用域外依然可以访问到函数的内部变量,换言之:闭包。

#### `getSaga`

导出的 `resolve` 与 `reject` 方法,通过 bind 先设置进了 `getSaga` (同时也赋给了 `app._getSaga`),sagas 最终也将 `getSaga` 的返回值放入了数组。

[getSaga 源码](https://github.com/dvajs/dva/blob/master/packages/dva-core/src/getSaga.js)

```js
export default function getSaga(resolve, reject, effects, model, onError, onEffect) {
  return function *() {
    for (const key in effects) {
      if (Object.prototype.hasOwnProperty.call(effects, key)) {
        const watcher = getWatcher(resolve, reject, key, effects[key], model, onError, onEffect);
		// 将 watcher 分离到另一个线程去执行
        const task = yield sagaEffects.fork(watcher);
		// 同时 fork 了一个线程,用于在 model 卸载后取消正在进行中的 task
		// `${model.namespace}/@@CANCEL_EFFECTS` 的发出动作在 index.js 的 start 方法中,unmodel 方法里。
        yield sagaEffects.fork(function *() {
          yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`);
          yield sagaEffects.cancel(task);
        });
      }
    }
  };
}
```
可以看到,`getSaga` 最终返回了一个 [generator 函数](http://www.ruanyifeng.com/blog/2015/04/generator.html)。

在该函数遍历了 **model 中 effects 属性** 的所有方法(注:同样是 generator 函数)。结合 `index.js` 里的 ` for (const m of app._models)`,该遍历针对所有的 model。

对于每一个 effect,getSaga 生成了一个 watcher ,并使用 saga 函数的 **fork** 将该函数切分到另一个单独的线程中去(生成了一个 task 对象)。同时为了方便对该线程进行控制,在此 fork 了一个 generator 函数。在该函数中拦截了取消 effect 的 action(事实上,应该是卸载effect 所在 model 的 action),一旦监听到则立刻取消分出去的 task 线程。

##### getWatcher

```js
function getWatcher(resolve, reject, key, _effect, model, onError, onEffect) {
  let effect = _effect;
  let type = 'takeEvery';
  let ms;

  if (Array.isArray(_effect)) {
	// effect 是数组而不是函数的情况下暂不考虑
  }

  function *sagaWithCatch(...args) {
		// .... sagaWithCatch 的逻辑
  }

  const sagaWithOnEffect = applyOnEffect(onEffect, sagaWithCatch, model, key);

  switch (type) {
    case 'watcher':
      return sagaWithCatch;
    case 'takeLatest':
      return function*() {
        yield takeLatest(key, sagaWithOnEffect);
      };
    case 'throttle':
      return function*() {
        yield throttle(ms, key, sagaWithOnEffect);
      };
    default:
      return function*() {
        yield takeEvery(key, sagaWithOnEffect);
      };
  }
}

function createEffects(model) {
	// createEffects(model) 的逻辑
}

function applyOnEffect(fns, effect, model, key) {
  for (const fn of fns) {
    effect = fn(effect, sagaEffects, model, key);
  }
  return effect;
}
```

先不考虑 effect 的属性是数组而不是方法的情况。

`getWatcher` 接收六个参数:
- `resolve/reject`: 中间件 `middleware` 的 res 和 rej 方法。
- `key`:经过 prefixNamespace 转义后的 effect 方法名,namespace/effect(也是调用 action 时的 type)。
-` _effect`:effects 中 key 属性所指向的 generator 函数。
- `model`: model
- `onError`: 之前定义过的捕获全局错误的方法
- `onEffect`:plugin.use 中传入的在触发 effect 时执行的回调函数(钩子函数)


`applyOnEffect` 对 effect 进行了动态代理,在保证 effect (即 `_effect`)正常调用的情况下,为期添加了 fns 的回调函数数组(即 `onEffect`)。使得在 effect 执行时, `onEffect` 内的每一个回调函数都可以被触发。

因为没有经过 effects 的属性是数组的情况,所以 `type` 的值是 `takeEvery`,也就是监听每一个发出的 action ,即 `getWatcher` 的返回值最终走的是 switch 的 default 选项:

```js
function*() {
        yield takeEvery(key, sagaWithOnEffect);
      };
	  
```
换句话说,每次发出指向 effects 的函数都会调用 `sagaWithOnEffect`。

根据 `const sagaWithOnEffect = applyOnEffect(onEffect, sagaWithCatch, model, key);` 的执行情况,如果 onEffect 的插件为空的情况下,`sagaWithOnEffect` 的值为 `sagaWithCatch`。

```js
  function *sagaWithCatch(...args) {
    try {
      yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@start` });
      const ret = yield effect(...args.concat(createEffects(model)));
      yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@end` });
      resolve(key, ret);
    } catch (e) {
      onError(e);
      if (!e._dontReject) {
        reject(key, e);
      }
    }
  }

```

在 `sagaWithOnEffect` 函数中,sagas 使用传入的参数(也就是 action)执行了对应的 model 中 对应的 effect 方法,同时将返回值使用之前保存在 map 里的 resolve 返回了其返回值。同时在执行 effect 方法的时候,将 saga 本身的所有方法(put、call、fork 等等)作为第二个参数,使用 `concat` 拼接在 action 的后面。在执行 effect 方法前,又发出了 start 和 end 两个 action,方便 onEffect 的插件进行拦截和调用。

因此,对于 `if (m.effects) sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect')));`。

1. dva 通过 `app._getSaga(m.effects, m, onError, plugin.get('onEffect'))` 返回了一个 genenrator 函数。
2. 在 genenrator 函数中手动 fork 出一个 watcher 函数的监听线程(当然也 fork 了取消线程的功能)。
3. 该函数(在普通状态下)是一个 takeEvery 的阻塞是线程,接收 2 个参数。第一个参数为监听的 action,第二个参数为监听到 action 后的回调函数。
4. (普通状态下)的回调函数,就是手动调用了 model 里 effects 中对应属性的函数。在此之前之后发出了 `start` 和 `end` 的 action,同时用之前 promise 中间件保存在 map 中的 resolve 方法返回了值。
5. 最后使用 sagas.forEach(sagaMiddleware.run) 启动了 watcher 的监听。

### store

现在已经有了针对异步数据流的解决办法,那么该创建 store 了。

正常情况的 redux 的 createStore 接收三个参数 reducer, initState,applyMiddleware(middlewares)。

不过 dva 提供了自己的 `createStore` 方法,用来组织一系列自己创建的参数。
```js
    // Create store
    const store = app._store = createStore({ // eslint-disable-line
      reducers: createReducer(),
      initialState: hooksAndOpts.initialState || {},
      plugin,
      createOpts,
      sagaMiddleware,
      promiseMiddleware,
    });
```

#### createReducer

```js
    function createReducer() {
      return reducerEnhancer(combineReducers({
        ...reducers,
        ...extraReducers,
        ...(app._store ? app._store.asyncReducers : {}),
      }));
    }
```

`createReducer` 实际上是用 plugin 里的 onReducer (如果有)扩展了 reducer 功能,对于 `const reducerEnhancer = plugin.get('onReducer');`,plugin 里的相关代码为:

```js
function getOnReducer(hook) {
  return function (reducer) {
    for (const reducerEnhancer of hook) {
      reducer = reducerEnhancer(reducer);
    }
    return reducer;
  };
}

```

> 如果有 onReducer 的插件,那么用 reducer 的插件扩展 reducer;否则直接返回 reducer。

combineReducers 中:
- 第一个 `...reducers` 是从 dva 里传入的 historyReducer,以及通过 ` reducers[m.namespace] = getReducer(m.reducers, m.state);` 剥离出的 model 中的 reducer
- 第二个参数为手动在 plugin 里添加的 extraReducers;
- 第三个参数为异步 reducer,主要是用于在 dva 运行以后动态加载 model 里的 reducer。


#### createStore


现在我们有了一个 combine 过的 reducer,有了 core 中创建的 sagaMiddleware 和 promiseMiddleware,还有了从 dva 中传入的 createOpts,现在可以正式创建 store 了。

> 从 dva 中传入的 createOpts 为 
```js
    setupMiddlewares(middlewares) {
      return [
        routerMiddleware(history),
        ...middlewares,
      ];
    },
```
> 用与把 redux-router 的中间件排在中间件的第一个。


虽然看起来很长,但是对于大多数普通用户来说,在未开启 redux 的调试插件,未传入额外的 onAction 以及 extraEnhancers 的情况下,上面的代码等价于:

```js
import { createStore, applyMiddleware, compose } from 'redux';
import flatten from 'flatten';
import invariant from 'invariant';
import window from 'global/window';
import { returnSelf, isArray } from './utils';

export default function ({
  reducers,
  initialState,
  plugin,
  sagaMiddleware,
  promiseMiddleware,
  createOpts: {
    setupMiddlewares = returnSelf,
  },
}) {

  const middlewares = setupMiddlewares([
    sagaMiddleware,
    promiseMiddleware
  ]);

  const enhancers = [
    applyMiddleware(...middlewares)
  ];

  return createStore(reducers, initialState, compose(...enhancers));
  // 对于 redux 中 的 compose 函数,在数组长度为 1  的情况下返回第一个元素。
  // compose(...enhancers) 等同于 applyMiddleware(...middlewares)
}

```

### 订阅

现在 dva 已经创建了 store,有了异步数据流加载方案,并且又做了一些其他的事情:

```js
    // Extend store
    store.runSaga = sagaMiddleware.run;
    store.asyncReducers = {};

    // Execute listeners when state is changed
    const listeners = plugin.get('onStateChange');
    for (const listener of listeners) {
      store.subscribe(() => {
        listener(store.getState());
      });
    }

    // Run sagas
    sagas.forEach(sagaMiddleware.run);
```

- 手动运行 getSaga 里返回的 watcer 函数。
- 判断如果有 onStateChange 的 plugin 也手动运行一下。

model 里的 state、effect、reducer 已经实现了,就缺最后的订阅 subscription 部分。

```js
    // Setup app
    setupApp(app);

    // Run subscriptions
    const unlisteners = {};
    for (const model of this._models) {
      if (model.subscriptions) {
        unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError);
      }
    }
```

setupApp(app) 是从 dva 里传过来的,主要是使用 patchHistory 函数代理 history.linsten,即强化了 redux 和 router 的联系,是的路径变化可以引起 state 的变化,进而听过监听 state 的变化来触发回调。
> 这也是 core 中唯一使用 this 的地方,逼得 dva 中必须使用 oldStart.call(app) 来进行调用。

#### runSubscription

这是 runSubscription 的代码

```js
export function run(subs, model, app, onError) {
  const funcs = [];
  const nonFuncs = [];
  for (const key in subs) {
    if (Object.prototype.hasOwnProperty.call(subs, key)) {
      const sub = subs[key];
      const unlistener = sub({
        dispatch: prefixedDispatch(app._store.dispatch, model),
        history: app._history,
      }, onError);
      if (isFunction(unlistener)) {
        funcs.push(unlistener);
      } else {
        nonFuncs.push(key);
      }
    }
  }
  return { funcs, nonFuncs };
}
```
- 第一个参数为 model 中的 subscription 对象。
- 第二个参数为对应的 model
- 第三个参数为 core 里创建的 app
- 第四个参数为全局异常捕获的 onError

1. `Object.prototype.hasOwnProperty.call(subs, key)` 
还是使用原型方法判断 key 是不是 subs 的自有属性。

2. 如果是自由属性,那么拿到属性对应的值(是一个 function)

3. 调用该 function,传入 dispatch 和 history 属性。history 就是经过 redux-router 强化过的 history,而 dispatch,也就是 `prefixedDispatch(app._store.dispatch, model)`

```js
export default function prefixedDispatch(dispatch, model) {
  return (action) => {
	// 断言检测
    return dispatch({ ...action, type: prefixType(type, model) });
  };
}

```

实际上是用将 action 里的 type 添加了 `${model.namespance}/` 的前缀。

自此,model 中的四大组件全部完毕,完成了 dva 的数据层处理。


================================================
FILE: docs/knowledgemap/README.md
================================================
---
sidebarDepth: 2
---

# 知识地图

- [Read "the dva.js Knowledgemap" in English](https://github.com/dvajs/dva-knowledgemap/blob/master/README_en.md)
- ["the dva.js Knowledgemap" 日本語版](https://github.com/dvajs/dva-knowledgemap/blob/master/README_ja.md)

---

不知大家学 react 或 dva 时会不会有这样的疑惑:

- es6 特性那么多,我需要全部学会吗?
- react component 有 3 种写法,我需要全部学会吗?
- reducer 的增删改应该怎么写?
- 怎么做全局/局部的错误处理?
- 怎么发异步请求?
- 怎么处理复杂的异步业务逻辑?
- 怎么配置路由?
- ...

这篇文档梳理了基于 [dva-cli](https://github.com/dvajs/dva-cli) 使用 [dva](https://github.com/dvajs/dva) 的最小知识集,让你可以用最少的时间掌握创建类似 [dva-hackernews](https://github.com/dvajs/dva-hackernews) 应用的全部知识,并且不需要掌握额外的冗余知识。

## JavaScript 语言

### 变量声明

#### const 和 let

不要用 `var`,而是用 `const` 和 `let`,分别表示常量和变量。不同于 `var` 的函数作用域,`const` 和 `let` 都是块级作用域。

```javascript
const DELAY = 1000;

let count = 0;
count = count + 1;
```

#### 模板字符串

模板字符串提供了另一种做字符串组合的方法。

```javascript
const user = 'world';
console.log(`hello ${user}`);  // hello world

// 多行
const content = `
  Hello ${firstName},
  Thanks for ordering ${qty} tickets to ${event}.
`;
```

#### 默认参数

```javascript
function logActivity(activity = 'skiing') {
  console.log(activity);
}

logActivity();  // skiing
```

### 箭头函数

函数的快捷写法,不需要通过 `function` 关键字创建函数,并且还可以省略 `return` 关键字。

同时,箭头函数还会继承当前上下文的 `this` 关键字。

比如:

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

等同于:

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

### 模块的 Import 和 Export

`import` 用于引入模块,`export` 用于导出模块。

比如:

```javascript
// 引入全部
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 {};
```

### ES6 对象和数组

#### 析构赋值

析构赋值让我们从 Object 或 Array 里取部分数据存为变量。

```javascript
// 对象
const user = { name: 'guanguan', age: 2 };
const { name, age } = user;
console.log(`${name} : ${age}`);  // guanguan : 2

// 数组
const arr = [1, 2];
const [foo, bar] = arr;
console.log(foo);  // 1
```

我们也可以析构传入的函数参数。

```javascript
const add = (state, { payload }) => {
  return state.concat(payload);
};
```

析构时还可以配 alias,让代码更具有语义。

```javascript
const add = (state, { payload: todo }) => {
  return state.concat(todo);
};
```

#### 对象字面量改进

这是析构的反向操作,用于重新组织一个 Object 。

```javascript
const name = 'duoduo';
const age = 8;

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

定义对象方法时,还可以省去 `function` 关键字。

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

#### Spread Operator

Spread Operator 即 3 个点 `...`,有几种不同的使用方法。

可用于组装数组。

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

也可用于获取数组的部分项。

```javascript
const arr = ['a', 'b', 'c'];
const [first, ...rest] = arr;
rest;  // ['b', 'c']

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

还可收集函数参数为数组。

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

代替 apply。

```javascript
function foo(x, y, z) {}
const args = [1,2,3];

// 下面两句效果相同
foo.apply(null, args);
foo(...args);
```

对于 Object 而言,用于组合成新的 Object 。(ES2017 stage-2 proposal)

```javascript
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](#spread-attributes)。

### Promises

Promise 用于更优雅地处理异步请求。比如发起异步请求:

```javascript
fetch('/api/todos')
  .then(res => res.json())
  .then(data => ({ data }))
  .catch(err => ({ err }));
```

定义 Promise 。

```javascript
const delay = (timeout) => {
  return new Promise(resolve => {
    setTimeout(resolve, timeout);
  });
};

delay(1000).then(_ => {
  console.log('executed');
});
```

### Generators

dva 的 effects 是通过 generator 组织的。Generator 返回的是迭代器,通过 `yield` 关键字实现暂停功能。

这是一个典型的 dva effect,通过 `yield` 把异步逻辑通过同步的方式组织起来。

```javascript
app.model({
  namespace: 'todos',
  effects: {
    *addRemote({ payload: todo }, { put, call }) {
      yield call(addTodo, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
});
```

## React Component

###  Stateless Functional Components

React Component 有 3 种定义方式,分别是 `React.createClass`, `class` 和 `Stateless Functional Component`。推荐尽量使用最后一种,保持简洁和无状态。这是函数,不是 Object,没有 `this` 作用域,是 pure function。

比如定义 App Component 。

```javascript
function App(props) {
  function handleClick() {
    props.dispatch({ type: 'app/create' });
  }
  return <div onClick={handleClick}>${props.name}</div>
}
```

等同于:

```javascript
class App extends React.Component {
  handleClick() {
    this.props.dispatch({ type: 'app/create' });
  }
  render() {
    return <div onClick={this.handleClick.bind(this)}>${this.props.name}</div>
  }
}
```

### JSX

#### Component 嵌套

类似 HTML,JSX 里可以给组件添加子组件。

```html
<App>
  <Header />
  <MainContent />
  <Footer />
</App>
```

#### className

`class` 是保留词,所以添加样式时,需用 `className` 代替 `class` 。

```html
<h1 className="fancy">Hello dva</h1>
```

#### JavaScript 表达式

JavaScript 表达式需要用 `{}` 括起来,会执行并返回结果。

比如:

```javascript
<h1>{ this.props.title }</h1>
```

#### Mapping Arrays to JSX

可以把数组映射为 JSX 元素列表。

```javascript
<ul>
  { this.props.todos.map((todo, i) => <li key={i}>{todo}</li>) }
</ul>
```

#### 注释

尽量别用 `//` 做单行注释。

```javascript
<h1>
  {/* multiline comment */}
  {/*
    multi
    line
    comment
    */}
  {
    // single line
  }
  Hello
</h1>
```

#### Spread Attributes

这是 JSX 从 ECMAScript6 借鉴过来的很有用的特性,用于扩充组件 props 。

比如:

```javascript
const attrs = {
  href: 'http://example.org',
  target: '_blank',
};
<a {...attrs}>Hello</a>
```

等同于

```javascript
const attrs = {
  href: 'http://example.org',
  target: '_blank',
};
<a href={attrs.href} target={attrs.target}>Hello</a>
```

### Props

数据处理在 React 中是非常重要的概念之一,分别可以通过 props, state 和 context 来处理数据。而在 dva 应用里,你只需关心 props 。

#### propTypes

JavaScript 是弱类型语言,所以请尽量声明 propTypes 对 props 进行校验,以减少不必要的问题。

```javascript
function App(props) {
  return <div>{props.name}</div>;
}
App.propTypes = {
  name: React.PropTypes.string.isRequired,
};
```

内置的 prop type 有:

- PropTypes.array
- PropTypes.bool
- PropTypes.func
- PropTypes.number
- PropTypes.object
- PropTypes.string

#### 往下传数据

![](https://zos.alipayobjects.com/rmsportal/NAzeMyUoPMqxfRv.png)

#### 往上传数据

![](https://zos.alipayobjects.com/rmsportal/fiKKgDGuEJfSvxv.png)

### CSS Modules

<img src="https://zos.alipayobjects.com/rmsportal/mHVRpjNYhVuFdsS.png" width="150" style="background:#fff;" />

#### 理解 CSS Modules

一张图理解 CSS Modules 的工作原理:

![](https://zos.alipayobjects.com/rmsportal/SWBwWTbZKqxwEPq.png)

`button` class 在构建之后会被重命名为 `ProductList_button_1FU0u` 。`button` 是 local name,而 `ProductList_button_1FU0u` 是 global name 。**你可以用简短的描述性名字,而不需要关心命名冲突问题。**

然后你要做的全部事情就是在 css/less 文件里写 `.button {...}`,并在组件里通过 `styles.button` 来引用他。

#### 定义全局 CSS

CSS Modules 默认是局部作用域的,想要声明一个全局规则,可用 `:global` 语法。

比如:

```css
.title {
  color: red;
}
:global(.title) {
  color: green;
}
```

然后在引用的时候:

```javascript
<App className={styles.title} /> // red
<App className="title" />        // green
```

#### `classnames` Package

在一些复杂的场景中,一个元素可能对应多个 className,而每个 className 又基于一些条件来决定是否出现。这时,[classnames](https://github.com/JedWatson/classnames) 这个库就非常有用。

```javascript
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 组合:

```javascript
<App type="submit" /> // btn btnLarge
<App type="edit" />   // btn btnSmall
```

## Reducer

reducer 是一个函数,接受 state 和 action,返回老的或新的 state 。即:`(state, action) => state`

### 增删改

以 todos 为例。

```javascript
app.model({
  namespace: 'todos',
  state: [],
  reducers: {
    add(state, { payload: todo }) {
      return state.concat(todo);
    },
    remove(state, { payload: id }) {
      return state.filter(todo => todo.id !== id);
    },
    update(state, { payload: updatedTodo }) {
      return state.map(todo => {
        if (todo.id === updatedTodo.id) {
          return { ...todo, ...updatedTodo };
        } else {
          return todo;
        }
      });
    },
  },
};
```

### 嵌套数据的增删改

建议最多一层嵌套,以保持 state 的扁平化,深层嵌套会让 reducer 很难写和难以维护。

```javascript
app.model({
  namespace: 'app',
  state: {
    todos: [],
    loading: false,
  },
  reducers: {
    add(state, { payload: todo }) {
      const todos = state.todos.concat(todo);
      return { ...state, todos };
    },
  },
});
```

下面是深层嵌套的例子,应尽量避免。

```javascript
app.model({
  namespace: 'app',
  state: {
    a: {
      b: {
        todos: [],
        loading: false,
      },
    },
  },
  reducers: {
    add(state, { payload: todo }) {
      const todos = state.a.b.todos.concat(todo);
      const b = { ...state.a.b, todos };
      const a = { ...state.a, b };
      return { ...state, a };
    },
  },
});
```

## Effect

示例:

```javascript
app.model({
  namespace: 'todos',
  effects: {
    *addRemote({ payload: todo }, { put, call }) {
      yield call(addTodo, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
});
```

### Effects

#### put

用于触发 action 。

```javascript
yield put({ type: 'todos/add', payload: 'Learn Dva' });
```

#### call

用于调用异步逻辑,支持 promise 。

```javascript
const result = yield call(fetch, '/todos');
```

#### select

用于从 state 里获取数据。

```javascript
const todos = yield select(state => state.todos);
```

### 错误处理

#### 全局错误处理

dva 里,effects 和 subscriptions 的抛错全部会走 `onError` hook,所以可以在 `onError` 里统一处理错误。

```javascript
const app = dva({
  onError(e, dispatch) {
    console.log(e.message);
  },
});
```

然后 effects 里的抛错和 reject 的 promise 就都会被捕获到了。

#### 本地错误处理

如果需要对某些 effects 的错误进行特殊处理,需要在 effect 内部加 `try catch` 。

```javascript
app.model({
  effects: {
    *addRemote() {
      try {
        // Your Code Here
      } catch(e) {
        console.log(e.message);
      }
    },
  },
});
```

### 异步请求

异步请求基于 whatwg-fetch,API 详见:https://github.com/github/fetch

#### GET 和 POST

```javascript
import request from '../util/request';

// GET
request('/api/todos');

// POST
request('/api/todos', {
  method: 'POST',
  body: JSON.stringify({ a: 1 }),
});
```

#### 统一错误处理

假如约定后台返回以下格式时,做统一的错误处理。

```javascript
{
  status: 'error',
  message: '',
}
```

编辑 `utils/request.js`,加入以下中间件:

```javascript
function parseErrorMessage({ data }) {
  const { status, message } = data;
  if (status === 'error') {
    throw new Error(message);
  }
  return { data };
}
```

然后,这类错误就会走到 `onError` hook 里。

## Subscription

`subscriptions` 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。格式为 `({ dispatch, history }) => unsubscribe` 。

### 异步数据初始化

比如:当用户进入 `/users` 页面时,触发 action `users/fetch` 加载用户数据。

```javascript
app.model({
  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(({ pathname }) => {
        if (pathname === '/users') {
          dispatch({
            type: 'users/fetch',
          });
        }
      });
    },
  },
});
```

#### `path-to-regexp` Package

如果 url 规则比较复杂,比如 `/users/:userId/search`,那么匹配和 userId 的获取都会比较麻烦。这时推荐用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 简化这部分逻辑。

```javascript
import pathToRegexp from 'path-to-regexp';

// in subscription
const match = pathToRegexp('/users/:userId/search').exec(pathname);
if (match) {
  const userId = match[1];
  // dispatch action with userId
}
```

## Router

### Route Components

Route Components 是指 `./src/routes/` 目录下的文件,他们是 `./src/router.js` 里匹配的 Component。

#### 通过 connect 绑定数据

比如:

```javascript
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

更多详见:[react-router](https://github.com/reactjs/react-router/blob/master/docs/API.md#injected-props)

### 基于 action 进行页面跳转

```javascript
import { routerRedux } from 'dva/router';

// Inside Effects
yield put(routerRedux.push('/logout'));

// Outside Effects
dispatch(routerRedux.push('/logout'));

// With query
routerRedux.push({
  pathname: '/logout',
  query: {
    page: 2,
  },
});
```

除 `push(location)` 外还有更多方法,详见 [react-router-redux](https://github.com/reactjs/react-router-redux#pushlocation-replacelocation-gonumber-goback-goforward)

## dva 配置

### Redux Middleware

比如要添加 redux-logger 中间件:

```javascript
import createLogger from 'redux-logger';
const app = dva({
  onAction: createLogger(),
});
```

注:onAction 支持数组,可同时传入多个中间件。

### 切换 history 为 browserHistory

先安装 history 依赖,

```bash
$ npm install --save history
```

然后修改入口文件, 

```javascript
import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});
```

## 工具

### 通过 dva-cli 创建项目

先安装 dva-cli 。

```bash
$ npm install dva-cli -g
```

然后创建项目。

```bash
$ dva new myapp
```

最后,进入目录并启动。

```bash
$ cd myapp
$ npm start
```

### 通过 umi 使用 dva

先安装 dva-cli@next 。

```bash
$ npm install dva-cli@next -g
```

然后创建项目。

```bash
$ dva new myapp
```

最后,进入目录并启动。

```bash
$ cd myapp
$ npm start
```

详见[和 dva 一起用@umijs.org](https://umijs.org/guide/with-dva.html)。


================================================
FILE: examples/func-test/.eslintrc
================================================
{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "rules": {
    "generator-star-spacing": [0],
    "consistent-return": [0],
    "react/forbid-prop-types": [0],
    "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
    "global-require": [1],
    "import/prefer-default-export": [0],
    "react/jsx-no-bind": [0],
    "react/prop-types": [0],
    "react/prefer-stateless-function": [0],
    "no-else-return": [0],
    "no-restricted-syntax": [0],
    "import/no-extraneous-dependencies": [0],
    "no-use-before-define": [0],
    "jsx-a11y/no-static-element-interactions": [0],
    "no-nested-ternary": [0],
    "arrow-body-style": [0],
    "import/extensions": [0],
    "no-bitwise": [0],
    "no-cond-assign": [0],
    "import/no-unresolved": [0],
    "require-yield": [1]
  },
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true
    }
  }
}


================================================
FILE: examples/func-test/.roadhogrc
================================================
{
  "entry": "src/index.js",
  "env": {
    "development": {
      "extraBabelPlugins": [
        "dva-hmr",
        "transform-runtime"
      ]
    },
    "production": {
      "extraBabelPlugins": [
        "transform-runtime"
      ]
    }
  }
}


================================================
FILE: examples/func-test/.roadhogrc.mock.js
================================================
export default {};


================================================
FILE: examples/func-test/package.json
================================================
{
  "name": "dva-example",
  "private": true,
  "scripts": {
    "start": "roadhog server",
    "build": "roadhog build",
    "lint": "eslint --ext .js src test"
  },
  "engines": {
    "install-node": "6.9.2"
  },
  "dependencies": {
    "babel-runtime": "^6.9.2",
    "dva": "^2.0.0-0",
    "react": "^16.0.0",
    "react-dom": "^16.0.0"
  },
  "devDependencies": {
    "babel-eslint": "^7.1.1",
    "babel-plugin-dva-hmr": "^0.3.2",
    "babel-plugin-module-alias": "^1.6.0",
    "babel-plugin-transform-runtime": "^6.9.0",
    "eslint": "^3.12.2",
    "eslint-config-airbnb": "^13.0.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^2.2.3",
    "eslint-plugin-react": "^6.8.0",
    "expect": "^1.20.2",
    "redbox-react": "^1.3.2",
    "roadhog": "^1.2.0"
  }
}


================================================
FILE: examples/func-test/src/components/Example.js
================================================
import React from 'react';

const Example = () => {
  return <div>Example</div>;
};

Example.propTypes = {};

export default Example;


================================================
FILE: examples/func-test/src/index.css
================================================

html, body, :global(#root) {
  height: 100%;
}



================================================
FILE: examples/func-test/src/index.ejs
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Dva Demo</title>
  <link rel="stylesheet" href="index.css" />
</head>
<body>

<div id="root"></div>

<script src="index.js"></script>

</body>
</html>


================================================
FILE: examples/func-test/src/index.js
================================================
import dva from 'dva';
import './index.css';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model
app.model(require('./models/example'));

// 4. Router
app.router(require('./router'));

// 5. Start
app.start('#root');


================================================
FILE: examples/func-test/src/models/example.js
================================================
export default {
  namespace: 'example',

  state: {},

  subscriptions: {
    setup({ dispatch, history }) {
      // eslint-disable-line
      history.listen(location => {
        console.log(1, location);
      });
    },
  },

  effects: {
    *fetch({ payload }, { call, put }) {
      // eslint-disable-line
      yield put({ type: 'save' });
    },
  },

  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
  },
};


================================================
FILE: examples/func-test/src/router.js
================================================
import React from 'react';
import { routerRedux, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';

const { ConnectedRouter } = routerRedux;

function RouterConfig({ history }) {
  return (
    <ConnectedRouter history={history}>
      <Route path="/" exact component={IndexPage} />
    </ConnectedRouter>
  );
}

export default RouterConfig;


================================================
FILE: examples/func-test/src/routes/IndexPage.css
================================================

.normal {
  font-family: Georgia, sans-serif;
  margin-top: 3em;
  text-align: center;
}

.title {
  font-size: 2.5rem;
  font-weight: normal;
  letter-spacing: -1px;
}

.welcome {
  height: 328px;
  background: url(../assets/yay.jpg) no-repeat center 0;
  background-size: 388px 328px;
}

.list {
  font-size: 1.2em;
  margin-top: 1.8em;
  list-style: none;
  line-height: 1.5em;
}

.list code {
  background: #f7f7f7;
}


================================================
FILE: examples/func-test/src/routes/IndexPage.js
================================================
import React from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';

function IndexPage() {
  return (
    <div className={styles.normal}>
      <h1 className={styles.title}>Yay! Welcome to dva!</h1>
      <div className={styles.welcome} />
      <ul className={styles.list}>
        <li>
          To get started, edit <code>src/index.js</code> and save to reload.
        </li>
        <li>
          <a href="https://github.com/dvajs/dva-docs/blob/master/v1/en-us/getting-started.md">
            Getting Started
          </a>
        </li>
      </ul>
    </div>
  );
}

IndexPage.propTypes = {};

export default connect()(IndexPage);


================================================
FILE: examples/func-test/src/services/example.js
================================================
import request from '../utils/request';

export async function query() {
  return request('/api/users');
}


================================================
FILE: examples/func-test/src/utils/request.js
================================================
import fetch from 'dva/fetch';

function parseJSON(response) {
  return response.json();
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default function request(url, options) {
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(data => ({ data }))
    .catch(err => ({ err }));
}


================================================
FILE: examples/user-dashboard/.editorconfig
================================================
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab


================================================
FILE: examples/user-dashboard/.eslintrc
================================================
{
  "extends": "umi"
}

================================================
FILE: examples/user-dashboard/.gitignore
================================================
dist
node_modules
.DS_Store

.idea/


================================================
FILE: examples/user-dashboard/.umirc.js
================================================
export default {
  plugins: ['umi-plugin-dva'],
};


================================================
FILE: examples/user-dashboard/.webpackrc
================================================
{
  "theme": {
    "@primary-color": "#dc6aac",
    "@link-color": "#dc6aac",
    "@border-radius-base": "2px",
    "@font-size-base": "16px",
    "@line-height-base": "1.2"
  },
  "proxy": {
    "/api": {
      "target": "http://jsonplaceholder.typicode.com/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    }
  }
}


================================================
FILE: examples/user-dashboard/README.md
================================================
# dva-example-user-dashboard

详见[《12 步 30 分钟,完成用户管理的 CURD 应用 (react+dva+antd)》](https://github.com/sorrycc/blog/issues/18)。

---

<p align="center">
  <img src="https://zos.alipayobjects.com/rmsportal/bmkNCEoluwGaeGjYjInf.png" />
</p>

## Getting Started
Install dependencies.

```bash
$ npm install
```

Start server.

```bash
$ npm start
```

If success, app will be open in your default browser automatically.


================================================
FILE: examples/user-dashboard/package.json
================================================
{
  "name": "dva-example-user-dashboard",
  "private": true,
  "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "test": "umi test",
    "lint": "eslint --ext .js src test",
    "precommit": "npm run lint"
  },
  "dependencies": {
    "umi": "^1.0.0-0",
    "umi-plugin-dva": "^0.1.0"
  },
  "devDependencies": {
    "eslint": "^4.17.0",
    "eslint-config-umi": "^0.1.2",
    "eslint-plugin-flowtype": "^2.42.0",
    "eslint-plugin-import": "^2.8.0",
    "eslint-plugin-jsx-a11y": "^5.1.1",
    "eslint-plugin-react": "^7.6.1",
    "husky": "^0.13.0"
  }
}


================================================
FILE: examples/user-dashboard/src/constants.js
================================================
export const PAGE_SIZE = 3;


================================================
FILE: examples/user-dashboard/src/global.css
================================================

html, body, :global(#root) {
  height: 100%;
}


================================================
FILE: examples/user-dashboard/src/layouts/Header.js
================================================
import React from 'react';
import { Menu, Icon } from 'antd';
import Link from 'umi/link';
import withRouter from 'umi/withRouter';

function Header({ location }) {
  return (
    <Menu selectedKeys={[location.pathname]} mode="horizontal" theme="dark">
      <Menu.Item key="/users">
        <Link to="/users">
          <Icon type="bars" />Users
        </Link>
      </Menu.Item>
      <Menu.Item key="/">
        <Link to="/">
          <Icon type="home" />Home
        </Link>
      </Menu.Item>
      <Menu.Item key="/404">
        <Link to="/page-you-dont-know">
          <Icon type="frown-circle" />404
        </Link>
      </Menu.Item>
      <Menu.Item key="/antd">
        <a href="https://github.com/dvajs/dva">dva</a>
      </Menu.Item>
    </Menu>
  );
}

export default withRouter(Header);


================================================
FILE: examples/user-dashboard/src/layouts/index.css
================================================

.normal {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.content {
  flex: 1;
  display: flex;
}

.main {
  padding: 0 8px;
  flex: 1 0 auto;
}


================================================
FILE: examples/user-dashboard/src/layouts/index.js
================================================
import React from 'react';
import styles from './index.css';
import Header from './Header';

function MainLayout({ children, location }) {
  return (
    <div className={styles.normal}>
      <Header location={location} />
      <div className={styles.content}>
        <div className={styles.main}>{children}</div>
      </div>
    </div>
  );
}

export default MainLayout;


================================================
FILE: examples/user-dashboard/src/pages/index.css
================================================

.normal {
  font-family: Georgia, sans-serif;
  margin-top: 3em;
  text-align: center;
}

.title {
  font-size: 2.5rem;
  font-weight: normal;
  letter-spacing: -1px;
}

.welcome {
  height: 328px;
  background: url(../assets/yay.jpg) no-repeat center 0;
  background-size: 388px 328px;
}

.list {
  font-size: 1.2em;
  margin-top: 1.8em;
  list-style: none;
  line-height: 1.5em;
}

.list code {
  background: #f7f7f7;
}


================================================
FILE: examples/user-dashboard/src/pages/index.js
================================================
import React from 'react';
import styles from './index.css';

function IndexPage() {
  return (
    <div className={styles.normal}>
      <h1 className={styles.title}>Yay! Welcome to dva!</h1>
      <div className={styles.welcome} />
      <ul className={styles.list}>
        <li>
          To get started, edit <code>src/index.js</code> and save to reload.
        </li>
        <li>
          <a href="https://github.com/dvajs/dva-docs/blob/master/v1/en-us/getting-started.md">
            Getting Started
          </a>
        </li>
      </ul>
    </div>
  );
}

export default IndexPage;


================================================
FILE: examples/user-dashboard/src/pages/users/components/Users/UserModal.js
================================================
import React, { Component } from 'react';
import { Modal, Form, Input } from 'antd';

const FormItem = Form.Item;

class UserEditModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    };
  }

  showModelHandler = e => {
    if (e) e.stopPropagation();
    this.setState({
      visible: true,
    });
  };

  hideModelHandler = () => {
    this.setState({
      visible: false,
    });
  };

  okHandler = () => {
    const { onOk } = this.props;
    this.props.form.validateFields((err, values) => {
      if (!err) {
        onOk(values);
        this.hideModelHandler();
      }
    });
  };

  render() {
    const { children } = this.props;
    const { getFieldDecorator } = this.props.form;
    const { name, email, website } = this.props.record;
    const formItemLayout = {
      labelCol: { span: 6 },
      wrapperCol: { span: 14 },
    };

    return (
      <span>
        <span onClick={this.showModelHandler}>{children}</span>
        <Modal
          title="Edit User"
          visible={this.state.visible}
          onOk={this.okHandler}
          onCancel={this.hideModelHandler}
        >
          <Form layout="horizontal" onSubmit={this.okHandler}>
            <FormItem {...formItemLayout} label="Name">
              {getFieldDecorator('name', {
                initialValue: name,
              })(<Input />)}
            </FormItem>
            <FormItem {...formItemLayout} label="Email">
              {getFieldDecorator('email', {
                initialValue: email,
              })(<Input />)}
            </FormItem>
            <FormItem {...formItemLayout} label="Website">
              {getFieldDecorator('website', {
                initialValue: website,
              })(<Input />)}
            </FormItem>
          </Form>
        </Modal>
      </span>
    );
  }
}

export default Form.create()(UserEditModal);


================================================
FILE: examples/user-dashboard/src/pages/users/components/Users/Users.css
================================================

.normal {
}

.create {
  margin-bottom: 1.5em;
}

.operation a {
  margin: 0 .5em;
}


================================================
FILE: examples/user-dashboard/src/pages/users/components/Users/Users.js
================================================
import React from 'react';
import { connect } from 'dva';
import { Table, Pagination, Popconfirm, Button } from 'antd';
import { routerRedux } from 'dva/router';
import styles from './Users.css';
import { PAGE_SIZE } from '../../../../constants';
import UserModal from './UserModal';

function Users({ dispatch, list: dataSource, loading, total, page: current }) {
  function deleteHandler(id) {
    dispatch({
      type: 'users/remove',
      payload: id,
    });
  }

  function pageChangeHandler(page) {
    dispatch(
      routerRedux.push({
        pathname: '/users',
        query: { page },
      })
    );
  }

  function editHandler(id, values) {
    dispatch({
      type: 'users/patch',
      payload: { id, values },
    });
  }

  function createHandler(values) {
    dispatch({
      type: 'users/create',
      payload: values,
    });
  }

  const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
      render: text => <a href="">{text}</a>,
    },
    {
      title: 'Email',
      dataIndex: 'email',
      key: 'email',
    },
    {
      title: 'Website',
      dataIndex: 'website',
      key: 'website',
    },
    {
      title: 'Operation',
      key: 'operation',
      render: (text, record) => (
        <span className={styles.operation}>
          <UserModal record={record} onOk={editHandler.bind(null, record.id)}>
            <a>Edit</a>
          </UserModal>
          <Popconfirm
            title="Confirm to delete?"
            onConfirm={deleteHandler.bind(null, record.id)}
          >
            <a href="">Delete</a>
          </Popconfirm>
        </span>
      ),
    },
  ];

  return (
    <div className={styles.normal}>
      <div>
        <div className={styles.create}>
          <UserModal record={{}} onOk={createHandler}>
            <Button type="primary">Create User</Button>
          </UserModal>
        </div>
        <Table
          columns={columns}
          dataSource={dataSource}
          loading={loading}
          rowKey={record => record.id}
          pagination={false}
        />
        <Pagination
          className="ant-table-pagination"
          total={total}
          current={current}
          pageSize={PAGE_SIZE}
          onChange={pageChangeHandler}
        />
      </div>
    </div>
  );
}

function mapStateToProps(state) {
  const { list, total, page } = state.users;
  return {
    loading: state.loading.models.users,
    list,
    total,
    page,
  };
}

export default connect(mapStateToProps)(Users);


================================================
FILE: examples/user-dashboard/src/pages/users/models/users.js
================================================
import * as usersService from '../services/users';

export default {
  namespace: 'users',
  state: {
    list: [],
    total: null,
    page: null,
  },
  reducers: {
    save(state, { payload: { data: list, total, page } }) {
      return { ...state, list, total, page };
    },
  },
  effects: {
    *fetch({ payload: { page = 1 } }, { call, put }) {
      const { data, headers } = yield call(usersService.fetch, { page });
      yield put({
        type: 'save',
        payload: {
          data,
          total: parseInt(headers['x-total-count'], 10),
          page: parseInt(page, 10),
        },
      });
    },
    *remove({ payload: id }, { call, put }) {
      yield call(usersService.remove, id);
      yield put({ type: 'reload' });
    },
    *patch({ payload: { id, values } }, { call, put }) {
      yield call(usersService.patch, id, values);
      yield put({ type: 'reload' });
    },
    *create({ payload: values }, { call, put }) {
      yield call(usersService.create, values);
      yield put({ type: 'reload' });
    },
    *reload(action, { put, select }) {
      const page = yield select(state => state.users.page);
      yield put({ type: 'fetch', payload: { page } });
    },
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname, query }) => {
        if (pathname === '/users') {
          dispatch({ type: 'fetch', payload: query });
        }
      });
    },
  },
};


================================================
FILE: examples/user-dashboard/src/pages/users/page.css
================================================

.normal {
  width: 900px;
  margin: 3em auto 0;
}


================================================
FILE: examples/user-dashboard/src/pages/users/page.js
================================================
import React from 'react';
import styles from './page.css';
import UsersComponent from './components/Users/Users';

function Users() {
  return (
    <div className={styles.normal}>
      <UsersComponent />
    </div>
  );
}

export default Users;


================================================
FILE: examples/user-dashboard/src/pages/users/services/users.js
================================================
import request from '../../../utils/request';
import { PAGE_SIZE } from '../../../constants';

export function fetch({ page }) {
  return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
}

export function remove(id) {
  return request(`/api/users/${id}`, {
    method: 'DELETE',
  });
}

export function patch(id, values) {
  return request(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(values),
  });
}

export function create(values) {
  return request('/api/users', {
    method: 'POST',
    body: JSON.stringify(values),
  });
}


================================================
FILE: examples/user-dashboard/src/plugins/onError.js
================================================
import { message } from 'antd';

const ERROR_MSG_DURATION = 3; // 3 秒

export default {
  onError(e) {
    message.error(e.message, ERROR_MSG_DURATION);
  },
};


================================================
FILE: examples/user-dashboard/src/utils/request.js
================================================
import fetch from 'dva/fetch';

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
async function request(url, options) {
  const response = await fetch(url, options);

  checkStatus(response);

  const data = await response.json();

  const ret = {
    data,
    headers: {},
  };

  if (response.headers.get('x-total-count')) {
    ret.headers['x-total-count'] = response.headers.get('x-total-count');
  }

  return ret;
}

export default request;


================================================
FILE: examples/with-immer/.umirc.js
================================================
export default {
  plugins: ['umi-plugin-dva'],
};


================================================
FILE: examples/with-immer/dva.js
================================================
import useImmer from 'dva-immer';

export function config() {
  return {
    ...useImmer(),
  };
}


================================================
FILE: examples/with-immer/model.js
================================================
export default {
  namespace: 'count',
  state: {
    a: {
      b: {
        c: {
          count: 0,
        },
      },
    },
  },
  reducers: {
    add(state) {
      state.a.b.c.count += 1;
    },
    setNewProp(state) {
      state.newProp = 'hi new prop';
    },
  },
};


================================================
FILE: examples/with-immer/package.json
================================================
{
  "dependencies": {
    "umi": "*",
    "umi-plugin-dva": "*",
    "dva-immer": "*"
  }
}


================================================
FILE: examples/with-immer/pages/index.js
================================================
import { connect } from 'dva';
import { Button } from 'antd-mobile';

function App({ count, newProp, dispatch }) {
  return (
    <div>
      <h1>Count: {count}</h1>
      <h1>state.newProp: {newProp || 'not setted'}</h1>
      <Button
        onClick={() => {
          dispatch({
            type: 'count/add',
          });
        }}
      >
        Add
      </Button>
      <Button
        onClick={() => {
          dispatch({
            type: 'count/setNewProp',
          });
        }}
      >
        Set New Prop
      </Button>
    </div>
  );
}

function mapStateToProps(state) {
  return {
    count: state.count.a.b.c.count,
    newProp: state.count.newProp,
  };
}

export default connect(mapStateToProps)(App);


================================================
FILE: examples/with-nextjs/.babelrc
================================================
{
    "presets": [
      "next/babel"
    ],
    "plugins": [
      ["module-resolver", {
        "alias": {
          "dva": "dva-no-router"
        }
      }]
    ]
  }

================================================
FILE: examples/with-nextjs/.eslintignore
================================================
.next/*
node_modules/*




================================================
FILE: examples/with-nextjs/.eslintrc
================================================
{
    "parser": "babel-eslint",
    "extends": "airbnb",
    "settings": {
      "import/resolver": {
        "babel-module": {
          "alias": {
            "test": "./test",
            "underscore": "lodash"
          }
        }
      }
    },
    "rules": {
      "arrow-body-style": [0],
      "consistent-return": [0],
      "generator-star-spacing": [0],
      "global-require": [1],
      "import/extensions": [0],
      "import/no-extraneous-dependencies": [0],
      "import/no-unresolved": [0],
      "import/prefer-default-export": [0],
      "jsx-a11y/no-static-element-interactions": [0],
      "no-bitwise": [0],
      "no-cond-assign": [0],
      "no-else-return": [0],
      "no-nested-ternary": [0],
      "no-restricted-syntax": [0],
      "no-use-before-define": [0],
      "react/forbid-prop-types": [0],
      "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
      "react/jsx-no-bind": [0],
      "react/prefer-stateless-function": [0],
      "react/prop-types": [0],
      "require-yield": [1],
      "react/react-in-jsx-scope": [0],
      "jsx-a11y/anchor-is-valid": [0]
    },
    "parserOptions": {
      "ecmaFeatures": {
        "experimentalObjectRestSpread": true
      }
    }
  }

================================================
FILE: examples/with-nextjs/.gitignore
================================================
/node_modules/
/.next/

================================================
FILE: examples/with-nextjs/README.md
================================================
# dva_next
compose dva.js with next.js

1.install

```
npm install
```

2.start

```
npm start
```


================================================
FILE: examples/with-nextjs/model/homepage.js
================================================
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));

const model = {
  namespace: 'index',
  state: {
    name: 'hopperhuang',
    count: 0,
    init: false,
  },
  reducers: {
    caculate(state, payload) {
      const { count } = state;
      const { delta } = payload;
      return { ...state, count: count + delta };
    },
  },
  effects: {
    *init(action, { put }) {
      yield delay(2000);
      yield put({ type: 'caculate', delta: 1 });
    },
  },
};

export default model;



================================================
FILE: examples/with-nextjs/model/index.js
================================================
import homepage from './homepage';

const model = [
  homepage,
];

export default model;


================================================
FILE: examples/with-nextjs/package.json
================================================
{
  "name": "dva-next.js",
  "version": "1.0.0",
  "description": "dva-next.js-example",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "next"
  },
  "author": "hopperhuang",
  "license": "ISC",
  "dependencies": {
    "dva-no-router": "^1.0.3",
    "next": "^5.0.0",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "devDependencies": {
    "babel-eslint": "^8.2.2",
    "babel-plugin-module-resolver": "^3.1.0",
    "eslint": "^4.19.1",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-import-resolver-babel-module": "^4.0.0",
    "eslint-plugin-import": "^2.9.0",
    "eslint-plugin-jsx-a11y": "^6.0.3",
    "eslint-plugin-react": "^7.7.0"
  }
}


================================================
FILE: examples/with-nextjs/pages/index.js
================================================

import Link from 'next/link';
import React from 'react';
import WithDva from '../utils/store';

class Page extends React.Component {
  static async getInitialProps(props) {
    // first time run in server side
    // other times run in client side ( client side init with default props
    // console.log('get init props');
    const {
      pathname, query, isServer, store,
    } = props;
    // dispatch effects to fetch data here
    await props.store.dispatch({ type: 'index/init' });
    return {
      // dont use store as property name, it will confilct with initial store
      pathname, query, isServer, dvaStore: store,
    };
  }

  render() {
    const { index } = this.props;
    const { name, count } = index;
    // console.log('rendered!!');
    return (
      <div>
      Hi,{name}!! &nbsp;
        <p>count:&nbsp; {count}</p>
        <p>
          <button onClick={() => { this.props.dispatch({ type: 'index/caculate', delta: 1 }); }} >
        plus
          </button>
        </p>
        <p>
          <button onClick={() => { this.props.dispatch({ type: 'index/caculate', delta: -1 }); }} >
          minus
          </button>
        </p>
        <p>
          <Link href="/users">
            <a>Go to /users</a>
          </Link>
        </p>
      </div>
    );
  }
}

export default WithDva((state) => { return { index: state.index }; })(Page);


================================================
FILE: examples/with-nextjs/pages/users.js
================================================

import Link from 'next/link';

export default function () {
  return (
    <div>
      Users
      <br />
      <Link href="/">
        <a>
          Back
        </a>
      </Link>
    </div>
  );
}


================================================
FILE: examples/with-nextjs/utils/store.js
================================================
import React from 'react';
import dva, { connect } from 'dva-no-router';
import { Provider } from 'react-redux';
import model from '../model/index';

const checkServer = () => Object.prototype.toString.call(global.process) === '[object process]';

// eslint-disable-next-line
const __NEXT_DVA_STORE__ =  '__NEXT_DVA_STORE__'

function createDvaStore(initialState) {
  let app;
  if (initialState) {
    app = dva({
      initialState,
    });
  } else {
    app = dva({});
  }
  const isArray = Array.isArray(model);
  if (isArray) {
    model.forEach((m) => {
      app.model(m);
    });
  } else {
    app.model(model);
  }
  app.router(() => {});
  app.start();
  // console.log(app);
  // eslint-disable-next-line
  const store = app._store
  return store;
}

function getOrCreateStore(initialState) {
  const isServer = checkServer();
  if (isServer) { // run in server
    // console.log('server');
    return createDvaStore(initialState);
  }
  // eslint-disable-next-line
  if (!window[__NEXT_DVA_STORE__]) {
    // console.log('client');
    // eslint-disable-next-line
    window[__NEXT_DVA_STORE__] = createDvaStore(initialState);
  }
  // eslint-disable-next-line
  return window[__NEXT_DVA_STORE__];
}

export default function withDva(...args) {
  return function CreateNextPage(Component) {
    const ComponentWithDva = (props = {}) => {
      const { store, initialProps, initialState } = props;
      const ConnectedComponent = connect(...args)(Component);
      return React.createElement(
        Provider,
        // in client side, it will init store with the initial state tranfer from server side
        { store: store && store.dispatch ? store : getOrCreateStore(initialState) },
        // transfer next.js's props to the page
        React.createElement(ConnectedComponent, initialProps),
      );
    };
    ComponentWithDva.getInitialProps = async (props = {}) => {
      // console.log('get......');
      const isServer = checkServer();
      const store = getOrCreateStore(props.req);
      // call children's getInitialProps
      // get initProps and transfer in to the page
      const initialProps = Component.getInitialProps
        ? await Component.getInitialProps({ ...props, isServer, store })
        : {};
      return {
        store,
        initialProps,
        initialState: store.getState(),
      };
    };
    return ComponentWithDva;
  };
}


================================================
FILE: examples/with-react-router-3/.eslintrc
================================================
{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "rules": {
    "generator-star-spacing": [0],
    "consistent-return": [0],
    "react/forbid-prop-types": [0],
    "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
    "global-require": [1],
    "import/prefer-default-export": [0],
    "react/jsx-no-bind": [0],
    "react/prop-types": [0],
    "react/prefer-stateless-function": [0],
    "no-else-return": [0],
    "no-restricted-syntax": [0],
    "import/no-extraneous-dependencies": [0],
    "no-use-before-define": [0],
    "jsx-a11y/no-static-element-interactions": [0],
    "no-nested-ternary": [0],
    "arrow-body-style": [0],
    "import/extensions": [0],
    "no-bitwise": [0],
    "no-cond-assign": [0],
    "import/no-unresolved": [0],
    "require-yield": [1]
  },
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true
    }
  }
}


================================================
FILE: examples/with-react-router-3/.roadhogrc
================================================
{
  "entry": "src/index.js",
  "env": {
    "development": {
      "extraBabelPlugins": [
        "dva-hmr",
        "transform-runtime",
        ["module-resolver", {
          "alias": {
            "dva": "dva-react-router-3"
          }
        }]
      ]
    },
    "production": {
      "extraBabelPlugins": [
        "transform-runtime",
        ["module-resolver", {
          "alias": {
            "dva": "dva-react-router-3"
          }
        }]
      ]
    }
  }
}


================================================
FILE: examples/with-react-router-3/.roadhogrc.mock.js
================================================
export default {};


================================================
FILE: examples/with-react-router-3/package.json
================================================
{
  "name": "dva-example-react-router-3",
  "private": true,
  "scripts": {
    "start": "roadhog server",
    "build": "roadhog build",
    "lint": "eslint --ext .js src test"
  },
  "engines": {
    "install-node": "6.9.2"
  },
  "dependencies": {
    "babel-runtime": "^6.9.2",
    "dva-react-router-3": "^0.3.0",
    "react": "^16.0.0",
    "react-dom": "^16.0.0"
  },
  "devDependencies": {
    "babel-eslint": "^7.1.1",
    "babel-plugin-dva-hmr": "^0.3.2",
    "babel-plugin-module-resolver": "^2.7.1",
    "babel-plugin-transform-runtime": "^6.9.0",
    "eslint": "^3.12.2",
    "eslint-config-airbnb": "^13.0.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^2.2.3",
    "eslint-plugin-react": "^6.8.0",
    "expect": "^1.20.2",
    "redbox-react": "^1.3.2",
    "roadhog": "^1.2.0"
  }
}


================================================
FILE: examples/with-react-router-3/src/components/Example.js
================================================
import React from 'react';

const Example = () => {
  return <div>Example</div>;
};

Example.propTypes = {};

export default Example;


================================================
FILE: examples/with-react-router-3/src/index.css
================================================

html, body, :global(#root) {
  height: 100%;
}



================================================
FILE: examples/with-react-router-3/src/index.ejs
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Dva Demo</title>
</head>
<body>

<div id="root"></div>

</body>
</html>


================================================
FILE: examples/with-react-router-3/src/index.js
================================================
import dva from 'dva';
import './index.css';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model
app.model(require('./models/example'));

// 4. Router
app.router(require('./router'));

// 5. Start
app.start('#root');


================================================
FILE: examples/with-react-router-3/src/models/example.js
================================================
export default {
  namespace: 'example',

  state: {},

  subscriptions: {
    setup({ dispatch, history }) {
      // eslint-disable-line
    },
  },

  effects: {
    *fetch({ payload }, { call, put }) {
      // eslint-disable-line
      yield put({ type: 'save' });
    },
  },

  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
  },
};


================================================
FILE: examples/with-react-router-3/src/router.js
================================================
import React from 'react';
import { Router, Route } from 'dva/router';
import IndexPage from './routes/IndexPage';

function RouterConfig({ history }) {
  return (
    <Router history={history}>
      <Route path="/" component={IndexPage} />
    </Router>
  );
}

export default RouterConfig;


================================================
FILE: examples/with-react-router-3/src/routes/IndexPage.css
================================================

.normal {
  font-family: Georgia, sans-serif;
  margin-top: 3em;
  text-align: center;
}

.title {
  font-size: 2.5rem;
  font-weight: normal;
  letter-spacing: -1px;
}

.welcome {
  height: 328px;
  background: url(../assets/yay.jpg) no-repeat center 0;
  background-size: 388px 328px;
}

.list {
  font-size: 1.2em;
  margin-top: 1.8em;
  list-style: none;
  line-height: 1.5em;
}

.list code {
  background: #f7f7f7;
}


================================================
FILE: examples/with-react-router-3/src/routes/IndexPage.js
================================================
import React from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';

function IndexPage() {
  return (
    <div className={styles.normal}>
      <h1 className={styles.title}>Yay! Welcome to dva!</h1>
      <div className={styles.welcome} />
      <ul className={styles.list}>
        <li>
          To get started, edit <code>src/index.js</code> and save to reload.
        </li>
        <li>
          <a href="https://github.com/dvajs/dva-docs/blob/master/v1/en-us/getting-started.md">
            Getting Started
          </a>
        </li>
      </ul>
    </div>
  );
}

IndexPage.propTypes = {};

export default connect()(IndexPage);


================================================
FILE: examples/with-react-router-3/src/services/example.js
================================================
import request from '../utils/request';

export async function query() {
  return request('/api/users');
}


================================================
FILE: examples/with-react-router-3/src/utils/request.js
================================================
import fetch from 'dva/fetch';

function parseJSON(response) {
  return response.json();
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default function request(url, options) {
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(data => ({ data }))
    .catch(err => ({ err }));
}


================================================
FILE: examples/with-redux-undo/.babelrc
================================================
{
  "presets": ["env", "react"],
  "plugins": ["transform-object-rest-spread"]
}


================================================
FILE: examples/with-redux-undo/.gitignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage

# production
/build
/dist

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

# development
.cache
*.map

================================================
FILE: examples/with-redux-undo/.npmrc
================================================
package-lock=false

================================================
FILE: examples/with-redux-undo/README.md
================================================
# Redux Undo Sample

```
npm install
npm start
```

or

```
yarn
yarn start
```

Open http://localhost:1234 to view Counter App
---

Support
https://github.com/omnidan/redux-undo


================================================
FILE: examples/with-redux-undo/package.json
================================================
{
  "name": "dva-example-redux-redu",
  "private": true,
  "scripts": {
    "start": "parcel ./src/index.html "
  },
  "author": "sjy <shijianyue47@gmail.com>",
  "devDependencies": {
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "parcel-bundler": "^1.4.1"
  },
  "dependencies": {
    "dva": "^2.2.3",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "redux-undo": "^0.6.1"
  },
  "license": "MIT"
}


================================================
FILE: examples/with-redux-undo/src/index.html
================================================
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>dva-example-redux-redu</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <div id="root"></div>
  <script src="./index.js"></script>
</body>

</html>


================================================
FILE: examples/with-redux-undo/src/index.js
================================================
import React from 'react';
import dva, { connect } from 'dva';
import undoable, { ActionCreators } from 'redux-undo';

import counter from './models/counter';

// 1. Initialize
const app = dva({
  onReducer: reducer => (state, action) => {
    const newState = undoable(reducer, {})(state, action);
    return { ...newState };
  },
});

// 2. Model
app.model(counter);

// 3. View
const App = connect(({ present: { counter } }) => ({
  counter,
}))(props => {
  return (
    <div style={{ textAlignLast: 'center' }}>
      <h2>Count: {props.counter}</h2>
      <button
        onClick={() => {
          props.dispatch({ type: 'counter/minus' });
        }}
      >
        -
      </button>
      <button
        onClick={() => {
          props.dispatch({ type: 'counter/add' });
        }}
      >
        +
      </button>
      <button
        style={{ marginLeft: 20 }}
        onClick={() => {
          props.dispatch(ActionCreators.undo());
        }}
      >
        Undo
      </button>
      <button
        onClick={() => {
          props.dispatch(ActionCreators.redo());
        }}
      >
        Redo
      </button>
    </div>
  );
});

// 4. Router
app.router(() => <App />);

// 5. Start
app.start('#root');


================================================
FILE: examples/with-redux-undo/src/models/counter.js
================================================
export default {
  namespace: 'counter',
  state: 0,
  reducers: {
    add(state) {
      return state + 1;
    },
    minus(state) {
      return state - 1;
    },
  },
};


================================================
FILE: jest.config.js
================================================
module.exports = {
  collectCoverageFrom: ['packages/**/src/*.{ts,tsx,js,jsx}'],
};


================================================
FILE: lerna.json
================================================
{
  "changelog": {
    "repo": "dvajs/dva",
    "labels": {
      "pr(enhancement)": ":rocket: Enhancement",
      "pr(bug)": ":bug: Bug Fix",
      "pr(documentation)": ":book: Documentation",
      "pr(dependency)": ":deciduous_tree: Dependency",
      "pr(chore)": ":turtle: Chore"
    },
    "cacheDir": ".changelog"
  },
  "packages": [
    "packages/*"
  ],
  "command": {
    "version": {
      "exact": true
    }
  },
  "npmClient": "yarn",
  "version": "independent"
}


================================================
FILE: package.json
================================================
{
  "private": true,
  "scripts": {
    "build": "father-build",
    "doc:dev": "./website/node_modules/.bin/vuepress dev ./docs",
    "doc:deploy": "rm -rf ./website/yarn.lock && cd ./website && npm run deploy && cd -",
    "changelog": "lerna-changelog",
    "test": "npm run debug -- --coverage",
    "debug": "umi-test",
    "coveralls": "cat ./coverage/lcov.info | coveralls",
    "lint": "eslint --ext .js packages",
    "precommit": "lint-staged",
    "release": "./scripts/publish.js",
    "bootstrap": "lerna bootstrap"
  },
  "devDependencies": {
    "@types/jest": "^24.0.22",
    "babel-eslint": "^9.0.0",
    "chalk": "^2.3.2",
    "coveralls": "^3.0.0",
    "eslint": "^5.6.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^4.3.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.0.2",
    "eslint-plugin-prettier": "^3.1.0",
    "eslint-plugin-react": "^7.11.1",
    "father-build": "^1.14.0",
    "husky": "^0.14.3",
    "lerna": "^3.4.0",
    "lerna-changelog": "^0.8.0",
    "lint-staged": "^7.2.2",
    "prettier": "^1.14.3",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-testing-library": "^6.0.0",
    "shelljs": "^0.8.1",
    "umi-test": "^1.5.2"
  },
  "lint-staged": {
    "*.js": [
      "prettier --trailing-comma all --single-quote --write",
      "git add"
    ]
  }
}


================================================
FILE: packages/dva/.fatherrc.js
================================================
export default {
  entry: ['src/index.js', 'src/dynamic.js'],
  cjs: 'rollup',
  esm: 'rollup',
  runtimeHelpers: true,
};


================================================
FILE: packages/dva/README.md
================================================
# dva

[![NPM version](https://img.shields.io/npm/v/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Build Status](https://img.shields.io/travis/dvajs/dva.svg?style=flat)](https://travis-ci.org/dvajs/dva)
[![Coverage Status](https://img.shields.io/coveralls/dvajs/dva.svg?style=flat)](https://coveralls.io/r/dvajs/dva)
[![NPM downloads](http://img.shields.io/npm/dm/dva.svg?style=flat)](https://npmjs.org/package/dva)
[![Dependencies](https://david-dm.org/dvajs/dva/status.svg)](https://david-dm.org/dvajs/dva)

Official React bindings for dva, with react-router@4.

## LICENSE

MIT


================================================
FILE: packages/dva/dynamic.d.ts
================================================
declare const dynamic: (resolve: (value?: PromiseLike<any>) => void) => void;
export default dynamic;


================================================
FILE: packages/dva/dynamic.js
================================================
require('./warnAboutDeprecatedCJSRequire.js')('dynamic');
module.exports = require('./dist/dynamic');


================================================
FILE: packages/dva/fetch.d.ts
================================================
import * as isomorphicFetch from 'isomorphic-fetch';

export = isomorphicFetch;


================================================
FILE: packages/dva/fetch.js
================================================
require('./warnAboutDeprecatedCJSRequire.js')('fetch');
module.exports = require('isomorphic-fetch');


================================================
FILE: packages/dva/index.d.ts
================================================
import {
  Reducer,
  Action,
  AnyAction,
  ReducersMapObject,
  MiddlewareAPI,
  StoreEnhancer,
  bindActionCreators
} from 'redux';

import { History } from "history";

export interface Dispatch<A extends Action = AnyAction> {
  <T extends A>(action: T): Promise<any> | T;
}

export interface onActionFunc {
  (api: MiddlewareAPI<any>): void,
}

export interface ReducerEnhancer {
  (reducer: Reducer<any>): void,
}

export interface Hooks {
  onError?: (e: Error, dispatch: Dispatch<any>) => void,
  onAction?: onActionFunc | onActionFunc[],
  onStateChange?: () => void,
  onReducer?: ReducerEnhancer,
  onEffect?: () => void,
  onHmr?: () => void,
  extraReducers?: ReducersMapObject,
  extraEnhancers?: StoreEnhancer<any>[],
}

export type DvaOption = Hooks & {
  namespacePrefixWarning?: boolean,
  initialState?: Object,
  history?: Object,
}

export interface EffectsCommandMap {
  put: <A extends AnyAction>(action: A) => any,
  call: Function,
  select: Function,
  take: Function,
  cancel: Function,
  [key: string]: any,
}

export type Effect = (action: AnyAction, effects: EffectsCommandMap) => void;
export type EffectType = 'takeEvery' | 'takeLatest' | 'watcher' | 'throttle';
export type EffectWithType = [Effect, { type: EffectType }];
export type Subscription = (api: SubscriptionAPI, done: Function) => void;
export type ReducersMapObjectWithEnhancer = [ReducersMapObject, ReducerEnhancer];

export interface EffectsMapObject {
  [key: string]: Effect | EffectWithType,
}

export interface SubscriptionAPI {
  history: History,
  dispatch: Dispatch<any>,
}

export interface SubscriptionsMapObject {
  [key: string]: Subscription,
}

export interface Model {
  namespace: string,
  state?: any,
  reducers?: ReducersMapObject | ReducersMapObjectWithEnhancer,
  effects?: EffectsMapObject,
  subscriptions?: SubscriptionsMapObject,
}

export interface RouterAPI {
  history: History,
  app: DvaInstance,
}

export interface Router {
  (api?: RouterAPI): JSX.Element | Object,
}

export interface DvaInstance {
  /**
   * Register an object of hooks on the application.
   *
   * @param hooks
   */
  use: (hooks: Hooks) => void,

  /**
   * Register a model.
   *
   * @param model
   */
  model: (model: Model) => void,

  /**
   * Unregister a model.
   *
   * @param namespace
   */
  unmodel: (namespace: string) => void,

  /**
   * Config router. Takes a function with arguments { history, dispatch },
   * and expects router config. It use the same api as react-router,
   * return jsx elements or JavaScript Object for dynamic routing.
   *
   * @param router
   */
  router: (router: Router) => void,

  /**
   * Start the application. Selector is optional. If no selector
   * arguments, it will return a function that return JSX elements.
   *
   * @param selector
   */
  start: (selector?: HTMLElement | string) => any,
}

export default function dva(opts?: DvaOption): DvaInstance;

export { bindActionCreators };

export {
  connect, connectAdvanced, useSelector, useDispatch, useStore,
  DispatchProp, shallowEqual
} from 'react-redux';

import * as routerRedux from 'connected-react-router';
export { routerRedux };

import * as fetch from 'isomorphic-fetch';
export { fetch };

import * as router from 'react-router-dom';
export { router };
export { useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom';


================================================
FILE: packages/dva/package.json
================================================
{
  "name": "dva",
  "version": "3.0.0-alpha.1",
  "description": "React and redux based, lightweight and elm-style framework.",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "typings": "index.d.ts",
  "sideEffects": false,
  "repository": {
    "type": "git",
    "url": "https://github.com/dvajs/dva"
  },
  "homepage": "https://github.com/dvajs/dva",
  "keywords": [
    "dva",
    "alibaba",
    "react",
    "react-native",
    "redux",
    "redux-saga",
    "elm",
    "framework",
    "frontend"
  ],
  "authors": [
    "chencheng <sorrycc@gmail.com> (https://github.com/sorrycc)"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/dvajs/dva/issues"
  },
  "dependencies": {
    "@babel/runtime": "^7.0.0",
    "@types/isomorphic-fetch": "^0.0.35",
    "@types/react-redux": "^7.1.0",
    "@types/react-router-dom": "^5.1.2",
    "connected-react-router": "6.5.2",
    "dva-core": "2.0.4",
    "global": "^4.3.2",
    "history": "^4.7.2",
    "invariant": "^2.2.4",
    "isomorphic-fetch": "^2.2.1",
    "react-redux": "^7.1.0",
    "react-router-dom": "^5.1.2",
    "redux": "^4.0.1"
  },
  "peerDependencies": {
    "react": ">=18",
    "react-dom": ">=18"
  },
  "files": [
    "dist",
    "src",
    "dynamic.js",
    "fetch.js",
    "index.js",
    "router.js",
    "saga.js",
    "warnAboutDeprecatedCJSRequire.js",
    "*.d.ts"
  ]
}


================================================
FILE: packages/dva/router.d.ts
================================================
import * as routerRedux from 'connected-react-router';

export * from 'react-router-dom';
export { routerRedux };


================================================
FILE: packages/dva/router.js
================================================
require('./warnAboutDeprecatedCJSRequire.js')('router');
module.exports = require('react-router-dom');
module.exports.routerRedux = require('connected-react-router');


================================================
FILE: packages/dva/saga.js
================================================
require('./warnAboutDeprecatedCJSRequire.js')('saga');
module.exports = require('dva-core/saga');


================================================
FILE: packages/dva/src/dynamic.js
================================================
import React, { Component } from 'react';

const cached = {};
function registerModel(app, model) {
  model = model.default || model;
  if (!cached[model.namespace]) {
    app.model(model);
    cached[model.namespace] = 1;
  }
}

let defaultLoadingComponent = () => null;

function asyncComponent(config) {
  const { resolve } = config;

  return class DynamicComponent extends Component {
    constructor(...args) {
      super(...args);
      this.LoadingComponent = config.LoadingComponent || defaultLoadingComponent;
      this.state = {
        AsyncComponent: null,
      };
      this.load();
    }

    componentDidMount() {
      this.mounted = true;
    }

    componentWillUnmount() {
      this.mounted = false;
    }

    load() {
      resolve().then(m => {
        const AsyncComponent = m.default || m;
        if (this.mounted) {
          this.setState({ AsyncComponent });
        } else {
          this.state.AsyncComponent = AsyncComponent; // eslint-disable-line
        }
      });
    }

    render() {
      const { AsyncComponent } = this.state;
      const { LoadingComponent } = this;
      if (AsyncComponent) return <AsyncComponent {...this.props} />;

      return <LoadingComponent {...this.props} />;
    }
  };
}

export default function dynamic(config) {
  const { app, models: resolveModels, component: resolveComponent } = config;
  return asyncComponent({
    resolve:
      config.resolve ||
      function() {
        const models = typeof resolveModels === 'function' ? resolveModels() : [];
        const component = resolveComponent();
        return new Promise(resolve => {
          Promise.all([...models, component]).then(ret => {
            if (!models || !models.length) {
              return resolve(ret[0]);
            } else {
              const len = models.length;
              ret.slice(0, len).forEach(m => {
                m = m.default || m;
                if (!Array.isArray(m)) {
                  m = [m];
                }
                m.map(_ => registerModel(app, _));
              });
              resolve(ret[len]);
            }
          });
        });
      },
    ...config,
  });
}

dynamic.setDefaultLoadingComponent = LoadingComponent => {
  defaultLoadingComponent = LoadingComponent;
};


================================================
FILE: packages/dva/src/index.js
================================================
import React from 'react';
import invariant from 'invariant';
import { createBrowserHistory, createMemoryHistory, createHashHistory } from 'history';
import document from 'global/document';
import {
  Provider,
  connect,
  connectAdvanced,
  useSelector,
  useDispatch,
  useStore,
  shallowEqual,
} from 'react-redux';
import { bindActionCreators } from 'redux';
import { utils, create, saga } from 'dva-core';
import * as router from 'react-router-dom';
import * as routerRedux from 'connected-react-router';

const { connectRouter, routerMiddleware } = routerRedux;
const { isFunction } = utils;
const { useHistory, useLocation, useParams, useRouteMatch } = router;

export default function(opts = {}) {
  const history = opts.history || createHashHistory();
  const createOpts = {
    initialReducer: {
      router: connectRouter(history),
    },
    setupMiddlewares(middlewares) {
      return [routerMiddleware(history), ...middlewares];
    },
    setupApp(app) {
      app._history = patchHistory(history);
    },
  };

  const app = create(opts, createOpts);
  const oldAppStart = app.start;
  app.router = router;
  app.start = start;
  return app;

  function router(router) {
    invariant(
      isFunction(router),
      `[app.router] router should be function, but got ${typeof router}`,
    );
    app._router = router;
  }

  function start(container) {
    // 允许 container 是字符串,然后用 querySelector 找元素
    if (isString(container)) {
      container = document.querySelector(container);
      invariant(container, `[app.start] container ${container} not found`);
    }

    // 并且是 HTMLElement
    invariant(
      !container || isHTMLElement(container),
      `[app.start] container should be HTMLElement`,
    );

    // 路由必须提前注册
    invariant(app._router, `[app.start] router must be registered before app.start()`);

    if (!app._store) {
      oldAppStart.call(app);
    }
    const store = app._store;

    // export _getProvider for HMR
    // ref: https://github.com/dvajs/dva/issues/469
    app._getProvider = getProvider.bind(null, store, app);

    // If has container, render; else, return react component
    if (container) {
      render(container, store, app, app._router);
      app._plugin.apply('onHmr')(render.bind(null, container, store, app));
    } else {
      return getProvider(store, this, this._router);
    }
  }
}

function isHTMLElement(node) {
  return typeof node === 'object' && node !== null && node.nodeType && node.nodeName;
}

function isString(str) {
  return typeof str === 'string';
}

function getProvider(store, app, router) {
  const DvaRoot = extraProps => (
    <Provider store={store}>{router({ app, history: app._history, ...extraProps })}</Provider>
  );
  return DvaRoot;
}

function render(container, store, app, router) {
  const ReactDOM = require('react-dom/client'); // eslint-disable-line
  ReactDOM.createRoot(container).render(React.createElement(getProvider(store, app, router)));
}

function patchHistory(history) {
  const oldListen = history.listen;
  history.listen = callback => {
    // TODO: refact this with modified ConnectedRouter
    // Let ConnectedRouter to sync history to store first
    // connected-react-router's version is locked since the check function may be broken
    // min version of connected-react-router
    // e.g.
    // function (e, t) {
    //   var n = arguments.length > 2 && void 0 !== arguments[2] && arguments[2];
    //   r.inTimeTravelling ? r.inTimeTravelling = !1 : a(e, t, n)
    // }
    // ref: https://github.com/umijs/umi/issues/2693
    const cbStr = callback.toString();
    const isConnectedRouterHandler =
      (callback.name === 'handleLocationChange' && cbStr.indexOf('onLocationChanged') > -1) ||
      (cbStr.indexOf('.inTimeTravelling') > -1 &&
        cbStr.indexOf('.inTimeTravelling') > -1 &&
        cbStr.indexOf('arguments[2]') > -1);
    // why add __isDvaPatch: true
    // since it's a patch from dva, we need to identify it in the listen handlers
    callback(history.location, history.action, { __isDvaPatch: true });
    return oldListen.call(history, (...args) => {
      if (isConnectedRouterHandler) {
        callback(...args);
      } else {
        // Delay all listeners besides ConnectedRouter
        setTimeout(() => {
          callback(...args);
        });
      }
    });
  };
  return history;
}

export fetch from 'isomorphic-fetch';
export dynamic from './dynamic';
export { connect, connectAdvanced, useSelector, useDispatch, useStore, shallowEqual };
export { bindActionCreators };
export { router };
export { saga };
export { routerRedux };
export { createBrowserHistory, createMemoryHistory, createHashHistory };
export { useHistory, useLocation, useParams, useRouteMatch };


================================================
FILE: packages/dva/test/index.e2e.js
================================================
import React from 'react';
import { render, fireEvent, cleanup } from 'react-testing-library';
import dva, {
  connect,
  useDispatch,
  useSelector,
  useStore,
  createMemoryHistory,
  router,
  routerRedux,
  shallowEqual,
} from '../dist/index';

const { Link, Switch, Route, Router } = router;

afterEach(cleanup);

const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));

test('normal', () => {
  const app = dva();
  app.model({
    namespace: 'count',
    state: 0,
    reducers: {
      add(state) {
        return state + 1;
      },
    },
  });
  app.router(() => <div />);
  app.start();

  expect(app._store.getState().count).toEqual(0);
  app._store.dispatch({ type: 'count/add' });
  expect(app._store.getState().count).toEqual(1);
});

test('subscription execute multiple times', async () => {
  const app = dva();
  app.model({
    namespace: 'count',
    state: 0,
    subscriptions: {
      setup({ history, dispatch }) {
        return history.listen(() => {
          dispatch({
            type: 'add',
          });
        });
      },
    },
    reducers: {
      add(state) {
        return state + 1;
      },
    },
  });

  const Count = connect(state => ({ count: state.count }))(function(props) {
    return <div data-testid="count">{props.count}</div>;
  });

  function Home() {
    return <div />;
  }

  function Users() {
    return <div />;
  }

  app.router(({ history }) => {
    return (
      <Router history={history}>
        <>
          <Link to="/">Home</Link>
          <Link to="/users">Users</Link>
          <Count />
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/users" component={Users} />
          </Switch>
        </>
      </Router>
    );
  });

  const { getByTestId, getByText } = render(React.createElement(app.start()));
  expect(getByTestId('count').innerHTML).toEqual('1');
  fireEvent.click(getByText('Users'));
  await delay(100);
  expect(getByTestId('count').innerHTML).toEqual('2');
  fireEvent.click(getByText('Home'));
  await delay(100);
  expect(getByTestId('count').innerHTML).toEqual('3');
});

test('connect', () => {
  const app = dva();
  app.model({
    namespace: 'count',
    state: 0,
    reducers: {
      add(state) {
        return state + 1;
      },
    },
  });
  const App = connect(state => ({ count: state.count }))(({ count, dispatch }) => {
    return (
      <>
        <div data-testid="count">{count}</div>
        <button
          onClick={() => {
            dispatch({ type: 'count/add' });
          }}
        >
          add
        </button>
      </>
    );
  });
  app.router(() => <App />);

  const { getByTestId, getByText } = render(React.createElement(app.start()));
  expect(getByTestId('count').innerHTML).toEqual('0');
  fireEvent.click(getByText('add'));
  expect(getByTestId('count').innerHTML).toEqual('1');
});

test('hooks api: useDispatch, useSelector shallowEqual, and useStore', () => {
  const app = dva();
  app.model({
    namespace: 'count',
    state: 0,
    reducers: {
      add(state) {
        return state + 1;
      },
    },
  });

  const useShallowEqualSelector = selector => useSelector(selector, shallowEqual);

  const App = () => {
    const dispatch = useDispatch();
    const store = useStore();
    const { count } = useSelector(state => ({ count: state.count }));
    const { shallowEqualCount } = useShallowEqualSelector(state => ({
      shallowEqualCount: state.count,
    }));

    return (
      <>
        <div data-testid="count">{count}</div>
        <div data-testid="shallowEqualCount">{shallowEqualCount}</div>
        <div data-testid="state">{store.getState().count}</div>
        <button
          onClick={() => {
            dispatch({ type: 'count/add' });
          }}
        >
          add
        </button>
      </>
    );
  };
  app.router(() => <App />);

  const { getByTestId, getByText } = render(React.createElement(app.start()));
  expect(getByTestId('count').innerHTML).toEqual('0');
  expect(getByTestId('shallowEqualCount').innerHTML).toEqual('0');
  fireEvent.click(getByText('add'));
  expect(getByTestId('count').innerHTML).toEqual('1');
  expect(getByTestId('shallowEqualCount').innerHTML).toEqual('1');
  expect(getByTestId('state').innerHTML).toEqual('1');
});

test('navigate', async () => {
  const history = createMemoryHistory({
    initialEntries: ['/'],
  });
  const app = dva({
    history,
  });

  function Home() {
    return <h1 data-testid="title">You are on Home</h1>;
  }
  function Users() {
    return <h1 data-testid="title">You are on Users</h1>;
  }
  app.router(({ history }) => {
    return (
      <Router history={history}>
        <>
          <Link to="/">Home</Link>
          <Link to="/users">Users</Link>
          <button
            onClick={() => {
              app._store.dispatch(routerRedux.push('/'));
            }}
          >
            RouterRedux to Home
          </button>
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/users" component={Users} />
          </Switch>
        </>
      </Router>
    );
  });

  const { getByTestId, getByText } = render(React.createElement(app.start()));
  expect(getByTestId('title').innerHTML).toEqual('You are on Home');
  fireEvent.click(getByText('Users'));
  await delay(100);
  expect(getByTestId('title').innerHTML).toEqual('You are on Users');
  fireEvent.click(getByText('RouterRedux to Home'));
  await delay(100);
  expect(getByTestId('title').innerHTML).toEqual('You are on Home');
});


================================================
FILE: packages/dva/test/index.test.js
================================================
import expect from 'expect';
import React from 'react';
import dva, {
  useDispatch,
  useSelector,
  useStore,
  useHistory,
  useLocation,
  useParams,
  useRouteMatch,
} from '../src/index';

const countModel = {
  namespace: 'count',
  state: 0,
  reducers: {
    add(state, { payload }) {
      return state + payload || 1;
    },
    minus(state, { payload }) {
      return state - payload || 1;
    },
  },
};

describe('index', () => {
  xit('normal', () => {
    const app = dva();
    app.model({ ...countModel });
    app.router(() => <div />);
    app.start('#root');
  });

  it('start without container', () => {
    const app = dva();
    app.model({ ...countModel });
    app.router(() => <div />);
    app.start();
  });

  it('throw error if no routes defined', () => {
    const app = dva();
    expect(() => {
      app.start();
    }).toThrow(/router must be registered before app.start/);
  });

  it('opts.initialState', () => {
    const app = dva({
      initialState: { count: 1 },
    });
    app.model({ ...countModel });
    app.router(() => <div />);
    app.start();
    expect(app._store.getState().count).toEqual(1);
  });

  it('opts.onAction', () => {
    let count;
    const countMiddleware = () => () => () => {
      count += 1;
    };

    const app = dva({
      onAction: countMiddleware,
    });
    app.router(() => <div />);
    app.start();

    count = 0;
    app._store.dispatch({ type: 'test' });
    expect(count).toEqual(1);
  });

  it('opts.onAction with array', () => {
    let count;
    const countMiddleware = () => next => action => {
      count += 1;
      next(action);
    };
    const count2Middleware = () => next => action => {
      count += 2;
      next(action);
    };

    const app = dva({
      onAction: [countMiddleware, count2Middleware],
    });
    app.router(() => <div />);
    app.start();

    count = 0;
    app._store.dispatch({ type: 'test' });
    expect(count).toEqual(3);
  });

  it('opts.extraEnhancers', () => {
    let count = 0;
    const countEnhancer = storeCreator => (reducer, preloadedState, enhancer) => {
      const store = storeCreator(reducer, preloadedState, enhancer);
      const oldDispatch = store.dispatch;
      store.dispatch = action => {
        count += 1;
        oldDispatch(action);
      };
      return store;
    };
    const app = dva({
      extraEnhancers: [countEnhancer],
    });
    app.router(() => <div />);
    app.start();

    app._store.dispatch({ type: 'test' });
    expect(count).toEqual(1);
  });

  it('opts.onStateChange', () => {
    let savedState = null;

    const app = dva({
      onStateChange(state) {
        savedState 
Download .txt
gitextract_61ggrlan/

├── .circleci/
│   └── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── bug_report_cn.md
│   │   ├── feature_request.md
│   │   └── rfc_cn.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── stale.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── README_zh-CN.md
├── docs/
│   ├── .vuepress/
│   │   ├── config.js
│   │   └── override.styl
│   ├── API.md
│   ├── Concepts.md
│   ├── GettingStarted.md
│   ├── README.md
│   ├── api/
│   │   └── README.md
│   ├── guide/
│   │   ├── README.md
│   │   ├── concepts.md
│   │   ├── develop-complex-spa.md
│   │   ├── examples-and-boilerplates.md
│   │   ├── fig-show.md
│   │   ├── getting-started.md
│   │   ├── introduce-class.md
│   │   └── source-code-explore.md
│   └── knowledgemap/
│       └── README.md
├── examples/
│   ├── func-test/
│   │   ├── .eslintrc
│   │   ├── .roadhogrc
│   │   ├── .roadhogrc.mock.js
│   │   ├── package.json
│   │   └── src/
│   │       ├── components/
│   │       │   └── Example.js
│   │       ├── index.css
│   │       ├── index.ejs
│   │       ├── index.js
│   │       ├── models/
│   │       │   └── example.js
│   │       ├── router.js
│   │       ├── routes/
│   │       │   ├── IndexPage.css
│   │       │   └── IndexPage.js
│   │       ├── services/
│   │       │   └── example.js
│   │       └── utils/
│   │           └── request.js
│   ├── user-dashboard/
│   │   ├── .editorconfig
│   │   ├── .eslintrc
│   │   ├── .gitignore
│   │   ├── .umirc.js
│   │   ├── .webpackrc
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src/
│   │       ├── constants.js
│   │       ├── global.css
│   │       ├── layouts/
│   │       │   ├── Header.js
│   │       │   ├── index.css
│   │       │   └── index.js
│   │       ├── pages/
│   │       │   ├── index.css
│   │       │   ├── index.js
│   │       │   └── users/
│   │       │       ├── components/
│   │       │       │   └── Users/
│   │       │       │       ├── UserModal.js
│   │       │       │       ├── Users.css
│   │       │       │       └── Users.js
│   │       │       ├── models/
│   │       │       │   └── users.js
│   │       │       ├── page.css
│   │       │       ├── page.js
│   │       │       └── services/
│   │       │           └── users.js
│   │       ├── plugins/
│   │       │   └── onError.js
│   │       └── utils/
│   │           └── request.js
│   ├── with-immer/
│   │   ├── .umirc.js
│   │   ├── dva.js
│   │   ├── model.js
│   │   ├── package.json
│   │   └── pages/
│   │       └── index.js
│   ├── with-nextjs/
│   │   ├── .babelrc
│   │   ├── .eslintignore
│   │   ├── .eslintrc
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── model/
│   │   │   ├── homepage.js
│   │   │   └── index.js
│   │   ├── package.json
│   │   ├── pages/
│   │   │   ├── index.js
│   │   │   └── users.js
│   │   └── utils/
│   │       └── store.js
│   ├── with-react-router-3/
│   │   ├── .eslintrc
│   │   ├── .roadhogrc
│   │   ├── .roadhogrc.mock.js
│   │   ├── package.json
│   │   └── src/
│   │       ├── components/
│   │       │   └── Example.js
│   │       ├── index.css
│   │       ├── index.ejs
│   │       ├── index.js
│   │       ├── models/
│   │       │   └── example.js
│   │       ├── router.js
│   │       ├── routes/
│   │       │   ├── IndexPage.css
│   │       │   └── IndexPage.js
│   │       ├── services/
│   │       │   └── example.js
│   │       └── utils/
│   │           └── request.js
│   └── with-redux-undo/
│       ├── .babelrc
│       ├── .gitignore
│       ├── .npmrc
│       ├── README.md
│       ├── package.json
│       └── src/
│           ├── index.html
│           ├── index.js
│           └── models/
│               └── counter.js
├── jest.config.js
├── lerna.json
├── package.json
├── packages/
│   ├── dva/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── dynamic.d.ts
│   │   ├── dynamic.js
│   │   ├── fetch.d.ts
│   │   ├── fetch.js
│   │   ├── index.d.ts
│   │   ├── package.json
│   │   ├── router.d.ts
│   │   ├── router.js
│   │   ├── saga.js
│   │   ├── src/
│   │   │   ├── dynamic.js
│   │   │   └── index.js
│   │   ├── test/
│   │   │   ├── index.e2e.js
│   │   │   └── index.test.js
│   │   └── warnAboutDeprecatedCJSRequire.js
│   ├── dva-core/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── saga.js
│   │   ├── src/
│   │   │   ├── Plugin.js
│   │   │   ├── checkModel.js
│   │   │   ├── constants.js
│   │   │   ├── createPromiseMiddleware.js
│   │   │   ├── createStore.js
│   │   │   ├── getReducer.js
│   │   │   ├── getSaga.js
│   │   │   ├── handleActions.js
│   │   │   ├── index.js
│   │   │   ├── prefixNamespace.js
│   │   │   ├── prefixType.js
│   │   │   ├── prefixedDispatch.js
│   │   │   ├── subscription.js
│   │   │   └── utils.js
│   │   └── test/
│   │       ├── checkModel.test.js
│   │       ├── effects.test.js
│   │       ├── handleActions.test.js
│   │       ├── model.test.js
│   │       ├── optsAndHooks.test.js
│   │       ├── plugin.test.js
│   │       ├── reducers.test.js
│   │       ├── repalceModel.test.js
│   │       ├── subscriptions.test.js
│   │       └── utils.test.js
│   ├── dva-immer/
│   │   ├── .fatherrc.js
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── src/
│   │   │   └── index.js
│   │   └── test/
│   │       └── index.test.js
│   └── dva-loading/
│       ├── .fatherrc.js
│       ├── README.md
│       ├── index.d.ts
│       ├── package.json
│       ├── src/
│       │   └── index.js
│       └── test/
│           ├── core.test.js
│           └── index.test.js
├── scripts/
│   └── publish.js
└── website/
    ├── .gitignore
    ├── now.json
    └── package.json
Download .txt
SYMBOL INDEX (264 symbols across 60 files)

FILE: examples/func-test/src/models/example.js
  method setup (line 7) | setup({ dispatch, history }) {
  method fetch (line 16) | *fetch({ payload }, { call, put }) {
  method save (line 23) | save(state, action) {

FILE: examples/func-test/src/router.js
  function RouterConfig (line 7) | function RouterConfig({ history }) {

FILE: examples/func-test/src/routes/IndexPage.js
  function IndexPage (line 5) | function IndexPage() {

FILE: examples/func-test/src/services/example.js
  function query (line 3) | async function query() {

FILE: examples/func-test/src/utils/request.js
  function parseJSON (line 3) | function parseJSON(response) {
  function checkStatus (line 7) | function checkStatus(response) {
  function request (line 24) | function request(url, options) {

FILE: examples/user-dashboard/src/constants.js
  constant PAGE_SIZE (line 1) | const PAGE_SIZE = 3;

FILE: examples/user-dashboard/src/layouts/Header.js
  function Header (line 6) | function Header({ location }) {

FILE: examples/user-dashboard/src/layouts/index.js
  function MainLayout (line 5) | function MainLayout({ children, location }) {

FILE: examples/user-dashboard/src/pages/index.js
  function IndexPage (line 4) | function IndexPage() {

FILE: examples/user-dashboard/src/pages/users/components/Users/UserModal.js
  class UserEditModal (line 6) | class UserEditModal extends Component {
    method constructor (line 7) | constructor(props) {
    method render (line 37) | render() {

FILE: examples/user-dashboard/src/pages/users/components/Users/Users.js
  function Users (line 9) | function Users({ dispatch, list: dataSource, loading, total, page: curre...
  function mapStateToProps (line 103) | function mapStateToProps(state) {

FILE: examples/user-dashboard/src/pages/users/models/users.js
  method save (line 11) | save(state, { payload: { data: list, total, page } }) {
  method fetch (line 16) | *fetch({ payload: { page = 1 } }, { call, put }) {
  method remove (line 27) | *remove({ payload: id }, { call, put }) {
  method patch (line 31) | *patch({ payload: { id, values } }, { call, put }) {
  method create (line 35) | *create({ payload: values }, { call, put }) {
  method reload (line 39) | *reload(action, { put, select }) {
  method setup (line 45) | setup({ dispatch, history }) {

FILE: examples/user-dashboard/src/pages/users/page.js
  function Users (line 5) | function Users() {

FILE: examples/user-dashboard/src/pages/users/services/users.js
  function fetch (line 4) | function fetch({ page }) {
  function remove (line 8) | function remove(id) {
  function patch (line 14) | function patch(id, values) {
  function create (line 21) | function create(values) {

FILE: examples/user-dashboard/src/plugins/onError.js
  constant ERROR_MSG_DURATION (line 3) | const ERROR_MSG_DURATION = 3;
  method onError (line 6) | onError(e) {

FILE: examples/user-dashboard/src/utils/request.js
  function checkStatus (line 3) | function checkStatus(response) {
  function request (line 20) | async function request(url, options) {

FILE: examples/with-immer/dva.js
  function config (line 3) | function config() {

FILE: examples/with-immer/model.js
  method add (line 13) | add(state) {
  method setNewProp (line 16) | setNewProp(state) {

FILE: examples/with-immer/pages/index.js
  function App (line 4) | function App({ count, newProp, dispatch }) {
  function mapStateToProps (line 31) | function mapStateToProps(state) {

FILE: examples/with-nextjs/model/homepage.js
  method caculate (line 11) | caculate(state, payload) {
  method init (line 18) | *init(action, { put }) {

FILE: examples/with-nextjs/pages/index.js
  class Page (line 6) | class Page extends React.Component {
    method getInitialProps (line 7) | static async getInitialProps(props) {
    method render (line 22) | render() {

FILE: examples/with-nextjs/utils/store.js
  function createDvaStore (line 11) | function createDvaStore(initialState) {
  function getOrCreateStore (line 36) | function getOrCreateStore(initialState) {
  function withDva (line 52) | function withDva(...args) {

FILE: examples/with-react-router-3/src/models/example.js
  method setup (line 7) | setup({ dispatch, history }) {
  method fetch (line 13) | *fetch({ payload }, { call, put }) {
  method save (line 20) | save(state, action) {

FILE: examples/with-react-router-3/src/router.js
  function RouterConfig (line 5) | function RouterConfig({ history }) {

FILE: examples/with-react-router-3/src/routes/IndexPage.js
  function IndexPage (line 5) | function IndexPage() {

FILE: examples/with-react-router-3/src/services/example.js
  function query (line 3) | async function query() {

FILE: examples/with-react-router-3/src/utils/request.js
  function parseJSON (line 3) | function parseJSON(response) {
  function checkStatus (line 7) | function checkStatus(response) {
  function request (line 24) | function request(url, options) {

FILE: examples/with-redux-undo/src/models/counter.js
  method add (line 5) | add(state) {
  method minus (line 8) | minus(state) {

FILE: packages/dva-core/src/Plugin.js
  function filterHooks (line 16) | function filterHooks(obj) {
  class Plugin (line 25) | class Plugin {
    method constructor (line 26) | constructor() {
    method use (line 34) | use(plugin) {
    method apply (line 51) | apply(key, defaultHandler) {
    method get (line 68) | get(key) {
  function getExtraReducers (line 81) | function getExtraReducers(hook) {
  function getOnReducer (line 89) | function getOnReducer(hook) {

FILE: packages/dva-core/src/checkModel.js
  function checkModel (line 4) | function checkModel(model, existModels) {
  function isAllFunction (line 55) | function isAllFunction(obj) {

FILE: packages/dva-core/src/constants.js
  constant NAMESPACE_SEP (line 1) | const NAMESPACE_SEP = '/';

FILE: packages/dva-core/src/createPromiseMiddleware.js
  function createPromiseMiddleware (line 3) | function createPromiseMiddleware(app) {

FILE: packages/dva-core/src/getReducer.js
  function getReducer (line 3) | function getReducer(reducers, state, handleActions) {

FILE: packages/dva-core/src/getSaga.js
  function getSaga (line 7) | function getSaga(effects, model, onError, onEffect, opts = {}) {
  function getWatcher (line 22) | function getWatcher(key, _effect, model, onError, onEffect, opts) {
  function createEffects (line 107) | function createEffects(model, opts) {
  function applyOnEffect (line 162) | function applyOnEffect(fns, effect, model, key) {

FILE: packages/dva-core/src/handleActions.js
  function identify (line 3) | function identify(value) {
  function handleAction (line 7) | function handleAction(actionType, reducer = identify) {
  function reduceReducers (line 18) | function reduceReducers(...reducers) {
  function handleActions (line 22) | function handleActions(handlers, defaultState) {

FILE: packages/dva-core/src/index.js
  method UPDATE (line 21) | UPDATE(state) {
  function create (line 33) | function create(hooksAndOpts = {}, createOpts = {}) {

FILE: packages/dva-core/src/prefixNamespace.js
  function prefix (line 5) | function prefix(obj, namespace, type) {
  function prefixNamespace (line 17) | function prefixNamespace(model) {

FILE: packages/dva-core/src/prefixType.js
  function prefixType (line 3) | function prefixType(type, model) {

FILE: packages/dva-core/src/prefixedDispatch.js
  function prefixedDispatch (line 6) | function prefixedDispatch(dispatch, model) {

FILE: packages/dva-core/src/subscription.js
  function run (line 5) | function run(subs, model, app, onError) {
  function unlisten (line 28) | function unlisten(unlisteners, namespace) {

FILE: packages/dva-core/test/effects.test.js
  method add (line 14) | add(state, { payload }) {
  method addDelay (line 19) | *addDelay({ payload }, { put, call }) {
  function testAppCreator (line 34) | function testAppCreator(opts) {
  method dump (line 102) | dump(state, { payload }) {
  method changeCountDelay (line 110) | *changeCountDelay({ payload }, { put, call }) {
  method process (line 114) | *process({ payload }, { put, select }) {
  method add (line 135) | add(state, { payload }) {
  method addDelay (line 140) | *addDelay({ payload }, { put, call }) {
  method test (line 144) | *test(action, { put, select, take }) {
  method addRequest (line 167) | addRequest() {
  method addFailure (line 170) | addFailure() {
  method addSuccess (line 173) | addSuccess() {
  method add (line 178) | *add(action, { put }) {
  method test (line 186) | *test(action, { put, take }) {
  method show (line 207) | show() {
  method addDelay (line 216) | *addDelay(_, { put }) {
  method add (line 239) | add(state, { payload }) {
  method addDelay (line 244) | *addDelay({ payload }, { put }) {
  method onError (line 263) | onError(err, dispatch, extension) {
  method append (line 275) | append(state, action) {
  method generate (line 281) | *generate() {
  method add (line 304) | add(state, { payload }) {
  method add (line 355) | add(state, { payload }) {
  method add (line 388) | add(state, { payload }) {
  method add (line 423) | add(state, { payload }) {
  method add (line 453) | add(state, { payload }) {
  method add (line 484) | add(state, { payload }) {
  method add (line 514) | add(state, { payload }) {
  method add (line 546) | add(state, { payload }) {
  method add (line 578) | add(state, { payload }) {
  method loading (line 638) | loading(state = false, action) {
  method onEffect (line 649) | onEffect(effect, { put }, model, key) {
  method onEffect (line 662) | onEffect(effect) {
  method add (line 675) | add(state) {
  method addRemote (line 680) | *addRemote(action, { put }) {
  method add (line 710) | add(state, { payload }) {
  method addDelay (line 715) | *addDelay({ payload }, { put, call, select }) {
  method add (line 747) | add(state, { payload }) {
  method addDelay (line 752) | *addDelay({ payload }, { put, call, select }) {

FILE: packages/dva-core/test/handleActions.test.js
  method [LOGIN_START] (line 17) | [LOGIN_START](state) {
  method [LOGIN_END] (line 24) | [LOGIN_END](state) {

FILE: packages/dva-core/test/model.test.js
  method add (line 13) | add(state, { payload }) {
  method add (line 25) | add(state, { payload }) {
  method setup (line 31) | setup() {
  method setup (line 55) | setup() {}
  method add (line 75) | add(state) {
  method add (line 84) | add(state) {
  method addBoth (line 89) | *addBoth(action, { put }) {
  method setup (line 95) | setup() {
  method add (line 126) | add(state, { payload }) {
  method add (line 138) | add(state, { payload }) {
  method setup (line 144) | setup() {
  method add (line 170) | add(state) {
  method add (line 181) | add(state) {
  method addBoth (line 186) | *addBoth(action, { put }) {
  method a (line 206) | a() {}
  method add (line 277) | add() {}

FILE: packages/dva-core/test/optsAndHooks.test.js
  function delay (line 4) | function delay(timeout) {
  method show (line 16) | show() {
  method hide (line 19) | hide() {
  method setup (line 36) | setup({ dispatch }) {
  method [ADD] (line 41) | [ADD](state, { payload }) {
  method [ADD_DELAY] (line 46) | *[ADD_DELAY]({ payload }, { call, put }) {
  method onError (line 71) | onError(e) {
  method add (line 80) | *add() {
  method onStateChange (line 173) | onStateChange(state) {
  method add (line 181) | add(state) {

FILE: packages/dva-core/test/plugin.test.js
  function onError (line 9) | function onError(err) {

FILE: packages/dva-core/test/reducers.test.js
  function enhancer (line 6) | function enhancer(reducer) {
  method add (line 21) | add(state, { payload }) {
  method routing (line 58) | routing() {}
  method add (line 84) | add(state) {
  method update (line 114) | update(state) {
  method putSetState (line 134) | *putSetState(action, { put }) {
  method setState (line 140) | setState(state) {

FILE: packages/dva-core/test/repalceModel.test.js
  method add (line 22) | add(state, { payload }) {
  method add (line 41) | add(state, { payload }) {
  method add (line 54) | add(state, { payload }) {
  method clear (line 57) | clear() {
  method setter (line 84) | setter(state, { payload }) {
  method add (line 89) | *add({ payload }, { put }) {
  method setter (line 103) | setter(state, { payload }) {
  method add (line 108) | *add(_, { put }) {
  method add (line 128) | add(state, { payload }) {
  method setup (line 133) | setup({ dispatch }) {
  method add (line 145) | add(state, { payload }) {
  method setup (line 150) | setup({ dispatch }) {
  method setup (line 172) | setup() {
  method onError (line 199) | onError() {
  method add (line 213) | *add() {

FILE: packages/dva-core/test/subscriptions.test.js
  method add (line 11) | add(state, { payload }) {
  method setup (line 16) | setup({ dispatch }) {
  method add (line 31) | add(state, { payload }) {
  method setup (line 36) | setup({ dispatch }) {
  method setup (line 51) | setup({ dispatch }) {
  method show (line 67) | show() {
  method setup (line 76) | setup({ dispatch }) {
  method setup (line 96) | setup(_obj, done) {
  method setup (line 116) | setup(_obj, done) {

FILE: packages/dva-immer/src/index.js
  method _handleActions (line 7) | _handleActions(handlers, defaultState) {

FILE: packages/dva-immer/test/index.test.js
  method add (line 24) | add(state) {
  method add (line 58) | add(state) {

FILE: packages/dva-loading/index.d.ts
  type DvaLoadingState (line 1) | interface DvaLoadingState {

FILE: packages/dva-loading/src/index.js
  constant SHOW (line 1) | const SHOW = '@@DVA_LOADING/SHOW';
  constant HIDE (line 2) | const HIDE = '@@DVA_LOADING/HIDE';
  constant NAMESPACE (line 3) | const NAMESPACE = 'loading';
  function createLoading (line 5) | function createLoading(opts = {}) {

FILE: packages/dva-loading/test/core.test.js
  method add (line 16) | add(state) {
  method addRemote (line 21) | *addRemote(action, { put }) {
  method add (line 61) | add(state) {
  method addRemote (line 66) | *addRemote(action, { put }) {
  method a (line 125) | *a(action, { call }) {
  method b (line 128) | *b(action, { call }) {
  method a (line 169) | *a(action, { call }) {
  method b (line 172) | *b(action, { call }) {
  method add (line 221) | add(state) {
  method a (line 266) | *a(action, { call }) {
  method b (line 269) | *b(action, { call }) {
  method onError (line 288) | onError(err) {
  method throwError (line 298) | *throwError(action, { call }) {

FILE: packages/dva-loading/test/index.test.js
  method add (line 15) | add(state) {
  method addRemote (line 20) | *addRemote(action, { put }) {
  method add (line 61) | add(state) {
  method addRemote (line 66) | *addRemote(action, { put }) {
  method a (line 127) | *a(action, { call }) {
  method b (line 130) | *b(action, { call }) {
  method a (line 172) | *a(action, { call }) {
  method b (line 175) | *b(action, { call }) {
  method add (line 225) | add(state) {
  method a (line 271) | *a(action, { call }) {
  method b (line 274) | *b(action, { call }) {
  method onError (line 294) | onError(err) {
  method throwError (line 304) | *throwError(action, { call }) {

FILE: packages/dva/index.d.ts
  type Dispatch (line 13) | interface Dispatch<A extends Action = AnyAction> {
  type onActionFunc (line 17) | interface onActionFunc {
  type ReducerEnhancer (line 21) | interface ReducerEnhancer {
  type Hooks (line 25) | interface Hooks {
  type DvaOption (line 36) | type DvaOption = Hooks & {
  type EffectsCommandMap (line 42) | interface EffectsCommandMap {
  type Effect (line 51) | type Effect = (action: AnyAction, effects: EffectsCommandMap) => void;
  type EffectType (line 52) | type EffectType = 'takeEvery' | 'takeLatest' | 'watcher' | 'throttle';
  type EffectWithType (line 53) | type EffectWithType = [Effect, { type: EffectType }];
  type Subscription (line 54) | type Subscription = (api: SubscriptionAPI, done: Function) => void;
  type ReducersMapObjectWithEnhancer (line 55) | type ReducersMapObjectWithEnhancer = [ReducersMapObject, ReducerEnhancer];
  type EffectsMapObject (line 57) | interface EffectsMapObject {
  type SubscriptionAPI (line 61) | interface SubscriptionAPI {
  type SubscriptionsMapObject (line 66) | interface SubscriptionsMapObject {
  type Model (line 70) | interface Model {
  type RouterAPI (line 78) | interface RouterAPI {
  type Router (line 83) | interface Router {
  type DvaInstance (line 87) | interface DvaInstance {

FILE: packages/dva/src/dynamic.js
  function registerModel (line 4) | function registerModel(app, model) {
  function asyncComponent (line 14) | function asyncComponent(config) {
  function dynamic (line 56) | function dynamic(config) {

FILE: packages/dva/src/index.js
  method setupMiddlewares (line 29) | setupMiddlewares(middlewares) {
  method setupApp (line 32) | setupApp(app) {
  function router (line 43) | function router(router) {
  function start (line 51) | function start(container) {
  function isHTMLElement (line 86) | function isHTMLElement(node) {
  function isString (line 90) | function isString(str) {
  function getProvider (line 94) | function getProvider(store, app, router) {
  function render (line 101) | function render(container, store, app, router) {
  function patchHistory (line 106) | function patchHistory(history) {

FILE: packages/dva/test/index.e2e.js
  method add (line 26) | add(state) {
  method setup (line 45) | setup({ history, dispatch }) {
  method add (line 54) | add(state) {
  function Home (line 64) | function Home() {
  function Users (line 68) | function Users() {
  method add (line 104) | add(state) {
  method add (line 137) | add(state) {
  function Home (line 187) | function Home() {
  function Users (line 190) | function Users() {

FILE: packages/dva/test/index.test.js
  method add (line 17) | add(state, { payload }) {
  method minus (line 20) | minus(state, { payload }) {
  method onStateChange (line 122) | onStateChange(state) {
  method add (line 130) | add(state) {

FILE: scripts/publish.js
  function publishToNpm (line 57) | function publishToNpm() {
Condensed preview — 170 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (307K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 1399,
    "preview": "version: 2.1\nexecutors:\n  node:\n    docker:\n      - image: circleci/node:10.13-browsers\n    working_directory: ~/dva\n\nen"
  },
  {
    "path": ".editorconfig",
    "chars": 245,
    "preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_tr"
  },
  {
    "path": ".eslintignore",
    "chars": 243,
    "preview": "node_modules/\nlib/\nes/\ndist/\npackages/dva-example/\npackages/dva-example-react-router-3/\npackages/dva-example-nextjs//\npa"
  },
  {
    "path": ".eslintrc",
    "chars": 2448,
    "preview": "{\n  \"parser\": \"babel-eslint\",\n  \"plugins\": [\n    \"prettier\"\n  ],\n  \"extends\": [\n    \"airbnb\",\n    \"prettier\"\n  ],\n  \"env"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 570,
    "preview": "---\nname: 'Bug report'\nabout: 'Report a bug to help us improve'\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## What happens"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_cn.md",
    "chars": 423,
    "preview": "---\nname: '缺陷问题反馈'\nabout: '反馈问题以帮助我们改进'\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!--\n感谢您向我们反馈问题,为了高效的解决问题,我们期望你能提供以下信息:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 427,
    "preview": "---\nname: 'Feature request'\nabout: 'Suggest an idea for this project'\ntitle: '[Feature Request] say something'\nlabels: '"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/rfc_cn.md",
    "chars": 258,
    "preview": "---\nname: 'RFC Proposals'\nabout: 'Provide a solution for this project'\ntitle: '[RFC] say something'\nlabels: 'type: propo"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 724,
    "preview": "<!--\nThank you for your pull request. Please review below requirements.\nBug fixes and new features should include tests."
  },
  {
    "path": ".github/stale.yml",
    "chars": 684,
    "preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a "
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 615,
    "preview": "name: Node CI\n\non: [push]\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        node_version"
  },
  {
    "path": ".gitignore",
    "chars": 169,
    "preview": ".DS_Store\n/coverage\n/.changelog\n/examples/**/.umi\n/examples/**/.umi-production\n/website/dist\n/node_modules\n/packages/**/"
  },
  {
    "path": ".prettierrc",
    "chars": 73,
    "preview": "{\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1104,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016-present ChenCheng (sorrycc@gmail.com)\n\nPermission is hereby granted, free of c"
  },
  {
    "path": "README.md",
    "chars": 5345,
    "preview": "English | [简体中文](./README_zh-CN.md)\n\n# dva\n\n[![codecov](https://codecov.io/gh/dvajs/dva/branch/master/graph/badge.svg)]("
  },
  {
    "path": "README_zh-CN.md",
    "chars": 4692,
    "preview": "[English](./README.md) | 简体中文\n\n# dva\n\n[![codecov](https://codecov.io/gh/dvajs/dva/branch/master/graph/badge.svg)](https:"
  },
  {
    "path": "docs/.vuepress/config.js",
    "chars": 995,
    "preview": "module.exports = {\n  title: 'DvaJS',\n  description: 'React and redux based, lightweight and elm-style framework.',\n  the"
  },
  {
    "path": "docs/.vuepress/override.styl",
    "chars": 90,
    "preview": "$accentColor = #fc54c3\n$textColor = #2c3e50\n$borderColor = #eaecef\n$codeBgColor = #282c34\n"
  },
  {
    "path": "docs/API.md",
    "chars": 8141,
    "preview": "# API\n\n## Export Files\n### dva\n\nDefault export file.\n\n### dva/router\n\nExport the api of [react-router@4.x](https://githu"
  },
  {
    "path": "docs/Concepts.md",
    "chars": 4339,
    "preview": "# Concepts\n\n## Data Flow\n\n<img src=\"https://zos.alipayobjects.com/rmsportal/PPrerEAKbIoDZYr.png\" width=\"807\" />\n\n## Mode"
  },
  {
    "path": "docs/GettingStarted.md",
    "chars": 12996,
    "preview": "# Getting Started\n\n> This article will lead you to create [dva](https://github.com/dvajs/dva) app quickly, and learn all"
  },
  {
    "path": "docs/README.md",
    "chars": 1402,
    "preview": "---\nhome: true\nactionText: 快速上手 →\nactionLink: /guide/\nfeatures:\n- title: 易学易用\n  details: 仅有 6 个 api,对 redux 用户尤其友好,配合 um"
  },
  {
    "path": "docs/api/README.md",
    "chars": 6974,
    "preview": "---\nsidebarDepth: 2\n---\n\n# API\n\n## 输出文件\n\n### dva\n\n默认输出文件。\n\n### dva/router\n\n默认输出 [react-router](https://github.com/ReactT"
  },
  {
    "path": "docs/guide/README.md",
    "chars": 1155,
    "preview": "# 介绍\n\ndva 首先是一个基于 [redux](https://github.com/reduxjs/redux) 和 [redux-saga](https://github.com/redux-saga/redux-saga) 的数据"
  },
  {
    "path": "docs/guide/concepts.md",
    "chars": 4256,
    "preview": "---\nsidebarDepth: 2\n---\n\n# Dva 概念\n\n## 数据流向\n\n数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 `dispatch` 发起一个 actio"
  },
  {
    "path": "docs/guide/develop-complex-spa.md",
    "chars": 8395,
    "preview": "# 使用 Dva 开发复杂 SPA\n\n> 作者:徐飞\n\n在dva的官方仓库里,提供了上手教程,讲述了dva的一些基本概念。到了真实的业务开发过程中,会遇到许许多多不能用那些基本操作覆盖的场景,本文尝试列举一些常见的需求在dva中的实现方式。"
  },
  {
    "path": "docs/guide/examples-and-boilerplates.md",
    "chars": 1031,
    "preview": "# 例子和脚手架\n\n## 官方\n\n* [Count](https://stackblitz.com/edit/dva-example-count): 简单计数器\n* [User Dashboard](https://github.com/d"
  },
  {
    "path": "docs/guide/fig-show.md",
    "chars": 2676,
    "preview": "# Dva 图解\n\n> 作者:至正<br />\n> 原文链接:[https://yuque.com/flying.ni/the-tower/tvzasn](https://yuque.com/flying.ni/the-tower/tvza"
  },
  {
    "path": "docs/guide/getting-started.md",
    "chars": 4620,
    "preview": "# 快速上手\n\n## 安装 dva-cli\n\n通过 npm 安装 dva-cli 并确保版本是 `0.9.1` 或以上。\n\n```bash\n$ npm install dva-cli -g\n$ dva -v\ndva-cli version "
  },
  {
    "path": "docs/guide/introduce-class.md",
    "chars": 5188,
    "preview": "# 入门课\n\n::: tip\n内容来自之前为内部同学准备的入门课。\n:::\n\n## React 没有解决的问题\n\nReact 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。\n\n如果开发大应用,还需要解决一个问题。\n\n* 通信:组"
  },
  {
    "path": "docs/guide/source-code-explore.md",
    "chars": 30116,
    "preview": "# Dva 源码解析\n\n> 作者:杨光\n\n## 隐藏在 package.json 里的秘密\n\n随便哪个 dva 的项目,只要敲入 npm start 就可以运行启动。之前敲了无数次我都没有在意,直到我准备研究源码的时候才意识到:**在敲下这"
  },
  {
    "path": "docs/knowledgemap/README.md",
    "chars": 13546,
    "preview": "---\nsidebarDepth: 2\n---\n\n# 知识地图\n\n- [Read \"the dva.js Knowledgemap\" in English](https://github.com/dvajs/dva-knowledgemap"
  },
  {
    "path": "examples/func-test/.eslintrc",
    "chars": 897,
    "preview": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"airbnb\",\n  \"rules\": {\n    \"generator-star-spacing\": [0],\n    \"consistent-ret"
  },
  {
    "path": "examples/func-test/.roadhogrc",
    "chars": 249,
    "preview": "{\n  \"entry\": \"src/index.js\",\n  \"env\": {\n    \"development\": {\n      \"extraBabelPlugins\": [\n        \"dva-hmr\",\n        \"tr"
  },
  {
    "path": "examples/func-test/.roadhogrc.mock.js",
    "chars": 19,
    "preview": "export default {};\n"
  },
  {
    "path": "examples/func-test/package.json",
    "chars": 791,
    "preview": "{\n  \"name\": \"dva-example\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"roadhog server\",\n    \"build\": \"roadhog build\""
  },
  {
    "path": "examples/func-test/src/components/Example.js",
    "chars": 134,
    "preview": "import React from 'react';\n\nconst Example = () => {\n  return <div>Example</div>;\n};\n\nExample.propTypes = {};\n\nexport def"
  },
  {
    "path": "examples/func-test/src/index.css",
    "chars": 49,
    "preview": "\nhtml, body, :global(#root) {\n  height: 100%;\n}\n\n"
  },
  {
    "path": "examples/func-test/src/index.ejs",
    "chars": 296,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "examples/func-test/src/index.js",
    "chars": 246,
    "preview": "import dva from 'dva';\nimport './index.css';\n\n// 1. Initialize\nconst app = dva();\n\n// 2. Plugins\n// app.use({});\n\n// 3. "
  },
  {
    "path": "examples/func-test/src/models/example.js",
    "chars": 463,
    "preview": "export default {\n  namespace: 'example',\n\n  state: {},\n\n  subscriptions: {\n    setup({ dispatch, history }) {\n      // e"
  },
  {
    "path": "examples/func-test/src/router.js",
    "chars": 372,
    "preview": "import React from 'react';\nimport { routerRedux, Route, Switch } from 'dva/router';\nimport IndexPage from './routes/Inde"
  },
  {
    "path": "examples/func-test/src/routes/IndexPage.css",
    "chars": 423,
    "preview": "\n.normal {\n  font-family: Georgia, sans-serif;\n  margin-top: 3em;\n  text-align: center;\n}\n\n.title {\n  font-size: 2.5rem;"
  },
  {
    "path": "examples/func-test/src/routes/IndexPage.js",
    "chars": 668,
    "preview": "import React from 'react';\nimport { connect } from 'dva';\nimport styles from './IndexPage.css';\n\nfunction IndexPage() {\n"
  },
  {
    "path": "examples/func-test/src/services/example.js",
    "chars": 107,
    "preview": "import request from '../utils/request';\n\nexport async function query() {\n  return request('/api/users');\n}\n"
  },
  {
    "path": "examples/func-test/src/utils/request.js",
    "chars": 737,
    "preview": "import fetch from 'dva/fetch';\n\nfunction parseJSON(response) {\n  return response.json();\n}\n\nfunction checkStatus(respons"
  },
  {
    "path": "examples/user-dashboard/.editorconfig",
    "chars": 245,
    "preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_tr"
  },
  {
    "path": "examples/user-dashboard/.eslintrc",
    "chars": 22,
    "preview": "{\n  \"extends\": \"umi\"\n}"
  },
  {
    "path": "examples/user-dashboard/.gitignore",
    "chars": 36,
    "preview": "dist\nnode_modules\n.DS_Store\n\n.idea/\n"
  },
  {
    "path": "examples/user-dashboard/.umirc.js",
    "chars": 51,
    "preview": "export default {\n  plugins: ['umi-plugin-dva'],\n};\n"
  },
  {
    "path": "examples/user-dashboard/.webpackrc",
    "chars": 340,
    "preview": "{\n  \"theme\": {\n    \"@primary-color\": \"#dc6aac\",\n    \"@link-color\": \"#dc6aac\",\n    \"@border-radius-base\": \"2px\",\n    \"@fo"
  },
  {
    "path": "examples/user-dashboard/README.md",
    "chars": 413,
    "preview": "# dva-example-user-dashboard\n\n详见[《12 步 30 分钟,完成用户管理的 CURD 应用 (react+dva+antd)》](https://github.com/sorrycc/blog/issues/1"
  },
  {
    "path": "examples/user-dashboard/package.json",
    "chars": 575,
    "preview": "{\n  \"name\": \"dva-example-user-dashboard\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"umi dev\",\n    \"build\": \"umi bu"
  },
  {
    "path": "examples/user-dashboard/src/constants.js",
    "chars": 28,
    "preview": "export const PAGE_SIZE = 3;\n"
  },
  {
    "path": "examples/user-dashboard/src/global.css",
    "chars": 48,
    "preview": "\nhtml, body, :global(#root) {\n  height: 100%;\n}\n"
  },
  {
    "path": "examples/user-dashboard/src/layouts/Header.js",
    "chars": 805,
    "preview": "import React from 'react';\nimport { Menu, Icon } from 'antd';\nimport Link from 'umi/link';\nimport withRouter from 'umi/w"
  },
  {
    "path": "examples/user-dashboard/src/layouts/index.css",
    "chars": 161,
    "preview": "\n.normal {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n\n.content {\n  flex: 1;\n  display: flex;\n}\n\n.main"
  },
  {
    "path": "examples/user-dashboard/src/layouts/index.js",
    "chars": 375,
    "preview": "import React from 'react';\nimport styles from './index.css';\nimport Header from './Header';\n\nfunction MainLayout({ child"
  },
  {
    "path": "examples/user-dashboard/src/pages/index.css",
    "chars": 423,
    "preview": "\n.normal {\n  font-family: Georgia, sans-serif;\n  margin-top: 3em;\n  text-align: center;\n}\n\n.title {\n  font-size: 2.5rem;"
  },
  {
    "path": "examples/user-dashboard/src/pages/index.js",
    "chars": 595,
    "preview": "import React from 'react';\nimport styles from './index.css';\n\nfunction IndexPage() {\n  return (\n    <div className={styl"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/components/Users/UserModal.js",
    "chars": 1912,
    "preview": "import React, { Component } from 'react';\nimport { Modal, Form, Input } from 'antd';\n\nconst FormItem = Form.Item;\n\nclass"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/components/Users/Users.css",
    "chars": 86,
    "preview": "\n.normal {\n}\n\n.create {\n  margin-bottom: 1.5em;\n}\n\n.operation a {\n  margin: 0 .5em;\n}\n"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/components/Users/Users.js",
    "chars": 2536,
    "preview": "import React from 'react';\nimport { connect } from 'dva';\nimport { Table, Pagination, Popconfirm, Button } from 'antd';\n"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/models/users.js",
    "chars": 1451,
    "preview": "import * as usersService from '../services/users';\n\nexport default {\n  namespace: 'users',\n  state: {\n    list: [],\n    "
  },
  {
    "path": "examples/user-dashboard/src/pages/users/page.css",
    "chars": 51,
    "preview": "\n.normal {\n  width: 900px;\n  margin: 3em auto 0;\n}\n"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/page.js",
    "chars": 248,
    "preview": "import React from 'react';\nimport styles from './page.css';\nimport UsersComponent from './components/Users/Users';\n\nfunc"
  },
  {
    "path": "examples/user-dashboard/src/pages/users/services/users.js",
    "chars": 564,
    "preview": "import request from '../../../utils/request';\nimport { PAGE_SIZE } from '../../../constants';\n\nexport function fetch({ p"
  },
  {
    "path": "examples/user-dashboard/src/plugins/onError.js",
    "chars": 161,
    "preview": "import { message } from 'antd';\n\nconst ERROR_MSG_DURATION = 3; // 3 秒\n\nexport default {\n  onError(e) {\n    message.error"
  },
  {
    "path": "examples/user-dashboard/src/utils/request.js",
    "chars": 861,
    "preview": "import fetch from 'dva/fetch';\n\nfunction checkStatus(response) {\n  if (response.status >= 200 && response.status < 300) "
  },
  {
    "path": "examples/with-immer/.umirc.js",
    "chars": 51,
    "preview": "export default {\n  plugins: ['umi-plugin-dva'],\n};\n"
  },
  {
    "path": "examples/with-immer/dva.js",
    "chars": 99,
    "preview": "import useImmer from 'dva-immer';\n\nexport function config() {\n  return {\n    ...useImmer(),\n  };\n}\n"
  },
  {
    "path": "examples/with-immer/model.js",
    "chars": 279,
    "preview": "export default {\n  namespace: 'count',\n  state: {\n    a: {\n      b: {\n        c: {\n          count: 0,\n        },\n      "
  },
  {
    "path": "examples/with-immer/package.json",
    "chars": 92,
    "preview": "{\n  \"dependencies\": {\n    \"umi\": \"*\",\n    \"umi-plugin-dva\": \"*\",\n    \"dva-immer\": \"*\"\n  }\n}\n"
  },
  {
    "path": "examples/with-immer/pages/index.js",
    "chars": 730,
    "preview": "import { connect } from 'dva';\nimport { Button } from 'antd-mobile';\n\nfunction App({ count, newProp, dispatch }) {\n  ret"
  },
  {
    "path": "examples/with-nextjs/.babelrc",
    "chars": 170,
    "preview": "{\n    \"presets\": [\n      \"next/babel\"\n    ],\n    \"plugins\": [\n      [\"module-resolver\", {\n        \"alias\": {\n          \""
  },
  {
    "path": "examples/with-nextjs/.eslintignore",
    "chars": 25,
    "preview": ".next/*\nnode_modules/*\n\n\n"
  },
  {
    "path": "examples/with-nextjs/.eslintrc",
    "chars": 1230,
    "preview": "{\n    \"parser\": \"babel-eslint\",\n    \"extends\": \"airbnb\",\n    \"settings\": {\n      \"import/resolver\": {\n        \"babel-mod"
  },
  {
    "path": "examples/with-nextjs/.gitignore",
    "chars": 22,
    "preview": "/node_modules/\n/.next/"
  },
  {
    "path": "examples/with-nextjs/README.md",
    "chars": 99,
    "preview": "# dva_next\ncompose dva.js with next.js\n\n1.install\n\n```\nnpm install\n```\n\n2.start\n\n```\nnpm start\n```\n"
  },
  {
    "path": "examples/with-nextjs/model/homepage.js",
    "chars": 515,
    "preview": "const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));\n\nconst model = {\n  namespace: 'index',\n  "
  },
  {
    "path": "examples/with-nextjs/model/index.js",
    "chars": 90,
    "preview": "import homepage from './homepage';\n\nconst model = [\n  homepage,\n];\n\nexport default model;\n"
  },
  {
    "path": "examples/with-nextjs/package.json",
    "chars": 722,
    "preview": "{\n  \"name\": \"dva-next.js\",\n  \"version\": \"1.0.0\",\n  \"description\": \"dva-next.js-example\",\n  \"main\": \"index.js\",\n  \"script"
  },
  {
    "path": "examples/with-nextjs/pages/index.js",
    "chars": 1374,
    "preview": "\nimport Link from 'next/link';\nimport React from 'react';\nimport WithDva from '../utils/store';\n\nclass Page extends Reac"
  },
  {
    "path": "examples/with-nextjs/pages/users.js",
    "chars": 201,
    "preview": "\nimport Link from 'next/link';\n\nexport default function () {\n  return (\n    <div>\n      Users\n      <br />\n      <Link h"
  },
  {
    "path": "examples/with-nextjs/utils/store.js",
    "chars": 2392,
    "preview": "import React from 'react';\nimport dva, { connect } from 'dva-no-router';\nimport { Provider } from 'react-redux';\nimport "
  },
  {
    "path": "examples/with-react-router-3/.eslintrc",
    "chars": 897,
    "preview": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"airbnb\",\n  \"rules\": {\n    \"generator-star-spacing\": [0],\n    \"consistent-ret"
  },
  {
    "path": "examples/with-react-router-3/.roadhogrc",
    "chars": 479,
    "preview": "{\n  \"entry\": \"src/index.js\",\n  \"env\": {\n    \"development\": {\n      \"extraBabelPlugins\": [\n        \"dva-hmr\",\n        \"tr"
  },
  {
    "path": "examples/with-react-router-3/.roadhogrc.mock.js",
    "chars": 19,
    "preview": "export default {};\n"
  },
  {
    "path": "examples/with-react-router-3/package.json",
    "chars": 822,
    "preview": "{\n  \"name\": \"dva-example-react-router-3\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"roadhog server\",\n    \"build\": "
  },
  {
    "path": "examples/with-react-router-3/src/components/Example.js",
    "chars": 134,
    "preview": "import React from 'react';\n\nconst Example = () => {\n  return <div>Example</div>;\n};\n\nExample.propTypes = {};\n\nexport def"
  },
  {
    "path": "examples/with-react-router-3/src/index.css",
    "chars": 49,
    "preview": "\nhtml, body, :global(#root) {\n  height: 100%;\n}\n\n"
  },
  {
    "path": "examples/with-react-router-3/src/index.ejs",
    "chars": 217,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "examples/with-react-router-3/src/index.js",
    "chars": 246,
    "preview": "import dva from 'dva';\nimport './index.css';\n\n// 1. Initialize\nconst app = dva();\n\n// 2. Plugins\n// app.use({});\n\n// 3. "
  },
  {
    "path": "examples/with-react-router-3/src/models/example.js",
    "chars": 384,
    "preview": "export default {\n  namespace: 'example',\n\n  state: {},\n\n  subscriptions: {\n    setup({ dispatch, history }) {\n      // e"
  },
  {
    "path": "examples/with-react-router-3/src/router.js",
    "chars": 293,
    "preview": "import React from 'react';\nimport { Router, Route } from 'dva/router';\nimport IndexPage from './routes/IndexPage';\n\nfunc"
  },
  {
    "path": "examples/with-react-router-3/src/routes/IndexPage.css",
    "chars": 423,
    "preview": "\n.normal {\n  font-family: Georgia, sans-serif;\n  margin-top: 3em;\n  text-align: center;\n}\n\n.title {\n  font-size: 2.5rem;"
  },
  {
    "path": "examples/with-react-router-3/src/routes/IndexPage.js",
    "chars": 668,
    "preview": "import React from 'react';\nimport { connect } from 'dva';\nimport styles from './IndexPage.css';\n\nfunction IndexPage() {\n"
  },
  {
    "path": "examples/with-react-router-3/src/services/example.js",
    "chars": 107,
    "preview": "import request from '../utils/request';\n\nexport async function query() {\n  return request('/api/users');\n}\n"
  },
  {
    "path": "examples/with-react-router-3/src/utils/request.js",
    "chars": 737,
    "preview": "import fetch from 'dva/fetch';\n\nfunction parseJSON(response) {\n  return response.json();\n}\n\nfunction checkStatus(respons"
  },
  {
    "path": "examples/with-redux-undo/.babelrc",
    "chars": 81,
    "preview": "{\n  \"presets\": [\"env\", \"react\"],\n  \"plugins\": [\"transform-object-rest-spread\"]\n}\n"
  },
  {
    "path": "examples/with-redux-undo/.gitignore",
    "chars": 318,
    "preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/cov"
  },
  {
    "path": "examples/with-redux-undo/.npmrc",
    "chars": 18,
    "preview": "package-lock=false"
  },
  {
    "path": "examples/with-redux-undo/README.md",
    "chars": 179,
    "preview": "# Redux Undo Sample\n\n```\nnpm install\nnpm start\n```\n\nor\n\n```\nyarn\nyarn start\n```\n\nOpen http://localhost:1234 to view Coun"
  },
  {
    "path": "examples/with-redux-undo/package.json",
    "chars": 497,
    "preview": "{\n  \"name\": \"dva-example-redux-redu\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"parcel ./src/index.html \"\n  },\n  \""
  },
  {
    "path": "examples/with-redux-undo/src/index.html",
    "chars": 263,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"utf-8\" />\n  <title>dva-example-redux-redu</title>\n  <meta name=\"viewport"
  },
  {
    "path": "examples/with-redux-undo/src/index.js",
    "chars": 1228,
    "preview": "import React from 'react';\nimport dva, { connect } from 'dva';\nimport undoable, { ActionCreators } from 'redux-undo';\n\ni"
  },
  {
    "path": "examples/with-redux-undo/src/models/counter.js",
    "chars": 173,
    "preview": "export default {\n  namespace: 'counter',\n  state: 0,\n  reducers: {\n    add(state) {\n      return state + 1;\n    },\n    m"
  },
  {
    "path": "jest.config.js",
    "chars": 84,
    "preview": "module.exports = {\n  collectCoverageFrom: ['packages/**/src/*.{ts,tsx,js,jsx}'],\n};\n"
  },
  {
    "path": "lerna.json",
    "chars": 479,
    "preview": "{\n  \"changelog\": {\n    \"repo\": \"dvajs/dva\",\n    \"labels\": {\n      \"pr(enhancement)\": \":rocket: Enhancement\",\n      \"pr(b"
  },
  {
    "path": "package.json",
    "chars": 1364,
    "preview": "{\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"father-build\",\n    \"doc:dev\": \"./website/node_modules/.bin/vuepress de"
  },
  {
    "path": "packages/dva/.fatherrc.js",
    "chars": 123,
    "preview": "export default {\n  entry: ['src/index.js', 'src/dynamic.js'],\n  cjs: 'rollup',\n  esm: 'rollup',\n  runtimeHelpers: true,\n"
  },
  {
    "path": "packages/dva/README.md",
    "chars": 589,
    "preview": "# dva\n\n[![NPM version](https://img.shields.io/npm/v/dva.svg?style=flat)](https://npmjs.org/package/dva)\n[![Build Status]"
  },
  {
    "path": "packages/dva/dynamic.d.ts",
    "chars": 102,
    "preview": "declare const dynamic: (resolve: (value?: PromiseLike<any>) => void) => void;\nexport default dynamic;\n"
  },
  {
    "path": "packages/dva/dynamic.js",
    "chars": 102,
    "preview": "require('./warnAboutDeprecatedCJSRequire.js')('dynamic');\nmodule.exports = require('./dist/dynamic');\n"
  },
  {
    "path": "packages/dva/fetch.d.ts",
    "chars": 80,
    "preview": "import * as isomorphicFetch from 'isomorphic-fetch';\n\nexport = isomorphicFetch;\n"
  },
  {
    "path": "packages/dva/fetch.js",
    "chars": 102,
    "preview": "require('./warnAboutDeprecatedCJSRequire.js')('fetch');\nmodule.exports = require('isomorphic-fetch');\n"
  },
  {
    "path": "packages/dva/index.d.ts",
    "chars": 3366,
    "preview": "import {\n  Reducer,\n  Action,\n  AnyAction,\n  ReducersMapObject,\n  MiddlewareAPI,\n  StoreEnhancer,\n  bindActionCreators\n}"
  },
  {
    "path": "packages/dva/package.json",
    "chars": 1381,
    "preview": "{\n  \"name\": \"dva\",\n  \"version\": \"3.0.0-alpha.1\",\n  \"description\": \"React and redux based, lightweight and elm-style fram"
  },
  {
    "path": "packages/dva/router.d.ts",
    "chars": 114,
    "preview": "import * as routerRedux from 'connected-react-router';\n\nexport * from 'react-router-dom';\nexport { routerRedux };\n"
  },
  {
    "path": "packages/dva/router.js",
    "chars": 167,
    "preview": "require('./warnAboutDeprecatedCJSRequire.js')('router');\nmodule.exports = require('react-router-dom');\nmodule.exports.ro"
  },
  {
    "path": "packages/dva/saga.js",
    "chars": 98,
    "preview": "require('./warnAboutDeprecatedCJSRequire.js')('saga');\nmodule.exports = require('dva-core/saga');\n"
  },
  {
    "path": "packages/dva/src/dynamic.js",
    "chars": 2276,
    "preview": "import React, { Component } from 'react';\n\nconst cached = {};\nfunction registerModel(app, model) {\n  model = model.defau"
  },
  {
    "path": "packages/dva/src/index.js",
    "chars": 4741,
    "preview": "import React from 'react';\nimport invariant from 'invariant';\nimport { createBrowserHistory, createMemoryHistory, create"
  },
  {
    "path": "packages/dva/test/index.e2e.js",
    "chars": 5591,
    "preview": "import React from 'react';\nimport { render, fireEvent, cleanup } from 'react-testing-library';\nimport dva, {\n  connect,\n"
  },
  {
    "path": "packages/dva/test/index.test.js",
    "chars": 3230,
    "preview": "import expect from 'expect';\nimport React from 'react';\nimport dva, {\n  useDispatch,\n  useSelector,\n  useStore,\n  useHis"
  },
  {
    "path": "packages/dva/warnAboutDeprecatedCJSRequire.js",
    "chars": 879,
    "preview": "var printWarning = function() {};\n\nif (process.env.NODE_ENV !== 'production') {\n  printWarning = function(format, subs) "
  },
  {
    "path": "packages/dva-core/.fatherrc.js",
    "chars": 78,
    "preview": "export default {\n  cjs: 'rollup',\n  esm: 'rollup',\n  runtimeHelpers: true,\n};\n"
  },
  {
    "path": "packages/dva-core/README.md",
    "chars": 660,
    "preview": "# dva-core\n\n[![NPM version](https://img.shields.io/npm/v/dva-core.svg?style=flat)](https://npmjs.org/package/dva-core)\n["
  },
  {
    "path": "packages/dva-core/package.json",
    "chars": 1061,
    "preview": "{\n  \"name\": \"dva-core\",\n  \"version\": \"2.0.4\",\n  \"description\": \"The core lightweight library for dva, based on redux and"
  },
  {
    "path": "packages/dva-core/saga.js",
    "chars": 40,
    "preview": "module.exports = require('redux-saga');\n"
  },
  {
    "path": "packages/dva-core/src/Plugin.js",
    "chars": 2239,
    "preview": "import invariant from 'invariant';\nimport { isPlainObject } from './utils';\n\nconst hooks = [\n  'onError',\n  'onStateChan"
  },
  {
    "path": "packages/dva-core/src/checkModel.js",
    "chars": 1666,
    "preview": "import invariant from 'invariant';\nimport { isArray, isFunction, isPlainObject } from './utils';\n\nexport default functio"
  },
  {
    "path": "packages/dva-core/src/constants.js",
    "chars": 34,
    "preview": "export const NAMESPACE_SEP = '/';\n"
  },
  {
    "path": "packages/dva-core/src/createPromiseMiddleware.js",
    "chars": 751,
    "preview": "import { NAMESPACE_SEP } from './constants';\n\nexport default function createPromiseMiddleware(app) {\n  return () => next"
  },
  {
    "path": "packages/dva-core/src/createStore.js",
    "chars": 1115,
    "preview": "import { createStore, applyMiddleware, compose } from 'redux';\nimport flatten from 'flatten';\nimport invariant from 'inv"
  },
  {
    "path": "packages/dva-core/src/getReducer.js",
    "chars": 407,
    "preview": "import defaultHandleActions from './handleActions';\n\nexport default function getReducer(reducers, state, handleActions) "
  },
  {
    "path": "packages/dva-core/src/getSaga.js",
    "chars": 5144,
    "preview": "import invariant from 'invariant';\nimport warning from 'warning';\nimport { effects as sagaEffects } from 'redux-saga';\ni"
  },
  {
    "path": "packages/dva-core/src/handleActions.js",
    "chars": 785,
    "preview": "import invariant from 'invariant';\n\nfunction identify(value) {\n  return value;\n}\n\nfunction handleAction(actionType, redu"
  },
  {
    "path": "packages/dva-core/src/index.js",
    "chars": 6868,
    "preview": "import { combineReducers } from 'redux';\nimport createSagaMiddleware, * as saga from 'redux-saga';\nimport invariant from"
  },
  {
    "path": "packages/dva-core/src/prefixNamespace.js",
    "chars": 994,
    "preview": "import warning from 'warning';\nimport { isArray } from './utils';\nimport { NAMESPACE_SEP } from './constants';\n\nfunction"
  },
  {
    "path": "packages/dva-core/src/prefixType.js",
    "chars": 498,
    "preview": "import { NAMESPACE_SEP } from './constants';\n\nexport default function prefixType(type, model) {\n  const prefixedType = `"
  },
  {
    "path": "packages/dva-core/src/prefixedDispatch.js",
    "chars": 579,
    "preview": "import invariant from 'invariant';\nimport warning from 'warning';\nimport { NAMESPACE_SEP } from './constants';\nimport pr"
  },
  {
    "path": "packages/dva-core/src/subscription.js",
    "chars": 1079,
    "preview": "import warning from 'warning';\nimport { isFunction } from './utils';\nimport prefixedDispatch from './prefixedDispatch';\n"
  },
  {
    "path": "packages/dva-core/src/utils.js",
    "chars": 413,
    "preview": "import isPlainObject from 'is-plain-object';\nexport { isPlainObject };\nexport const isArray = Array.isArray.bind(Array);"
  },
  {
    "path": "packages/dva-core/test/checkModel.test.js",
    "chars": 2566,
    "preview": "import { create } from '../src/index';\n\ndescribe('checkModel', () => {\n  it('namespace should be defined', () => {\n    c"
  },
  {
    "path": "packages/dva-core/test/effects.test.js",
    "chars": 20207,
    "preview": "import expect from 'expect';\nimport mm from 'mm';\nimport { create } from '../src/index';\n\nconst delay = timeout => new P"
  },
  {
    "path": "packages/dva-core/test/handleActions.test.js",
    "chars": 1226,
    "preview": "import expect from 'expect';\nimport handleActions from '../src/handleActions';\n\ndescribe('handleActions', () => {\n  cons"
  },
  {
    "path": "packages/dva-core/test/model.test.js",
    "chars": 6064,
    "preview": "import EventEmitter from 'events';\nimport { create } from '../src/index';\n\ndescribe('app.model', () => {\n  it('dynamic m"
  },
  {
    "path": "packages/dva-core/test/optsAndHooks.test.js",
    "chars": 4241,
    "preview": "import expect from 'expect';\nimport { create } from '../src/index';\n\nfunction delay(timeout) {\n  return new Promise(reso"
  },
  {
    "path": "packages/dva-core/test/plugin.test.js",
    "chars": 1266,
    "preview": "import expect from 'expect';\nimport Plugin from '../src/Plugin';\n\ndescribe('plugin', () => {\n  it('basic', () => {\n    l"
  },
  {
    "path": "packages/dva-core/test/reducers.test.js",
    "chars": 3649,
    "preview": "import expect from 'expect';\nimport { create } from '../src/index';\n\ndescribe('reducers', () => {\n  it('enhancer', () =>"
  },
  {
    "path": "packages/dva-core/test/repalceModel.test.js",
    "chars": 5102,
    "preview": "import expect from 'expect';\nimport EventEmitter from 'events';\nimport { create } from '../src/index';\n\ndescribe('app.re"
  },
  {
    "path": "packages/dva-core/test/subscriptions.test.js",
    "chars": 2784,
    "preview": "import expect from 'expect';\nimport { create } from '../src/index';\n\ndescribe('subscriptions', () => {\n  it('dispatch ac"
  },
  {
    "path": "packages/dva-core/test/utils.test.js",
    "chars": 872,
    "preview": "import expect from 'expect';\nimport { findIndex } from '../src/utils';\n\ndescribe('utils', () => {\n  describe('#findIndex"
  },
  {
    "path": "packages/dva-immer/.fatherrc.js",
    "chars": 78,
    "preview": "export default {\n  cjs: 'rollup',\n  esm: 'rollup',\n  runtimeHelpers: true,\n};\n"
  },
  {
    "path": "packages/dva-immer/README.md",
    "chars": 939,
    "preview": "# dva-immer\n\n[![NPM version](https://img.shields.io/npm/v/dva-immer.svg?style=flat)](https://npmjs.org/package/dva-immer"
  },
  {
    "path": "packages/dva-immer/package.json",
    "chars": 707,
    "preview": "{\n  \"name\": \"dva-immer\",\n  \"version\": \"1.0.2\",\n  \"description\": \"Auto loading data binding plugin for dva.\",\n  \"main\": \""
  },
  {
    "path": "packages/dva-immer/src/index.js",
    "chars": 771,
    "preview": "import produce from 'immer';\n\nexport { enableES5, enableAllPlugins } from 'immer';\n\nexport default function() {\n  return"
  },
  {
    "path": "packages/dva-immer/test/index.test.js",
    "chars": 1653,
    "preview": "import dva from 'dva';\nimport userImmer from '../src/index';\n\ndescribe('dva-immer', () => {\n  it('normal', () => {\n    c"
  },
  {
    "path": "packages/dva-loading/.fatherrc.js",
    "chars": 78,
    "preview": "export default {\n  cjs: 'rollup',\n  esm: 'rollup',\n  runtimeHelpers: true,\n};\n"
  },
  {
    "path": "packages/dva-loading/README.md",
    "chars": 1266,
    "preview": "# dva-loading\n\n[![NPM version](https://img.shields.io/npm/v/dva-loading.svg?style=flat)](https://npmjs.org/package/dva-l"
  },
  {
    "path": "packages/dva-loading/index.d.ts",
    "chars": 159,
    "preview": "export interface DvaLoadingState {\n  global: boolean;\n  models: { [type: string]: boolean | undefined };\n  effects: { [t"
  },
  {
    "path": "packages/dva-loading/package.json",
    "chars": 737,
    "preview": "{\n  \"name\": \"dva-loading\",\n  \"version\": \"3.0.25\",\n  \"description\": \"Auto loading data binding plugin for dva.\",\n  \"main\""
  },
  {
    "path": "packages/dva-loading/src/index.js",
    "chars": 2256,
    "preview": "const SHOW = '@@DVA_LOADING/SHOW';\nconst HIDE = '@@DVA_LOADING/HIDE';\nconst NAMESPACE = 'loading';\n\nfunction createLoadi"
  },
  {
    "path": "packages/dva-loading/test/core.test.js",
    "chars": 7798,
    "preview": "import expect from 'expect';\nimport { create as dva } from 'dva-core';\nimport createLoading from '../src/index';\n\nconst "
  },
  {
    "path": "packages/dva-loading/test/index.test.js",
    "chars": 7362,
    "preview": "import expect from 'expect';\nimport dva from 'dva';\nimport createLoading from '../src/index';\n\nconst delay = timeout => "
  },
  {
    "path": "scripts/publish.js",
    "chars": 1771,
    "preview": "#!/usr/bin/env node\n\nconst shell = require('shelljs');\nconst { join } = require('path');\nconst { fork } = require('child"
  },
  {
    "path": "website/.gitignore",
    "chars": 15,
    "preview": "/node_modules\n\n"
  },
  {
    "path": "website/now.json",
    "chars": 113,
    "preview": "{\n  \"name\": \"dva\",\n  \"version\": 2,\n  \"builds\": [\n    { \"src\": \"package.json\", \"use\": \"@now/static-build\" }\n  ]\n}\n"
  },
  {
    "path": "website/package.json",
    "chars": 209,
    "preview": "{\n  \"scripts\": {\n    \"now-build\": \"echo empty build\",\n    \"deploy\": \"vuepress build ../docs --dest ./dist && now && now "
  }
]

About this extraction

This page contains the full source code of the dvajs/dva GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 170 files (275.7 KB), approximately 86.6k tokens, and a symbol index with 264 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!