DvaJS: React and redux based, lightweight and elm-style framework.javascript
实例项目源码:https://github.com/AK-47-D/re...html
经过 npm 安装 dva-cli 并确保版本是 0.9.1
或以上。前端
$ npm install dva-cli -g $ dva -v dva-cli version 0.9.1
安装完 dva-cli 以后,就能够在命令行里访问到 dva
命令(不能访问?)。如今,你能够经过 dva new
建立新应用。java
$ dva new dva-quickstart
这会建立 dva-quickstart
目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。node
而后咱们 cd
进入 dva-quickstart
目录,并启动开发服务器:react
$ cd dva-quickstart $ npm start
几秒钟后,你会看到如下输出:webpack
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 的欢迎界面。git
经过 npm 安装 antd
和 babel-plugin-import
。babel-plugin-import
是用来按需加载 antd 的脚本和样式的,详见 repo 。github
$ npm install antd babel-plugin-import --save (国内镜像:tnpm)
编辑 .webpackrc
,使 babel-plugin-import
插件生效。
{ + "extraBabelPlugins": [ + ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }] + ] }
注:dva-cli 基于 roadhog 实现 build 和 dev,更多
.webpackrc
的配置详见
roadhog#配置
咱们要写个应用来先显示产品列表。首先第一步是建立路由,路由能够想象成是组成应用的不一样页面。
新建 route component routes/Products.js
,内容以下:
import React from 'react'; const Products = (props) => ( <h2>List of Products</h2> ); export default Products;
添加路由信息到路由表,编辑 router.js
:
+ import Products from './routes/Products'; ... + <Route path="/products" exact component={Products} />
而后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的 <h2>
标签。
随着应用的发展,你会须要在多个页面分享 UI 元素 (或在一个页面使用屡次),在 dva 里你能够把这部分抽成 component 。
咱们来编写一个 ProductList
component,这样就能在不一样的地方显示产品列表了。
新建 components/ProductList.js
文件:
import React from 'react' import PropTypes from 'prop-types' import {Button, Popconfirm, Table} from 'antd' /** * React Component 有 3 种定义方式,分别是: * React.createClass, class 和 Stateless Functional Component。 * * 推荐最后一种,保持简洁和无状态。 * 这是函数,不是 Object,没有 this 做用域,是 pure function。 * @param onDelFn * @param products * @returns {XML} * @constructor */ const ProductList = ({onDelFn, products}) => { const columns = [ // 表格的列 { title: 'ID', dataIndex: 'id' }, { title: 'Name', dataIndex: 'name' }, { title: 'Actions', render: (text, record) => { return ( <Popconfirm title={'Delete?'} onConfirm={() => { onDelFn(record.id) }}> <Button>Delete</Button> </Popconfirm> ) } } ] return (<Table dataSource={products} columns={columns}/>) } ProductList.propTypes = { onDelFn: PropTypes.func.isRequired, products: PropTypes.array.isRequired } // 记得导出哦! export default ProductList;
完成 UI 后,如今开始处理数据和逻辑。
dva 经过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
新建 model models/products.js
:
export default { namespace: 'products', state: [], reducers: { 'delete'(state, { payload: id }) { return state.filter(item => item.id !== id); }, }, };
这个 model 里:
namespace
表示在全局 state 上的 keystate
是初始值,在这里是空数组reducers
等同于 redux 里的 reducer,接收 action,同步更新 state而后别忘记在 index.js
里载入他:
// 3\. Model + app.model(require('./models/products').default);
到这里,咱们已经单独完成了 model 和 component,那么他们如何串联起来呢?
dva 提供了 connect 方法。若是你熟悉 redux,这个 connect 就是 react-redux 的 connect 。
编辑 routes/Products.js
,替换为如下内容:
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
:
- const app = dva(); + const app = dva({ + initialState: { + products: [ + { name: 'dva', id: 1 }, + { name: 'antd', id: 2 }, + ], + }, + });
刷新浏览器,应该能看到如下效果:
完成开发而且在开发环境验证以后,就须要部署给咱们的用户了。先执行下面的命令:
$ npm run build
几秒后,输出应该以下:
> @ 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/
目录下找到这些文件。
数据的改变发生一般是经过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候能够经过 dispatch
发起一个 action,若是是同步行为会直接经过 Reducers
改变 State
,若是是异步行为(反作用)会先触发 Effects
而后流向 Reducers
最终改变 State
,因此在 dva 中,数据流向很是清晰简明,而且思路基本跟开源社区保持一致(也是来自于开源社区)。
type State = any
State 表示 Model 的状态数据,一般表现为一个 javascript 对象(固然它能够是任何值);操做的时候每次都要看成不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
在 dva 中你能够经过 dva 的实例属性 _store
看到顶部的 state 数据,可是一般你不多会用到:
const app = dva(); console.log(app._store); // 顶部的 state 数据
type AsyncAction = any
Action 是一个普通 javascript 对象,它是改变 State 的惟一途径。不管是从 UI 事件、网络回调,仍是 WebSocket 等数据源所得到的数据,最终都会经过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type
属性指明具体的行为,其它字段能够自定义,若是要发起一个 action 须要使用 dispatch
函数;须要注意的是 dispatch
是在组件 connect Models之后,经过 props 传入的。
dispatch({ type: 'add', });
type dispatch = (a: Action) => Action
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的惟一途径,可是它只描述了一个行为,而 dipatch 能够看做是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件经过 props 能够访问到 dispatch,能够调用 Model 中的 Reducer 或者 Effects,常见的形式如:
dispatch({ type: 'user/add', // 若是在 model 外调用,须要添加 namespace payload: {}, // 须要传递的信息 });
type Reducer<S, A> = (state: S, action: A) => S
Reducer(也称为 reducing function)函数接受两个参数:以前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。
Reducer 的概念来自因而函数式编程,不少语言中都有 reduce API。如在 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 必须是纯函数,因此一样的输入必然获得一样的输出,它们不该该产生任何反作用。而且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操做都是返回一个全新的数据(独立,纯净),因此热重载和时间旅行这些功能才可以使用。
Effect 被称为反作用,在咱们的应用中,最多见的就是异步操做。它来自于函数编程的概念,之因此叫反作用是由于它使得咱们的函数变得不纯,一样的输入不必定得到一样的输出。
dva 为了控制反作用的操做,底层引入了redux-sagas作异步流程控制,因为采用了generator的相关概念,因此将异步转成同步写法,从而将effects转为纯函数。至于为何咱们这么纠结于 纯函数,若是你想了解更多能够阅读Mostly adequate guide to FP,或者它的中文译本JS函数式编程指南。
Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,而后根据条件 dispatch 须要的 action。数据源能够是当前的时间、服务器的 websocket 链接、keyboard 输入、geolocation 变化、history 路由变化等等。
import key from 'keymaster'; ... app.model({ namespace: 'count', subscriptions: { keyEvent({dispatch}) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, } });
这里的路由一般指的是前端路由,因为咱们的应用如今一般是单页应用,因此须要前端代码来控制路由逻辑,经过浏览器提供的 History API 能够监听浏览器url的变化,从而控制路由相关操做。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
import { Router, Route } from 'dva/router'; app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> );
在组件设计方法中,咱们提到过 Container Components,在 dva 中咱们一般将其约束为 Route Components,由于在 dva 中咱们一般以页面维度来设计 Container Components。
因此在 dva 中,一般须要 connect Model的组件都是 Route Components,组织在/routes/
目录下,而/components/
目录下则是纯组件(Presentational Components)。
做者:至正
原文连接: https://yuque.com/flying.ni/the-tower/tvzasn
最多见的 Web 类示例之一: TodoList = Todo list + Add todo button
按照 React 官方指导意见, 若是多个 Component 之间要发生交互, 那么状态(即: 数据)就维护在这些 Component 的最小公约父节点上, 也便是 <App/>
<TodoList/> <Todo/>
以及<AddTodoBtn/>
自己不维持任何 state, 彻底由父节点<App/>
传入 props 以决定其展示, 是一个纯函数的存在形式, 即: Pure Component
React 只负责页面渲染, 而不负责页面逻辑, 页面逻辑能够从中单独抽取出来, 变成 store
与图一相比, 几个明显的改进点:
<App/>
里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer<TodoList/>
及<AddTodoBtn/>
都是 Pure Component, 经过 connect 方法能够很方便地给它俩加一层 wrapper 从而创建起与 store 的联系: 能够经过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态有变, 被 connect 的组件也随之刷新这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好
上面说了, 可使用 Middleware 拦截 action, 这样一来异步的网络操做也就很方便了, 作成一个 Middleware 就好了, 这里使用 redux-saga 这个类库, 举个栗子:
有了前面的三步铺垫, Dva 的出现也就水到渠成了, 正如 Dva 官网所言, Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 作了 3 件很重要的事情, 大大提高了编码体验:
约定优于配置, 老是好的
😆
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'}) }); }, }, });
国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。