

























原文链接:https://www.cnblogs.com/colorful-coco/p/9454315.html
Ant Design Pro 是一个企业级中后台前端/设计解决方案。本地环境需要安装 node 和 git,技术栈基于 ES2015+、React、dva、g2 和 antd。
参考:
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 除了和 React 一起用外,还支持其它界面库。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
连接 React 组件与 Redux store。
[mapStateToProps(state, [ownProps]): stateProps](Function)
如果定义该参数,组件将会监听 Redux store 的变化。只要 Redux store 发生改变,mapStateToProps 函数就会被调用,该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)。mapStateToProps 函数接收整个 Redux store 的 state 作为 props,然后返回一个传入到组件 props 的对象。注入 dispatch 和 todos:
function mapStateToProps(state) {
return { todos: state.todos }
}
export default connect(mapStateToProps)(TodoApp)
// 注入 dispatch 和全局 state
export default connect(state => state)(TodoApp)
// 不要这样做!这会导致每次 action 都触发整个 TodoApp 重新渲染
// 最好在多个组件上使用 connect(),每个组件只监听它所关联的部分 state。
Action 是把数据从应用(这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。
effects: {
*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 } });
},
}
创建一个 Effect 描述信息,用来命令 middleware 以参数 args 调用函数 fn 。
fn 的参数数组。创建一个 Effect 描述信息,用来命令 middleware 向 Store 发起一个 action。 这个 effect 是非阻塞型的,并且所有向下游抛出的错误(例如在 reducer 中),都不会冒泡回到 saga 当中。
创建一个 Effect,用来命令 middleware 在当前 Store 的 state 上调用指定的选择器。
selector: Function:一个 (state, ...args) => args 的函数。它接受当前 state 和一些可选参数,并返回当前 Store state 上的一部分数据。dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念。dva 帮你自动化了Redux 架构一些繁琐的步骤,比如redux store 的创建,中间件的配置,路由的初始化等等,只需写几行代码就可以实现上述步骤。
通过 npm 安装 antd 和 babel-plugin-import。babel-plugin-import 是用来按需加载 antd 的脚本和样式的。编辑 .webpackrc,使 babel-plugin-import 插件生效。
// .webpackrc.js
extraBabelPlugins: [['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }]]
// src/index.js 入口js
import dva from 'dva';
import browserHistory from 'history/createBrowserHistory';
import createLoading from 'dva-loading';
// 1. Initialize
const app = dva({
history: browserHistory(),
});
// 2. Plugins
app.use(createLoading());
// 3. Model
app.model(require('./models/global').default);
app.model(require('./models/menu').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root'); // 启动应用
dva(opts) 创建应用,返回 dva 实例。(注:dva 支持多实例)
opts 包含:
history:指定给路由用的 history,默认是 hashHistoryapp.router(({ history, app }) => RouterConfig)
注册路由表,推荐把路由信息抽成一个单独的文件,这样结合 babel-plugin-dva-hmr 可实现路由和组件的热加载(只更新页面修改的部分,不会刷新整个页面)。
// .webpackrc.js
env: {
development: {
extraBabelPlugins: ['dva-hmr'],
},
},
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
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' });
},*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 });
}
});
},
},
};
namespace:model 的命名空间,同时也是他在全局 state 上的属性
state:初始值
reducers:以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state 的地方。由 action 触发
effects:以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由 action 触发,可以触发 action,可以和服务器交互,可以获取全局 state 的数据等等。
subscriptions:以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
app.model(model):注册 model
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 },
})
);
}
const columns = [
{
title: 'Username',
dataIndex: 'username',
key: 'username',
render: text => <a href="">{text}</a>,
},
{
title: 'Street',
dataIndex: 'address.street',
key: 'street',
},
{
title: 'Website',
dataIndex: 'website',
key: 'website',
},
{
title: 'Operation',
key: 'operation',
render: (text, record) => (
<span className={styles.operation}>
<Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, record.id)}>
<a href="">Delete</a>
</Popconfirm>
</span>
),
},
];
return (
<div className={styles.normal}>
<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);
dva 提供了 connect 方法
这个 connect 就是 react-redux 的 connect 。 connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State。
Model 对象的属性
Action 是一个普通 javascript 对象
它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects。
dispatch({
type: 'user/add', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
Reducer函数接受两个参数
之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。
state: {
list: [],
total: null,
page: null,
},
reducers: {
save(state, { payload: { data: list, total, page } }) {
return { ...state, list, total, page };
},
}
Effect
Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。
dva 提供多个 effect 函数内部的处理函数,比较常用的是 call 和 put。
effects: {
*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 } });
},
}
Router
这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
在组件设计方法中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在 /routes/ 目录下,而 /components/ 目录下则是纯组件。
组件设计
React 应用是由一个个独立的 Component 组成的,我们在拆分 Component 的过程中要尽量让每个 Component 专注做自己的事。
一般来说,我们的组件有两种设计:Container Component、Presentational Component
Container Component 一般指的是具有监听数据行为的组件,一般来说它们的职责是绑定相关联的 model 数据,以数据容器的角色包含其它子组件。
Presentational Component
它不会关联订阅 model 上的数据,而所需数据的传递则是通过 props 传递到组件内部。
对组件分类,主要有两个好处:让项目的数据处理更加集中;让组件高内聚低耦合,更加聚焦;
试想如果每个组件都去订阅数据 model,那么一方面组件本身跟 model 耦合太多,另一方面代码过于零散,到处都在操作数据,会带来后期维护的烦恼。
除了写法上订阅数据的区别以外,在设计思路上两个组件也有很大不同。 Presentational Component是独立的纯粹的,可以参考 ant.design UI组件的React实现 ,每个组件跟业务数据并没有耦合关系,只是完成自己独立的任务,需要的数据通过 props 传递进来,需要操作的行为通过接口暴露出去。 而 Container Component 更像是状态管理器,它表现为一个容器,订阅子组件需要的数据,组织子组件的交互逻辑和展示。
roadhog 是和 webpack 相似的库,起的是 webpack 自动打包和热更替的作用
roadhog 是一个 cli 工具,提供 dev、 build 和 test 三个命令,分别用于本地调试、构建和测试,并且提供了特别易用的 mock 功能。在体验上,保持了和 create-react-app一致(如 redbox 显示出错信息、HMR、ESLint 出错提示等等),并且提供了 JSON 格式的配置方式。如果 create-react-app 的默认配置不能满足需求,而他又不提供定制的功能,于是基于他实现了一个可配置版。所以如果既要 create-react-app 的优雅体验,又想定制配置,那么可以试试 roadhog。
## Install globally or locally
$ npm i roadhog -g
## Local development
$ roadhog dev
## Build
$ roadhog build
## Test
$ roadhog test
roadhog dev 支持 mock, 在.roadhogrc.mock.js 里配置
export default {
// Support type as Object and Array
'GET /api/users': { users: [1,2] },
// Method like GET or POST can be omitted(省略)
'/api/users/1': { id: 1 },
// Support for custom functions, the API is the same as express@4
'POST /api/users/create': (req, res) => { res.end('OK'); },
};
roadhog 的 webpack 部分是基于 af-webpack 的实现。在项目根目录创建 .webpackrc 进行配置,格式是 JSON。
redux 是状态管理的库,router 是(唯一)控制页面跳转的库。两者都很美好,但是不美好的是两者无法协同工作。换句话说,当路由变化以后,store 无法感知到。于是便有了 react-router-redux。
react-router-redux 是 redux 的一个中间件,主要作用是:加强了React Router库中history这个实例,以允许将history中接受到的变化反应到state中去。
从代码上讲,主要是监听了 history 的变化。dva 在此基础上又进行了一层代理,把代理后的对象当作初始值传递给了 dva-core,方便其在 model 的 subscriptions 中监听 router 变化。
异步请求库,输出 isomorphic-fetch 的接口。
dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。
// 1、注册 dva-loading 插件
import dva from 'dva';
import createLoading from 'dva-loading';
const app = dva();
app.use(createLoading());
// 2、从store中获取loading状态
import React from 'react';
import { connect } from 'dva';
import { Table } from 'antd';
function Users({ dispatch, list: dataSource, loading }) {
const columns = [
{
title: 'Username',
dataIndex: 'username',
key: 'username',
render: text => <a href="">{text}</a>,
},
{
title: 'Street',
dataIndex: 'address.street',
key: 'street',
},
{
title: 'Website',
dataIndex: 'website',
key: 'website',
}
];
return (
<div className={styles.normal}>
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
rowKey={record => record.id}
pagination={false}
/>
</div>
);
}
function mapStateToProps(state) {
const { list } = state.users;
return {
loading: state.loading.models.users,
list,
};
}
export default connect(mapStateToProps)(Users);
React 中常见模式是为一个组件返回多个元素。为了包裹多个元素我们写过很多的 div 和 span,进行不必要的嵌套,无形中增加了浏览器的渲染压力。
react15版以前,render 函数的返回必须有一个根节点,否则报错,为满足这一原则我会使用一个没有任何样式的 div 包裹一下。
import React from 'react';
export default function () {
return (
<div>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
</div>
);
}
react 16版开始, render支持返回数组,这一特性已经可以减少不必要节点嵌套
import React from 'react';
export default function () {
return [
<div>一步 01</div>,
<div>一步 02</div>,
<div>一步 03</div>
];
}
而且React 16为我们提供了Fragment。Fragment与Vue.js的 <template> 功能类似,可做不可见的包裹元素。
import React from 'react';
export default function () {
return (
<React.Fragment>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
</React.Fragment>
);
}
参考:https://segmentfault.com/a/1190000013220508
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象—遍历器对象。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
hw.next() // { value: 'hello', done: false }
hw.next() // { value: 'world', done: false }
hw.next() // { value: 'ending', done: true }
hw.next() // { value: undefined, done: true }
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。另外需要注意,yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
ES6 诞生以前,异步编程的方法,大概有四种:回调函数、事件监听、发布/订阅、Promise 对象。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
您的赞助将会支持作者创作及本站运维


此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。