GithubHelp home page GithubHelp logo

blog's Introduction

blog

personal blog repository.

blog's People

Contributors

sorrycc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

编译时报栈溢出是为什么?

按着12步的步骤来,到第3步npm start时cmd窗口抛出一个错误
image
为什么?然后antd的css样式无法加载,需要手动import

MobX 入门教程

大家用 redux 这么久,有没有被那么多概念和约定烦到?

比如:

  • 一个 click 的事件需要经过 action, dispatch, middleware, reducer 才能走完流程
  • reducer 里不能直接修改 state,而是每次返回一个新的
  • 要区分 container 和 component
  • container 里要在 connect 里 select 数据,一不小心就选多了或选少了,出于性能考虑还要借助 reselect 这个库
  • 发异步请求需要借助 redux-thunk 或 redux-saga
  • ...

如果有,那么可以尝试下 mobx

什么是 mobx

mobx 只做一件事,解决 state 到 view 的数据更新问题

mobx 是一个库 (library),不是一个框架 (framework)。他不限制如何组织代码,在哪里保存 state 、如何处理事件,怎么发异步请求等等。我们可以回归到 Vanilla JavaScript,可以和任意类库组合使用。

核心理念

mobx 引入了几个概念,Observable state, DerivationsReactions

可以拿 Excel 表格做个比喻,Observable state 是单元格,Derivations 是计算公式,单元格的修改会触发公司的重新计算,并返回值,而最终公式的计算结果需要显示在屏幕上(比如通过图表的方式),这是 Reactions

下面通过代码理解下这些概念,以 mobx 和 react 的组合使用为例:(Open Demo on jsfiddle)

import { observable, computed } from 'mobx';
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

////////////////////
// Store

class TodoStore {
  @observable todos = [];
  @computed get completedTodosCount() {
    return this.todos.filter(todo => todo.completed === true).length;
  }
  addTodo(task) {
    this.todos.push({ task, completed: false });
  }
}

////////////////////
// Components

@observer
class TodoList extends Component {
  render() {
    const { todoStore } = this.props;
    return (
      <div>
        { todoStore.todos.map((todo, index) => <Todo todo={todo} key={index} />) }
        Progress: { todoStore.completedTodosCount }
      </div>
    );
  }
}

@observer
class Todo extends Component {
  render() {
    const { todo } = this.props;
    return (
      <li onDoubleClick={this.onRename}>
        <input
          type="checkbox"
          checked={ todo.completed }
          onChange={ this.onToggleCompleted }
        />
        { todo.task }
      </li>
    );
  }
  onToggleCompleted = () => {
    const todo = this.props.todo;
    todo.completed = !todo.completed;
  }
  onRename = () => {
    const todo = this.props.todo;
    todo.task = prompt('Task name', todo.task) || ""; 
  }
}

////////////////////
// Init

const todoStore = new TodoStore();
todoStore.addTodo('foo');
todoStore.addTodo('bar');

ReactDOM.render(
  <TodoList todoStore={todoStore} />,
  document.getElementById('mount')
);

这里通过 @observable 定义 Observable state,通过 @computed 定义 Derivations,通过 @observer 封装了 React Component 的 render 方法,这是 Reactions

为什么用 mobx

mobx 官网 罗列了不少区分与 flux 框架的优点,这里摘录一些比较打动我的。

简单

没有 connect,没有 cursor,没有 Immutable Data ... 总之感觉会少很多代码。同时概念也更少。

可以用 class 来组织和修改数据

对于组织复杂的领域模型比较适用。

可以用 JavaScript 引用来组织和修改数据

比如,可以直接

todo.completed = true;

而不需要

return todos.map((todo) => {
  if (todo.id === action.payload.id) {
    return {...todo, {completed: true}}
  else {
    return todo;
  }
});

同时也不需要引入额外的 immutable.js。

性能相比 redux 有优势

mobx 会建立虚拟推导图 (virtual derivation graph),保证最少的推导依赖。dan_abramov 亲自操刀为 todoMVC 做了极致的优化才和 mobx 打成平手。链接

不足

参考之前 redux + redux-saga 的方案,这里的一些点可能会成为你我不用他的原因。

浏览器兼容性,不支持 IE8

由于用了 reactive arrays, objects with reactive properties (getters) 这些 ES5 特性,而且这些特性不能通过 es5-shim 解决。兼容列表可参考:http://kangax.github.io/compat-table/es5/

缺少最佳实践

这部分不在 mobx 的范围之内,需要自己探索一套最佳实践。比如如何触发 action,如何组织 store,如何组织业务逻辑,如何发异步请求,如何在 React Component 之间传递数据等等。

热替换 (Hot Module Replacement)

用过 HMR,就不愿再回到手动刷页面的时代。mobx 支持 [react-transform] 的热替换方式,但是否支持 webpack 原生热替换情况下对 store 进行替换,还有待探索。

总结

mobx 简单高效,在用吐了 redux 之后,对 mobx 简直爱不释手。除了不支持 IE8 这个硬伤,其他缺点都还是可以接受的。我会在后面的小项目中应用它,并尝试探索一套最佳实践。

扩展阅读

求问

我有个这样的场景:
list = [
{
infoList:[
{
name:'xx'
}
]
}
];

循环list的时候,infoList里面的item动态增加了checked属性变成:
list = [
{
infoList:[
{
name:'xx',
checked: true/false
}
]
}
];
这个属性我是通过:
extendObservable(commodityItem,{
checked: false
});
这种方式让其变成obserable的。现在我想在ui层面上事件修改,如:
<select checked={item.checked} onChanage={(checked)=>{ item.checked = checked; }} />
不起作用额,想请教下应该怎么做?刚开始用mobx。感谢。

MobX 和 TFRP

MobX 是一个 TFRP 编程范式的实现实现。

那什么是 TFRP?

FRP

要知道 TFRP,就得先了解 FRP 。先看 FRP 的定义:

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.
-- Heinrich Apfelmus

所以,FRP 的本质是,在声明一个值的时候,同时指定他的动态行为。这个值可能是事件,也可能是数据。

分支

然后 FRP 有两个重要的分支:

  1. 基于 Event Stream 的 FRP
  2. Transparent FRP (TFRP)

基于 Event Stream 的 FRP 擅长于管理 Stream,可进行 Joining, splitting, merging, mapping, sampling 等等。在需要处理多个 Event Stream 的时候非常有用,但对于简单场景来说,就过于复杂了。比如 RxJS 和 BaconJS 就属于此类。

Transparent FRP 是在背后去实现 Reactive Programming 。和 Event Stream 的 FRP 一样,TFRP 会在需要的时候更新 View,不同的是 TFRP 不需要你定义如何 (How) 以及何时 (When) 更新。这一类型的框架有 Meter(Tracker),knockoutJS 和 EmberJS 。

那么已经有这么多实现了,为什么还要有 Mobx ?

Mobx

Mobx 和其他实现有些不同。

  1. 同步执行 (这样监听的值始终是最新的,并且调试会方便,因为没有额外的 Promise/Async 库引入的堆栈信息)
  2. 没有引入额外的数据结构,基于普通的 Object, Class, Array 实现 (更少学习成本,更新数据时更自然)
  3. 独立方案 (不捆绑框架,相比 Meter, EmberJS 和 VueJS 而言)

深入

更多关于 FRP 的资料:

Tracker 文档:

dva 入门:手把手教你写应用

本文已迁移至 https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/getting-started.md

我们将基于 dva 完成一个简单 app,并熟悉他的所有概念。

最终效果:

这是一个测试鼠标点击速度的 App,记录 1 秒内用户能最多点几次。顶部的 Highest Record 纪录最高速度;中间的是当前速度,给予即时反馈,让用户更有参与感;下方是供点击的按钮。

看到这个需求,我们可能会想:

  1. 该如何创建应用?
  2. 创建完后,该如何一步步组织代码?
  3. 开发完后,该如何构建、部署和发布?

在代码组织部分,可能会想:

  1. 如何写 Component ?
  2. 如何写样式?
  3. 如何写 Model ?
  4. 如何 connect Model 和 Component ?
  5. 用户操作后,如何更新数据到 State ?
  6. 如何处理异步逻辑? (点击之后 +1,然后延迟一秒 -1)
  7. 如何处理路由?

以及:

  1. 不想每次刷新 Highest Record 清 0,想通过 localStorage 记录,这样刷新之后还能保留 Highest Record。该如何处理?
  2. 希望同时支持键盘的点击测速,又该如何处理?

我们可以带着这些问题来看这篇文章,但不必担心有多复杂,因为全部 JavaScript 代码只有 70 多行。

安装 dva-cli

你应该会更希望关注逻辑本身,而不是手动敲入一行行代码来构建初始的项目结构,以及配置开发环境。

那么,首先需要安装的是 dva-cli 。dva-cli 是 dva 的命令行工具,包含 init、new、generate 等功能,目前最重要的功能是可以快速生成项目以及你所需要的代码片段。

$ npm install -g dva-cli

安装完成后,可以通过 dva -v 查看版本,以及 dva -h 查看帮助信息。

创建新应用

安装完 dva-cli 后,我们用他来创建一个新应用,取名 myApp

$ dva new myApp --demo

注意:--demo 用于创建简单的 demo 级项目,正常项目初始化不加要这个参数。

然后进入项目目录,并启动。

$ cd myApp
$ npm start

几秒之后,会看到这样的输出:

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

(如需关闭 server,请按 Ctrl-C.)

在浏览器里打开 http://localhost:8989/ ,正常情况下,你会看到一个 "Hello Dva" 页面。

定义 model

接到需求之后推荐的做法不是立刻编码,而是先以上帝模式做整体设计。

  1. 先设计 model
  2. 再设计 component
  3. 最后连接 model 和 component

这个需求里,我们定义 model 如下:

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

namespace 是 model state 在全局 state 所用的 key,state 是默认数据。然后 state 里的 record 表示 highest recordcurrent 表示当前速度。

完成 component

完成 Model 之后,我们来编写 Component 。推荐尽量通过 stateless functions 的方式组织 Component,在 dva 的架构里我们基本上不需要用到 state 。

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>
  );
};

注意:

  1. 这里先 import styles from './index.less';,再通过 styles.xxx 的方式声明 css classname 是基于 css-modules 的方式,后面的样式部分会用上
  2. 通过 props 传入两个值,countdispatchcount 对应 model 上的 state,在后面 connect 的时候绑定,dispatch 用于分发 action
  3. dispatch({type: 'count/add'}) 表示分发了一个 {type: 'count/add'} 的 action,至于什么是 action,详见:[email protected]

更新 state

更新 state 是通过 reducers 处理的,详见 [email protected]

reducer 是唯一可以更新 state 的地方,这个唯一性让我们的 App 更具可预测性,所有的数据修改都有据可查。reducer 是 pure function,他接收参数 state 和 action,返回新的 state,通过语句表达即 (state, action) => newState

这个需求里,我们需要定义两个 reducer,count/addcount/minus,分别用于计数的增和减。值得注意的是 count/add 时 record 的逻辑,他只在有更高的记录时才会被记录。

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

注意:

  1. { ...state } 里的 ... 是对象扩展运算符,类似 Object.extend,详见:对象的扩展运算符
  2. add(state) {} 等同于 add: function(state) {}

绑定数据

还记得之前的 Component 里用到的 count 和 dispatch 吗? 会不会有疑问他们来自哪里?

在定义了 Model 和 Component 之后,我们需要把他们连接起来。这样 Component 里就能使用 Model 里定义的数据,而 Model 中也能接收到 Component 里 dispatch 的 action 。

这个需求里只要用到 count

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

这里的 connect 来自 react-redux

定义路由

接收到 url 之后决定渲染哪些 Component,这是由路由决定的。

这个需求只有一个页面,路由的部分不需要修改。

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

注意:

  1. history 默认是 hashHistory 并且带有 _k 参数,可以换成 browserHistory,也可以通过配置去掉 _k 参数。

现在刷新浏览器,如果一切正常,应该能看到下面的效果:

添加样式

默认是通过 css modules 的方式来定义样式,这和普通的样式写法并没有太大区别,由于之前已经在 Component 里 hook 了 className,这里只需要在 index.less 里填入以下内容:

.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;
  }
}

效果如下:

异步处理

在此之前,我们所有的操作处理都是同步的,用户点击 + 按钮,数值加 1。

现在我们要开始处理异步任务,dva 通过对 model 增加 effects 属性来处理 side effect(异步任务),这是基于 redux-saga 实现的,语法为 generator。(但是,这里不需要我们理解 generator,知道用法就可以了)

在这个需求里,当用户点 + 按钮,数值加 1 之后,会额外触发一个 side effect,即延迟 1 秒之后数值 1 。

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

注意:

  1. *add() {} 等同于 add: function*(){}
  2. call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,详见 redux-saga 文档
  3. 默认的 effect 触发规则是每次都触发(takeEvery),还可以选择 takeLatest,或者完全自定义 take 规则

刷新浏览器,正常的话,就应该已经实现了最开始需求图里的所有要求。

订阅键盘事件

在实现了鼠标测速之后,怎么实现键盘测速呢?

在 dva 里有个叫 subscriontions 的概念,他来自于 elm

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

dva 中的 subscriptions 是和 model 绑定的。

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

这里我们不需要手动安装 keymaster 依赖,在我们敲入 import key from 'keymaster'; 并保存的时候,dva-cli 会为我们安装 keymaster 依赖并保存到 package.json 中。输出如下:

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.

所有代码

index.js

import dva, { connect } from 'dva';
import { Router, Route } from 'dva/router';
import React from 'react';
import styles from './index.less';
import key from 'keymaster';

const app = dva();

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'count/minus' });
    },
  },
  subscriptions: {
    keyboardWatcher(dispatch) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'count/add'}) });
    },
  },
});

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

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

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

app.start('#root');


// ---------
// Helpers

function delay(timeout){
  return new Promise(resolve => {
    setTimeout(resolve, timeout);
  });
}

构建应用

我们已在开发环境下进行了验证,现在需要部署给用户使用。敲入以下命令:

$ npm run build

输出:

> @ 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

该命令成功执行后,编译产物就在 dist 目录下。

下一步

通过完成这个简单的例子,大家前面的问题是否都已经有了答案? 以及是否熟悉了 dva 包含的概念:model, router, reducers, effects, subscriptions ?

还有其他问题?可以关注 dva repo 了解更多细节。

(完)

dva 2.0 发布

距离 dva@1 发布已经快整整一年,经过一段时间断断续续的开发,dva@2 终于能和大家见面了。

2.0 最主要的变化是提取了 dva-core,是仅封装了 redux 和 redux-saga 的纯数据流方案。这使得 dva 可以应用在除 react 之外的其他领域,比如 RN、小程序、游戏、vue 等领域;同时也可满足同一领域的多种实现,比如为 react 应用不同的路由方案的 dva-react-router-3dva-no-router。(#530)

本次发布包含 dva-core 和 3 个 react 实现:

此外,还有一些社区基于 dva-core 的实现:

改进

dispatch(effectAction) => Proimse

为了方便在视图层 dispatch action 并处理回调,比如 #175,我们在 dispatch 里针对 effect 类型的 action 做了返回 Promise 的特殊处理。

例如:

dispatch({ type: 'count/addAsync' })
  .then(() => {
    console.log('done');
  });

新增 dva/dynamic 接口,配合 react-router@4 处理组件的按需加载

react-router@4 的路由是组件式的,手动处理组件的按需加载并结合 model 和 app 有点麻烦,所有封装了 dva/dynamic util 方法。

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

// render
<Route exact path="/users" component={Users} />

take 自动补全 namespace 前缀

注:之前手动加 namespace 的会收到一个 warning。

{
  namespace: 'count',
  effects: {
    *a(action, { take }) {
      // Before
      yield take('count/b');

      // After
      yield take('b');
    }
  }
}

effect 前后会额外触发 /@@start/@@end 的 action,可利用此约定实现 put 的同步执行

例如:

yield put({ type: 'addDelay', payload: { amount: 2 } });
yield take('addDelay/@@end');
const count = yield select(state => state.count);
yield put({ type: 'addDelay', payload: { amount: count, delay: 0 } });

参考用例 dva/effects-test.js at d49e3567eaadf06d12c701e670b2ba3bbe043553 · dvajs/dva · GitHub

Break Changes

react-router@4

路由基于 react-router@4 实现,写法上会有不同。

同名 reducer 和 effect 不会 fallthrough(即两者都执行),而是仅执行 effect

// model.js
export default {
  namespace: 'count',
  reducers: {
    a() {},
  },
  effects: {
    *a() {},
  }
}

// 只会执行 effects.a 不会执行 reducers.a
dispatch({ type: 'count/a' });

删除 dva/mobile

之前的 dva/mobile 其实是无路由版的 dva,所以可以用 dva-no-router 代替。

history 的 location 属性上不再包含 query

这是 history 库的变动,有和 query 相关的请用 query-string 处理一遍。

FAQ

[email protected] 用户如何升级?

原则上推荐升级到基于 react-router@4 的 dva@2,react-router@4 路由的去中心化带来的好处绝对值得一学,比如布局嵌套、Inclusive 路由、路由抽象等等。可以参考 user-dashboard 升级到 dva@2 的 commit

但肯定会有出于成本考虑,既想用 dva@2,又想继续用老版本的路由方案的,我们为大家准备了 dva-react-router-3。大家可以参考 dva-example-react-router-3 进行升级。而为了减少代码修改量,比如把所有的 import xx from 'dva' 改成 import xx from 'dva-react-router-3',大家可以通过 babel-plugin-module-resolver 进行构建时替换:

["module-resolver", {
  "alias": {
    "dva": "dva-react-router-3"
  }
}]

(完)

删除操作有误

在columns中需也需要用·{}·来包含第二个变量
render: (text, { id }) => ( <span className = { styles.operation } > <a href = "" > Edit </a> <Popconfirm title = "Confirm to delete?" onConfirm = { deleteHandler.bind(null, {id})} > <a href = "" > Delete </a> </Popconfirm> </span> ),

执行 "dva g route users"报错

OS: win64
nodeversion: v7.7.2
npm version: 5.0.3
dva-cli version: 0.7.8

错误为:

dva g route users
D:\node\node_modules\dva-cli\bin\dva-generate
create routeComponent src/routes/Users.js, src/routes/Users.cs
s
TypeError: root.findRouters(...).getRouterInfo is not a function
at transform (D:\node\node_modules\dva-cli\node_modules\dva-ast\l
ib\transform.js:42:32)
at exports.default (D:\node\node_modules\dva-cli\node_modules\dva
-ast\lib\api\index.js:46:36)
at D:\node\node_modules\dva-cli\lib\generate.js:92:27
at generate (D:\node\node_modules\dva-cli\lib\generate.js:108:11)
at Object. (D:\node\node_modules\dva-cli\bin\dva-gener
ate:11:27)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)

dva 1.0 - 基于 react 和 redux 的轻量级框架

Hi all,

如果。

  • 你喜欢 redux,但面对丰富的社区方案不知如何选;
  • 你喜欢 elm 的架构;
  • 你想要代码足够清晰;
  • 你不想去记很多的 API ;(only 5 methods)
  • 你不想每次修改都刷新页面;(HMR)
  • 你的项目够大,并且希望按需加载;
  • 你想借助可视化工具提升效率;(cygnus 开发中)
  • 你想要优雅地处理异步请求,以及统一出错;
  • 你想同一套架构既用于 PC,又用于 H5,还用于 ReactNative;
  • 你自动切换 loading 状态,而不用一遍遍地重复写 showLoading 和 hideLoading;
  • ...

那么,请试试 dva

dva 是什么

轻量级,基于 react 和 redux,elm 风格的前端框架。

dva 如何运转

为什么要有 dva

#6

谁在用 dva

  • 支付宝 (目前共计 17 个项目)
  • 聚划算

下一步

你可以:

传值问题

mobx可以在不同页面传值吗?具体应该怎么操作呢?

12步30分钟

走到第六步了 都根据作者的做了发动 但是没有报错 数 据也没有显示出来 ,首页还是那个小丑,为什么啊

CSS 高级布局技巧

随着 IE8 逐渐退出舞台,很多高级的 CSS 特性都已被浏览器原生支持,再不学下就要过时了。

:empty 区分空元素

兼容性:不支持 IE8

Demo

假如我们有以上列表:

<div class="item">a</div>
<div class="item">b</div>
<div class="item"></div>

我们希望可以对空元素和非空元素区别处理,那么有两种方案。

:empty 选择空元素:

.item:empty {
  display: none;
}

或者用 :not(:empty) 选择非空元素:

.item:not(:empty) {
  border: 1px solid #ccc;
  /* ... */
}

:*-Of-Type 选择元素

兼容性:不支持 IE8

举例说明。

给第一个 p 段落加粗:

p:first-of-type {
  font-weight: bold;
}

给最后一个 img 加边框:

img:last-of-type {
  border: 10px solid #ccc;
}

给无相连的 blockquote 加样式:

blockquote:only-of-type {
  border-left: 5px solid #ccc;
  padding-left: 2em;
}

让奇数列的 p 段落显示红色:

p:nth-of-type(even) {
  color: red;
}

此外,:nth-of-type 还可以有其他类型的参数:

/* 偶数个 */
:nth-of-type(even)

/* only 第三个 */
:nth-of-type(3)

/* 每第三个 */
:nth-of-type(3n)

/* 每第四加三个,即 3, 7, 11, ... */
:nth-of-type(4n+3)

calc 做流式布局

兼容性:不支持 IE8

Demo

左中右的流式布局:

nav {
  position: fixed;
  left: 0;
  top: 0;
  width: 5rem;
  height: 100%;
}

aside {
  position: fixed;
  right: 0;
  top: 0;
  width: 20rem;
  height: 100%;
}

main {
  margin-left: 5rem;
  width: calc(100% - 25rem);
}

vwvh 做全屏滚动效果

兼容性:不支持 IE8

Demo

vwvh 是相对于 viewport 而言的,所以不会随内容和布局的变化而变。

section {
  width: 100vw;
  height: 100vh;
  
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  
  background-size: cover;
  background-repeat: no-repeat;
  background-attachment: fixed;
}

section:nth-of-type(1) {
  background-image: url('https://unsplash.it/1024/683?image=1068');
}
section:nth-of-type(2) {
  background-image: url('https://unsplash.it/1024/683?image=1073');
}
section:nth-of-type(3) {
  background-image: url('https://unsplash.it/1024/683?image=1047');
}
section:nth-of-type(4) {
  background-image: url('https://unsplash.it/1024/683?image=1032');
}

body {
  margin: 0;
}
p {
  color: #fff;
  font-size: 100px;
  font-family: monospace;
}

unset 做 CSS Reset

兼容性:不支持 IE

Demo

body {
  color: red;
}
button {
  color: white;
  border: 1px solid #ccc;
}

/* 取消 section 中 button 的 color 设置 */
section button {
  color: unset;
}

column 做响应式的列布局

兼容性:不支持 IE9

Demo

nav {
  column-count: 4;
  column-width: 150px;
  column-gap: 3rem;
  column-rule: 1px dashed #ccc;
  column-fill: auto;
}

h2 {
  column-span: all;
}

(完)

执行到第四步报错

执行到第四步,报错:root.findRouters(...).getRouterInfo is not a function 。前面都能访问..api/users。求大神解答。
Uploading image.png…

从 atool-build + dora 到 roadhog

这几天收到比较多关于 roadhog疑问,为啥用 roadhog,啥时不用 roadhog,怎么从 atool-build + dora 切换到 roadhog 等等。解释如下:

roadhog 和 atool-build + dora 有啥区别?

配置

roadhog 是约束型配置,基于 JSON 格式,给出有限的配置方式;atool-build + dora 是扩展型,表现为插件和编程 webpack.config.js 的方式。

功能

标了 的后续可能会支持,看需求吧。

roadhog 的劣势:

  • 暂不内置 mock 方案,通过 proxy 和其他服务(比如 json-server )配合使用,已内置更好用的 mock 方案,sorrycc/roadhog#59
  • 暂不能扩展没有内置的 webpack 配置,比如要用 sass 现在是不行的,已支持 sorrycc/roadhog#36
  • server 无插件机制,不能扩展

roadhog 的优势:

为啥用 roadhog?

既然 roadhog 功能没 atool-build + dora 强大,那为啥要切换呢?

  • 体验好,基于 create-react-app,比如有非常友好的 错误处理
  • 配置简单,基于 JSON,比如禁用 CSS Modules 只要配"disableCSSModules": true
  • 黑盒升级,就算之后 roadhog 换成 rollup 或其他的,用户也不需要更改配置

啥情况下不换 roadhog?

以下情况不推荐换 roadhog 。

  • 有强定制需求,比如用 sass 等
  • 有强 mock 数据需求,并且之前通过 dora-plugin-proxy 写了很多 mock 的
  • 无线,并且有强定制需求的, 待调研

修改步骤

修改 package.json

删除 atool-builddora 相关依赖,加上 roadhog 依赖。

$ npm install roadhog --save-dev

修改 scripts 部分,让 startbuild 走 roadhog:

"start": "roadhog server"
"build": "roadhog build"

可参看这个 Commitdva-example-user-dashboard

新增 .roadhogrc

如果是用 dva + antd 的组合,babel 插件部分通常这么配:

"extraBabelPlugins": [
  "transform-runtime",
  ["import", { "libraryName": "antd", "style": "css" }]
],
"env": {
  "development": {
    "extraBabelPlugins": [
      "dva-hmr"
    ]
  }
}

然后把 webpack.config.js 中的配置参考 roadhog#配置 迁移到 .roadhogrc 中。

删除 webpack.config.js

(完)

从 0 开始实现 react 版本的 hackernews (基于 dva)


Live Demo

说一说基于 dva 实现 dva-hackernews 的过程。

基本思路是按照 service -> model -> component 的顺序来实现的,好处是可以用真实数据,不用额外写 mock 方法。

脚手架

通过 dva-cli 生成项目初始文件,然后 npm start 启动。

Service

hackernews 数据接口来自 firebase,所以可以直接用 firebase 这个 package 。firebase 基于 websocket 连接实现,除了初次请求慢些,后面的数据加载很快。相比 http 来说,省去不少请求。

为了方便在 effects 里调用,service 方法需要返回 promise 。watchList 除外,这个不在 effects 里调,而是在 subscriptions 里,用于实时更新列表数据。

Model

写 model 层是脑力劳动,而写 component 层是体力劳动。

数据结构

先设计数据结构,为了让 reducer 里写得比较容易,所以选择扁平化的方式。即把 item 拎出来,以 id 为 key 统一存放,然后其他地方即可引用 id 。

{
  list: {
    top: [123, 456],
    new: [123, 456],
  },
  itemsById: {
    123: { title: 'foo' },
    456: { title: 'bar' },
    789: { title: 'wow' },
  },
}

这样更新 item 就比较简单,反之如果要更新 list.top['123'] 的数据,想想都麻烦。(没用 immutable.js)

state 更新

然后是完成处理 action 的部分,reducers 和 effects,分别负责 state 更新和异步逻辑。

state 更新的部分写在 reducers 里,没什么特别的,灵活掌握 array 和 object 的各种方法就可以了,注意 array 到 object 的转换可以用 reduce 简化。

saveItems(state, { payload: itemsArr }) {
  const items = itemsArr.reduce((memo, item) => {
    memo[item.id] = item;
    return memo;
  }, {});
  return { ...state, itemsById: { ...state.itemsById, ...items }};
},

异步逻辑

异步逻辑部分,写在 effects 里。通过 generator 组织,所以基本上都是一层缩进下来就完了。

*fetchList({ payload }) {
  const { type, page } = payload;
  yield put({ type: 'app/showLoading' });

  const ids = yield call(fetchIdsByType, type);
  const itemsPerPage = yield select(state => state.item.itemsPerPage);
  const items = yield call(
    fetchItems,
    ids.slice(itemsPerPage * (page - 1), itemsPerPage * page)
  );
  yield put({ type: 'saveList', payload: { ids, type } });
  yield put({ type: 'saveItems', payload: items });

  yield put({ type: 'app/hideLoading' });
},

为了实时性,切换页面不管 item 是否有缓存,都会重新请求一遍。

评论数据是递归获取的,因为不知道有几层。还好是 websocket,如果换成 http 的实现应该会很慢。虽然是比较快,但在评论页面也能明显感觉到是一层层更新出来的。

定义完所有 action 的处理,接下来要看如何调用他们。基本上就两个地方,subscriptions 和 component 。

初始数据请求

subscription 意为订阅,用于数据源的订阅。

而初始数据加载实际上是订阅了 history 的变更,待满足 url 匹配时,触发 action 加载远程数据。这些逻辑不放 route component 还有好处是可以更好地配合 hmr,同时让 route component 保持 stateless component 的写法。

由于 react-router 的限制,这里需使用 path-to-regexp 库来解决 url 匹配的问题。

history.listen(({ pathname }, { params }) => {
  if (pathToRegexp(`/item/:itemId`).test(pathname)) {
    dispatch({
      type: 'item/fetchComments',
      payload: params.itemId,
    });
  }
});

当用户进入 item 页面时,通过 action item/fetchComments 获取评论数据。

实时更新

同上,实时更新也写在 subscriptions 里,等于是订阅了 list 的数据源。有更新时,保存新的 id,然后重新加载本页数据。

watchList(type, ids => {
  dispatch({
    type: 'saveList',
    payload: {
      type, ids
    },
  });
  dispatch({
    type: 'fetchList',
    payload: {
      type,
      page,
    },
  });
});

selector

由于我们的数据是扁平化的,不能直接交由 component 渲染,需要一层 selector 。比如我想要 top 下第 1 页的列表。

export function listSelector(state, ownProps) {
  const page = parseInt(ownProps.params.page || 1, 10);
  const { itemsPerPage, activeType, lists, itemsById } = state.item;
  const ids = lists[activeType].slice(itemsPerPage * (page - 1), itemsPerPage * page);
  const items = ids.reduce((memo, id) => {
    if (itemsById[id]) memo.push(itemsById[id]);
    return memo;
  }, []);
  const maxPage = Math.ceil(lists[activeType].length / itemsPerPage);
  return {
    items,
    page,
    maxPage,
    activeType,
  };
}

Component

写完 model 层,感到一阵轻松,剩下的基本不费脑了。

动画

动画没有用上 react-motion,而是基于 ReactCSSTransitionGroup 实现,方法和 vue 以及 angular 都类似。动效可以上 nganimate 找一个喜欢的样式过来用。

<ReactCSSTransitionGroup
  transitionName="item"
  transitionEnterTimeout={500}
  transitionLeaveTimeout={500}
>
  {
    items.map(item => <Item key={item.id} item={item} />)
  }
</ReactCSSTransitionGroup>

总结

以上是实现 hackernews 一些经验。先写什么并不重要,主要是要有分层的概念,可以先写 model,也可以先写 component 。dva 借鉴 elm 的概念整合了 reducers, effects 和 subscriptions 到 model,让分层更清晰,并让各种觉得的代码有所归属。希望大家能动手实践一把,会发现相比现有 redux 方法的优势。

More

按步骤,到第四步出错

@sorrycc 执行到第四步报错,前面都能正确访问...api/users .
E:\iSearch6\user-dashboard>dva g route users
E:\nodejs\node_global\node_modules\dva-cli\bin\dva-generate
create routeComponent src/routes/Users.js, src/routes/Users.css
TypeError: root.findRouters(...).getRouterInfo is not a function
at transform (E:\nodejs\node_global\node_modules\dva-cli\node_modules\dva-as
t\lib\transform.js:42:32)
at exports.default (E:\nodejs\node_global\node_modules\dva-cli\node_modules
dva-ast\lib\api\index.js:46:36)
at E:\nodejs\node_global\node_modules\dva-cli\lib\generate.js:92:27
at generate (E:\nodejs\node_global\node_modules\dva-cli\lib\generate.js:108:
11)
at Object. (E:\nodejs\node_global\node_modules\dva-cli\bin\dva-ge
nerate:11:27)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)

查询分页是有问题的

如果我先按关键字查询,得出列表再点击下一页,这样会将所有数据重新加载一遍,我看了你的代码也没有处理这块。

MobX 原理

先来看一个典型的 mobx + react 例子。(在 jsfiddle 里打开)

import { observable } from 'mobx';
import { observer } from 'react-mobx';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

const appState = observable({
  count: 0,
});
appState.increment = function() {
  this.count ++;
};
appState.decrement = function() {
  this.count --;
};

@observer
class Count extends Component {
  render() {
    return (<div>
      Counter: { appState.count } <br />
      <button onClick={this.handleInc}> + </button>
      <button onClick={this.handleDec}> - </button>
    </div>);
  }
  handleInc() {
    appState.increment();
  }
  handleDec() {
    appState.decrement();
  }
}

ReactDOM.render(<Count />, document.getElementById('root'));

这个例子里,先通过 mobx 定义了 appState,Count 的 render 执行时里引用 appState 的数据。然后如果用户点击 + 或 - 按钮,会触发 appState 的修改,appState 的修改会自动触发 Counter 的更新。

基本原理

而要理解 mobx 的原理,我们需要一个更底层的例子。

import { observable, autorun } from 'mobx';

const counter = observable(0);
autorun(() => {
  console.log('autorun', counter.get());
});

counter.set(1);

运行结果是:

autorun 0
autorun 1

大家可能会好奇,为什么 counter.set() 之后,autorun 会自动执行? 要达到这个目的,通过 counter 需要知道 autorun 是依赖他的。那么这个依赖关系是在什么时候以及如何生成的呢?

先看代码,这里涉及了 mobx 的 observable 和 autorun 接口。与此相关的有 Observable 和 Derivation 两个类。Observable 是数据源,Derivation 是推导。

类定义如下:

Observable
  - observing: [Derivation]
  - get()
  - set()

Derivation
  - observer: [Observable]

然后,autorun 执行的步骤是这样的:

  1. 生成一个 Derivation
  2. 执行传入函数,计算出 observing
    1. 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
  3. 在 observing 的 Observable 的 observer 里添加这个 Derivation

到这里,Observable 和 Derivation 的依赖关联就建立起来了。

那么 counter.set() 执行之后是如何触发 autorun 自动执行? 在有了上面这一层依赖关系之后,这个就很好理解了。counter.set() 执行时会从自己的 observing 属性里取依赖他的 Derivation,并触发他们的重新执行。

运行时依赖计算

再看一个例子。

import { observable, autorun } from 'mobx';

const counter = observable(0);
const foo = observable(0);
const bar = observable(0);
autorun(() => {
  if (counter.get() === 0) {
    console.log('foo', foo.get());
  } else {
    console.log('bar', bar.get());
  }
});

bar.set(10);    // 不触发 autorun
counter.set(1); // 触发 autorun
foo.set(100);   // 不触发 autorun
bar.set(100);   // 触发 autorun

执行结果:

foo 0
bar 10
bar 100

autorun 先是依赖 counter 和 foo,然后 counter 设为 1 之后,就不依赖 foo,而是依赖 counter 和 bar 了。所以之后修改 foo 并不会触发 autorun 。

那么 mobx 是如何在运行时计算依赖的呢?

实际上前面的 autorun 的执行步骤是做了简化的,真实的是这样:

  1. 生成一个 Derivation
  2. 记录 oldObserving (+)
  3. 执行传入函数,计算出 observing
    1. 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
  4. 和 oldObserving 做 diff,得到新增和删除列表 (+)
  5. 通过前面得到的 diff 结果,修改 Observable 的 observing

相比之前的,增加了 diff 的逻辑,以达到每次执行的时候动态更新依赖关系表的目的。

get/set magic

大家在看前面的例子里可能会有个疑问,为啥第一个例子里可以通过 appState.counter 来设置,而后面的例子里需要用 counter.getcounter.set 来取值和设值?

这和数据类型有关,mobx 支持的类型有 primitives, arrays, classes 和 objects 。primitives (原始类型) 只能通过 set 和 get 方法取值和设值。而 Object 则可以利用 Object.defineProperty 方法自定义 getter 和 setter 。

Object.defineProperty(adm.target, propName, {
  get: function() { return observable.get(); },
  set: ...
});

详见源码

ComputedValue

ComputedValue 同时实现了 Observable 和 Derivation 的接口,即可以监听 Observable,也可以被 Derivation 监听。

Reaction

Reaction 本质上是 Derivation,但他不能再被其他 Derivation 监听。

Autorun

autorun 是 Reaction 的简单封装

同步执行

其他的 TFRP 类库,比如 Tracker 和 Knockout ,数据更新后的执行都是异步的,需要等到下一个 event loop 。(可以想象成 setTimeout)

而 Mobx 的执行是同步的,这样做有两个好处:

  1. ComputedValue 在他依赖的值修改后可以马上被使用,这样你就永远不会使用一个过期的 ComputedValue
  2. 调试方便,堆栈里没有冗余的 Promise / async 库

Transation

由于 mobx 的更新是同步的,所以每 set 一个值,就会触发 reaction 的更新。所以为了批量更新,就引入了 transation 。

transaction(() => {
  user.firstName = "foo";
  user.lastName = "bar";
});

在一些情况下,等所有的修改执行完再执行所有的 deviration 会更合适。注意 transaction 只是推迟了 deviration 的执行,本身还是同步的。

Action

action 是 transation 是简单封装,支持通过 decorator 的方式调用。并且是 untrack 的,这样可以在 Derivation 里调用他。

Observe (mobx-react)

第一次 render 时:

  1. 初始化一个 Reaction,onValidate 时会 forceUpdate Component
  2. 在 reaction.track 里执行 baseRender,建立依赖关系

有数据修改时:

  1. 触发 onValidate 方法,执行 forceUpdate
  2. 触发 render 的执行 (由于在 reaction.track 里执行,所以会重新建立依赖关系)

shouldComponentUpdate:

  1. 和 PureRenderMixin 类似的实现,阻止不必要的更新

componentWillReact:

  1. 数据更新的时候触发
  2. 注意和 componentWillMount 和 componentWillUpdate 的区别

总结

第一眼看 mobx 觉得非常简单,概念也少。这对于简单项目可能够了,但在项目复杂之后就需要用到一些高级的功能,从而需要接触很多的概念,比如 Observable, ComputedValue, Derivation, Action, Transation, Autorun, Reaction, Modifier 等等。其实一点都不比 redux 简单。。

个人很喜欢 mobx 这个库,里面包含很多非常巧妙的实现和优化。所以试着想把原理给讲明白,但写完之后发现还是有些晦涩。

参考

Electron 实践之自动更新

本文仅包含 MacOX 经验,Windows 待实践后更新。

electron 官方的 auto update 文档并不完善,多番 google 后,找到一种使用简单、跨平台、无特殊服务器要求的方案。

效果图:

demo

生成证书

出于安全考虑,自动更新必须搭配证书和 https 服务器使用。证书的生成可以用苹果开发者证书或者 StartSSL 等生成证书,而只有苹果开发者证书可以过掉 GateKeeper

打包时通过环境变量 CSC_LINKCSC_KEY_PASSWORD 指定证书,另外在 Mac 下可不手动指定,electron-builder 会自动寻找合适的证书。

详见: Code Signing · electron-userland/electron-builder Wiki · GitHub

找一台服务器存放 update.json

经测试,update.json 不一定要 https 服务器。

格式参考以下例子:

使用 electron-simple-updater

先安装依赖,在 ./app 目录执行:

$ npm install electron-simple-updater --save

app/package.json 中配置 update.json 的地址:

"updater": {
  "url": "https://raw.githubusercontent.com/sorrycc/test-release/master/update.json"
},

main 端配置 updater :

import updater from 'electron-simple-updater';
updater.init({
  checkUpdateOnStart: false,
  autoDownload: false,
  logger: log,
});

renderer 端绑定时间并启动更新检测:

import { remote } from 'electron';
const updater = remote.require('electron-simple-updater');

updater.on('update-available', (meta) => {
  console.log('[updater] update avaiable', meta.version);
  updater.downloadUpdate();
});
updater.on('update-downloading', () => {});
updater.on('update-downloaded', () => {
  if (window.confirm('Restart and install updates?')) {
    updater.quitAndInstall();
  }
});
updater.on('error', (err) => {});

updater.checkForUpdates();

打包

基于 electron-builder

部署资源文件到 https 服务器

mac 下要有 release.json{ProductName}-{version}-mac.zip,需部署到 https 服务器。

release.json 格式如下:

{
  "url": "https://github.com/sorrycc/test-release/releases/download/1.2.0/ReleaseTracker-0.2.0-mac.zip",
  "name": "",
  "notes": "",
  "pub_date": "2017-1-20T14:18:48.988Z"
}

参考 Release 1.2.0 · sorrycc/test-release · GitHub

更新 update.json

参考前面的例子更新 update.json

参考

(完)

React + Redux 最佳实践

更新:我们基于此最佳实践做了一个封装方案:dva,可以简化使用 redux 和 redux-saga 时很多繁杂的操作。

前端变化虽快,但其实一直都围绕这几个概念在转:

  • URL - 访问什么页面
  • Data - 显示什么信息
  • View - 页面长成什么样
  • Action - 对页面做了什么操作
  • API Server - Data 数据的来源

在 redux 的生态圈内,每个环节有多种方案,比如 Data 可以是 immutable 或者 plain object,在你选了 immutable 之后,用 immutable.js 还是 seamless-immutable,以及是否用 redux-immutable 来辅助数据修改,都需要选择。

本文总结目前 react + redux 的最佳实践,解释原因,并提供可选方案。

心急的朋友可以直接看代码:https://github.com/sorrycc/github-stars

一、URL > Data

需求

routing

选择

react-router + react-router-redux: 前者是业界标准,后者可以同步 route 信息到 state,这样你可以在 view 根据 route 信息调整展现,以及通过 action 来修改 route 。

可选

二、Data

需求

为 redux 提供数据源,修改容易。

方案

plain object: 配合 combineReducer 已经可以满足需求。

同时在组织 Store 的时候,层次不要太深,尽量保持在 2 - 3 层。如果层次深,可以考虑用 updeep 来辅助修改数据。

可选

immutable.js: 通过自定义的 api 来操作数据,需要额外的学习成本。不熟悉 immutable.js 的可以先尝试用 seamless-immutable,JavaScript 原生接口,无学习门槛。

另外,不推荐用 redux-immutable 以及 redux-immutablejs,一是没啥必要,具体看他们的实现就知道了,都比较简单;更重要的是他们都改写了 combineReducer,会带来潜在的一些兼容问题。

三、Data > View

需求

数据的过滤和筛选。

方案

reselect: store 的 select 方案,用于提取数据的筛选逻辑,让 Component 保持简单。选 reselct 看重的是 可组合特性缓存机制

可选

四、View 之 CSS 方案

需求

合理的 CSS 方案,考虑团队协作。

方案

css-modules: 配合 webpack 的 css-loader 进行打包,会为所有的 class name 和 animation name 加 local scope,避免潜在冲突。

直接看代码:

Header.jsx

import style from './Header.less';
export default () => <div className={style.normal} />;

Header.less

.normal { color: red; }

编译后,文件中的 style.normal.normal 在会被重命名为类似 Header__normal___VI1de

可选

bem, rscss ,这两个都是基于约定的方案。但基于约定会带来额外的学习成本和不遍,比如 rscss 要求所有的 Component 都是两个词的连接,比如 Header 就必须换成类似 HeaderBox 这样。

radium,inline css 方案,没研究。

五、Action <> Store,业务逻辑处理

需求

统一处理业务逻辑,尤其是异步的处理。

方案

redux-saga: 用于管理 action,处理异步逻辑。可测试、可 mock、声明式的指令。

可选

redux-loop: 适用于相对简单点的场景,可以组合异步和同步的 action 。但他有个问题是改写了 combineReducer,会导致一些意想不到的兼容问题,比如我在特定场景下用不了 redux-devtool 。

redux-thunk, redux-promise 等: 相对原始的异步方案,适用于更简单的场景。在 action 需要组合、取消等操作时,会不好处理。

saga 入门

在 saga 之前,你可能会在 action creator 里处理业务逻辑,虽然能跑通,但是难以测试。比如:

// action creator with thunking
function createRequest () {
  return (dispatch, getState) => {
    dispatch({ type: 'REQUEST_STUFF' });
    someApiCall(function(response) {
      // some processing
      dispatch({ type: 'RECEIVE_STUFF' });
    });
  };
}

然后组件里可能这样:

function onHandlePress () {
  this.props.dispatch({ type: 'SHOW_WAITING_MODAL' });
  this.props.dispatch(createRequest());
}

这样通过 redux state 和 reducer 把所有的事情串联到起来。

但问题是:

Code is everywhere.

通过 saga,你只需要触发一个 action 。

function onHandlePress () {
  // createRequest 触发 action `BEGIN_REQUEST`
  this.props.dispatch(createRequest());
}

然后所有后续的操作都通过 saga 来管理。

function *hello() {
  // 等待 action `BEGIN_REQUEST`
  yield take('BEGIN_REQUEST');
  // dispatch action `SHOW_WAITING_MODAL`
  yield put({ type: 'SHOW_WAITING_MODAL' });
  // 发布异步请求
  const response = yield call(myApiFunctionThatWrapsFetch);
  // dispatch action `PRELOAD_IMAGES`, 附上 response 信息
  yield put({ type: 'PRELOAD_IMAGES', response.images });
  // dispatch action `HIDE_WAITING_MODAL`
  yield put({ type: 'HIDE_WAITING_MODAL' });
}

可以看出,调整之后的代码有几个优点:

  • 所有业务代码都存于 saga 中,不再散落在各处
  • 全同步执行,就算逻辑再复杂,看起来也不会乱

六、Data <> API Server

需求

异步请求。

方案

isomorphic-fetch: 便于在同构应用中使用,另外同时要写 node 和 web 的同学可以用一个库,学一套 api 。

然后通过 async + await 组织代码。

示例代码:

import fetch from 'isomorphic-fetch';
export async function fetchUser(uid) {
  return await fetch(`/users/${uid}`).then(res => res.json());
};

可选

reqwest

最终

(完)

使用django做后端该如何与dva做数据传输?

可能问题并不贴合文章,也显得很是伸手党。但是这个问题我也思考和google了很久,一直没有找到答案。
所以在这里冒昧的问下。

是用django接受url访问,返回模版html,内嵌antd组件吗?

还是说完全由dva接管前段,所有数据都通过api接口从后端django获取?

对于这个问题,我一直没有理清思路,望作者指点一二,或者给我一些链接,参考书籍。
谢谢~

Electron 应用实战 (架构篇)

以下内容已整合到脚手架:https://github.com/sorrycc/dva-boilerplate-electron

近期,我们在内部做了一个类似 IDE 性质的应用,基于 electron。过程中趟过不少坑,也有了些心得,记录如下。

包含:

  • 数据通讯
  • 架构方案
  • Two-Package 目录结构
  • 源码打包
  • 应用打包

数据通讯

数据通讯方案决定整体的架构方案。

翻翻 Electron 文档,应该不难发现,Electron 有两个进程,分别为 main 和 renderer,而两者之间是通过 ipc 进行通讯。main 端有 ipcMain,renderer 端有 ipcRenderer,分别用于通讯。

一个简单的读取文件的例子:

main 端

ipcMain.on('readFile', (event, { filePath }) => {
  content content = fs.readFileSync(filePath, 'utf-8');
  event.sender.send('readFileSuccess', { content });
});

renderer 端

ipcRenderer.on('readFileSuccess', (event, { content }) => {
  console.log(`content: ${content}`);
});
ipcRender.send('readFile', {
  filePath: '/path/to/file',
});

我们刚开始也是这么做,但过了几星期发现太绕,于是重构成通过 remote 方式。 remote 是一种简化的通讯方案,内部也是 ipc,所以运行起来和前面的方案并无差别,但使用上简化很多。比如,上面的例子可简化如下:

main 端

renderer 端

const content = remote.require('fs').readFileSync('/path/to/file');

参考

架构选择

架构方案有多种,选择适合自己的。

选择

在架构方案的选择上纠结过很久,不过这很大程度是和前面的通讯方案有关的。

方案一

传统 ipc 方案,main 端用 ipcMain, renderer 端用 ipcRenderer。

方案二

main 端和 renderer 端分别部署一个 dva(不了解 dva 的可以理解为 redux),封装 ipc 基于 action 通讯。main 端的 action 如果包含 toRenderer 会自动走到 renderer 端的,反之 renderer 端的 action 如果包含 toMain 则自动走到 main 端。

最终方案

上述两个方案的缺点是:

  1. main 和 renderer 均包含业务逻辑
  2. 通讯逻辑书写复杂

我们的最终方案是:

  1. main 端无逻辑,全部抽象为 services,提供函数级的方案,就和 restful 的服务端一样,写完后等着被调
  2. 由于打包的原因,我们把 main 和 renderer 分别打包成一个文件。所以 main 的 services 要暴露到全局变量,比如 global.services,这样在 renderer 里才能通过 remote.getGlobal('services') 调用到
  3. renderer 端包含大量业务逻辑,需和 main 通讯时通过 remote 来调
  4. renderer 端我们选择 react + dva 来组织代码,把所有逻辑存于 model,保证数据和视图的彻底分离,以及逻辑的清晰
  5. 目前还没有遇到 main 主动推消息到 renderer 的需求

参考

Two-Package 目录结构

定完整体架构之后,就要确定目录结构了,以及如何做构建和打包等等。我们在这也是绕了好大一圈,因为 electron 官网没有推荐这个,后面慢慢翻文档才发现这种组织方式的好处。

先说结论,我们采用的是 Two-Package 的目录结构,并且基于 webpack 打包 main 和 renderer 。

啥是 Two-Package Structure?

Two-Package Structure 是 pack 工具 electron-builder 给的约定,也是目前业界用的较多的方案。

+ dist            // pack 完后的输出,.dmg, .exe, .zip, .app 等文件
+ build           // background.png, icon.icns, icon.ico
+ app             // 用于 pack 给用户的目录
  + dist          // src 目录打包完放这里
  + assets        // 字体、图片等资源文件
  + pages         // 存放页面
  - package.json  // 生产依赖,存 dependencies
+ src             // 源码
  + main          // main
  + renderer      // renderer
  + shared        // main 和 renderer 公用文件
- package.json    // 开发依赖,存 devDependencies

为啥用 Two-Package Structure?

最大的好处是可以很好地分离开发依赖和生成环境依赖。开发依赖存 package.json,生产依赖存 app/package.json,这样在 pack 后交付给用户时就不会包含 webpack, mocha 等等的开发依赖了。

那么怎么区别依赖类型呢? 比如:

  • main 端依赖了 fs-extra 是不是生产环境依赖?
  • renderer 端依赖了 react 是不是生产环境依赖?

这没有标准答案,和源码打包策略有关,即 src 目录的源码是如何到 app/dist 下的。

资源

源码打包

首先打包我们是用的 webpack + babel,分别把 src/mainsrc/renderer 下的文件打包为 app/dist/main.jsapp/dist/renderer.js。打包 renderer 可以理解,打包 main 可能有人会有疑问。我们打包 main 是为了编码风格的一致。

externals

我们需要 externals 掉一些不能或不应该被打包到一起的依赖。

  1. renderer 端我们只 externals 了 electron
  2. main 端我们 externals 了所有的依赖

这样,renderer 端所有的依赖都是开发依赖,main 端的所有依赖都是生产依赖。

所以,在这种打包机制下,前面的问题就有了答案:

  • main 端依赖了 fs-extra 是不是生产环境依赖? --
  • renderer 端依赖了 react 是不是生产环境依赖? -- 不是

externals 配置

main

externals(context, request, callback) {
  callback(null, request.charAt(0) === '.' ? false : `require("${request}")`);
},

renderer

externals(context, request, callback) {
  let isExternal = false;
  const load = [
    'electron',
  ];
  if (load.includes(request)) {
    isExternal = `require("${request}")`;
  }
  callback(null, isExternal);
},

资源

应用打包

翻下 electron 开源应用的源码,我们会发现有些是用 electron-packager,有些是用 electron-builder 。这两个是什么关系?我们应该用哪个呢?

答案是用 electron-builder。 electron-builder 是基于 electron-packager 实现的,并在此基础上做了 Two-Package.json Structure 的约定,以及自动更新等等功能。

Rebuild native-module

由于我们用了 pty.js,包含 C++ 的原生实现。所以在 papck 前需先用 electron-rebuild 做 rebuild。

npm scripts

{
  "build": "NODE_ENV=production webpack",
  "rebuild": "electron-rebuild -d=https://gh-contractor-zcbenz.cnpmjs.org/atom-shell/dist/ -m ./app/node_modules",
  "pack": "npm run build && npm run rebuild && build"
}

Tips

  • rebuild 如果很慢,可能是要翻墙,可尝试 cnpmjs.org 提供的镜像,electron-rebuild -d=https://gh-contractor-zcbenz.cnpmjs.org/atom-shell/dist/

资源

(完)

路由翻墙之极路由 1S

我的主要用途是把他作为子路由,给 PS4 用。

买路由器

低配路由器即可,我选了 极路由 S1 ,¥89,支持 UDP 转发。

刷 padavan 固件

参考 极路由1S(HC5661A)刷Padavan固件教程 | 约翰提托博客 完成刷固件操作,然后可以通过 http://192.168.123.1/ 进行管理操作了。

Mac 下通过 ssh 和 scp 进行连接和传文件,比如:

$ ssh [email protected] -p 1022

SS 服务

推荐上 https://www.kislens.com/aff.php?aff=59 买年付 60 块钱的,多线,试过 youtube 不卡。

参考

(完)

文件多了 怎么打包没反应?

你好 我这边碰到的问题是:项目里面的文件很多,router.js里面的路由大概有30多个 然后运行roadhog build 就不动了 有啥解决办法吗》?

发起分页action

import React from 'react';
import { Table, Icon } from 'antd';
import { routerRedux } from 'dva/router';

const CompanyList = ({total, current, loading, dataSource, }) => {
const columns = [];
const pagination = {
total: total,
current: current,
pageSize: 10,
onChange: pageChangeHandler,
};
function pageChangeHandler(page,size) {
dispatch({
type: 'company/query',
payload: { page: page },
});

}

return (<Table columns={columns} loading={loading} dataSource={dataSource} pagination={pagination} scroll={{ x: 1500, y: 700 }} />);
}

export default CompanyList

我在ui组件里面发起一个action提示我dispath不是一个函数。有人遇到过吗

介绍 roadhog —— 让 create-react-app 可配的命令行工具

库地址:https://github.com/sorrycc/roadhog

roadhog 是啥?

简单来说,roadhog 是可配置的 react-create-app

roadhog 是一个 cli 工具,提供 serverbuild 两个命令,分别用于本地调试和构建。命令行体验和 create-react-app 一致,配置略有不同,比如默认开启 css modules然后还提供了 JSON 格式的配置方式

命名来源?

http://ow.blizzard.cn/heroes/roadhog

为啥要有 roadhog ?

做 roadhog 有多方原因:

首先,create-react-app 体验实在太好了,细节做地很到位,比如启动成功后会自动打开浏览器窗口这个操作,会检查当前是否已经有打开当前 URL 的 Tab,有的话就刷新那个 Tab 。但可惜他并不支持配置,比如我们要用 less 和 css-modules,就不能使用了。相信会有不少人有同样的想法。

另外,我们目前是基于 atool-builddora 的工具套件。dora 有插件机制,atool-build 的配置和 webpack 一样,基于编程。这两种扩展方式都太灵活,灵活是优点,但导致我们做功能升级时需要考虑太多的事情,并且无法保证兼容。

那么 roadhog 的配置方式和之前的有何不同呢?

配置方式的选择

我们做 cli 工具有一段时间了,从 spm2, spm3, atool-build + dora 到现在的 roadhog。(目前 roadhog 并非 atool 的升级版,两者场景不同, atool 扩展性更好) 配置方式从 JSON 到编程,最终又回归到 JSON 。

roadhog 为啥用 JSON 格式的配置?

做 atool 的时候我们是用编程的配置方式,优点是灵活,可随意改变工具内置的 webpack 配置。

但缺点也很明显:

1. 配置麻烦

比如要删除内置的 CommonChunkPlugin,不加注释基本没人能看懂了。

// Don't extract common.js and common.css
webpackConfig.plugins = webpackConfig.plugins.filter(function(plugin) {
  return !(plugin instanceof webpack.optimize.CommonsChunkPlugin);
});

更多详见:https://github.com/dvajs/dva-cli/blob/1a4cb33/boilerplates/app/webpack.config.js

2. 工具升级困难

举一个实际的例子。

atool-build 内部配置有一段为:

{
  test: /\.css$/,
  loader: 'css!postcss'
}

后面由于一些原因,我们改成了:

{
  test: /\.css$/,
  loader: `${require.resolve('css')}!${require.resolve('postcss')}`
}

但立马导致一些用户出错,原因是他的配置里有判断 loader 内容是否为 css!postcss,这就让工具的升级寸步难行。

roadhog 配置

基于上面的原因,roadhog 的配置以 JSON 格式呈现。

下面是目前支持的全部配置项,他们在 roadhog#配置 中有详细解释:

{
  "entry": "src/index.js",
  "disableCSSModules": false,
  "less": false,
  "publicPath": "/",
  "extraBabelPlugins": [],
  "autoprefixer": null,
  "proxy": null,
  "env": null,
}

以及未来可能支持的配置项:https://github.com/sorrycc/roadhog/issues?q=is%3Aissue+is%3Aopen+label%3Aconfig

体验 roadhog

安装 roadhog:

$ npm i roadhog -g

新建项目目录:

$ mkdir myapp && cd myapp

创建 package.json,内容为:

{}

创建 src/index.js,内容为:

import './index.html';
document.write('Hello, roadhog!');

创建 src/index.html,内容为:

<script src="index.js"></script>

启动:

$ roadhog server

正常的话,会自动帮你打开浏览器,你会看到 Hello, roadhog!


(完)

12 步 30 分钟,完成用户管理的 CURD 应用 (react+dva+antd)

本文仅适用于 dva@1,dva@2 的文档请移步 #62
本文仅适用于 dva@1,dva@2 的文档请移步 #62
本文仅适用于 dva@1,dva@2 的文档请移步 #62

本文会一步步引导大家如何创建一个 CURD 应用,包含查询、编辑、删除、创建,以及分页处理,数据 mock,自动处理 loading 状态等,基于 react, dvaantd

最终效果:

开始之前:

  • 确保 node 版本是 6.5 +
  • cnpmyarn 能节约你安装依赖的时间

Step 1. 安装 dva-cli 并创建应用

先安装 dva-cli,并确保版本是 0.7.x。

$ npm i [email protected] -g
$ dva -v
0.7.0

然后创建应用:

$ dva new user-dashboard
$ cd user-dashboard 

Step 2. 配置 antdbabel-plugin-import

babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。

$ npm i antd --save
$ npm i babel-plugin-import --save-dev

修改 .roadhogrc,在 "extraBabelPlugins" 里加上:

["import", { "libraryName": "antd", "style": "css" }]

Step 3. 配置代理,能通过 RESTFul 的方式访问 http://localhost:8000/api/users

修改 .roadhogrc,加上 "proxy" 配置:

"proxy": {
  "/api": {
    "target": "http://jsonplaceholder.typicode.com/",
    "changeOrigin": true,
    "pathRewrite": { "^/api" : "" }
  }
},

然后启动应用:(这个命令一直开着,后面不需要重启)

$ npm start

浏览器会自动开启,并打开 http://localhost:8000

访问 http://localhost:8000/api/users ,就能访问到 http://jsonplaceholder.typicode.com/users 的数据。(由于 typicode.com 服务的稳定性,偶尔可能会失败。不过没关系,正好便于我们之后对于出错的处理)

Step 4. 生成 users 路由

用 dva-cli 生成路由:

$ dva g route users

然后访问 http://localhost:8000/#/users

Step 5. 构造 users model 和 service

用 dva-cli 生成 Model :

$ dva g model users

修改 src/models/users.js

import * as usersService from '../services/users';

export default {
  namespace: 'users',
  state: {
    list: [],
    total: null,
  },
  reducers: {
    save(state, { payload: { data: list, total } }) {
      return { ...state, list, total };
    },
  },
  effects: {
    *fetch({ payload: { page } }, { call, put }) {
      const { data, headers } = yield call(usersService.fetch, { page });
      yield put({ type: 'save', payload: { data, total: headers['x-total-count'] } });
    },
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname, query }) => {
        if (pathname === '/users') {
          dispatch({ type: 'fetch', payload: query });
        }
      });
    },
  },
};

新增 src/services/users.js

import request from '../utils/request';

export function fetch({ page = 1 }) {
  return request(`/api/users?_page=${page}&_limit=5`);
}

由于我们需要从 response headers 中获取 total users 数量,所以需要改造下 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"
 */
export default 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;
}

切换到浏览器(会自动刷新),应该没任何变化,因为数据虽然好了,但并没有视图与之关联。但是打开 Redux 开发者工具,应该可以看到 users/fetchusers/save 的 action 以及相关的 state 。

Step 6. 添加界面,让用户列表展现出来

用 dva-cli 生成 component:

$ dva g component Users/Users

然后修改生成出来的 src/components/Users/Users.jssrc/components/Users/Users.css,并在 src/routes/Users.js 中引用他。具体参考这个 Commit

需留意两件事:

  1. 对 model 进行了微调,加入了 page 表示当前页
  2. 由于 components 和 services 中都用到了 pageSize,所以提取到 src/constants.js

改完后,切换到浏览器,应该能看到带分页的用户列表。

Step 7. 添加 layout

添加 layout 布局,使得我们可以在首页和用户列表页之间来回切换。

  1. 添加布局,src/components/MainLayout/MainLayout.js 和 CSS 文件
  2. src/routes 文件夹下的文件中引用这个布局

参考这个 Commit

注意:

  1. 页头的菜单会随着页面切换变化,高亮显示当前页所在的菜单项

Step 8. 通过 dva-loading 处理 loading 状态

dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。

先安装 dva-loading :

$ npm i dva-loading --save

修改 src/index.js 加载插件,在合适的地方加入下面两句:

+ import createLoading from 'dva-loading';
+ app.use(createLoading());

然后在 src/components/Users/Users.js 里绑定 loading 数据:

+ loading: state.loading.models.users,

具体参考这个 Commit

切换到浏览器,你的用户列表有 loading 了没?

Step 9. 处理分页

只改一个文件 src/components/Users/Users.js 就好。

处理分页有两个思路:

  1. 发 action,请求新的分页数据,保存到 model,然后自动更新页面
  2. 切换路由 (由于之前监听了路由变化,所以后续的事情会自动处理)

我们用的是思路 2 的方式,好处是用户可以直接访问到 page 2 或其他页面。

参考这个 Commit

Step 10. 处理用户删除

经过前面的 9 步,应用的整体脉络已经清晰,相信大家已经对整体流程也有了一定了解。

后面的功能调整基本都可以按照以下三步进行:

  1. service
  2. model
  3. component

我们现在开始增加用户删除功能。

  1. service, 修改 src/services/users.js
export function remove(id) {
  return request(`/api/users/${id}`, {
    method: 'DELETE',
  });
}
  1. model, 修改 src/models/users.js
*remove({ payload: id }, { call, put, select }) {
  yield call(usersService.remove, id);
  const page = yield select(state => state.users.page);
  yield put({ type: 'fetch', payload: { page } });
},
  1. component, 修改 src/components/Users/Users.js,替换 deleteHandler 内容:
dispatch({
  type: 'users/remove',
  payload: id,
});

切换到浏览器,删除功能应该已经生效。

Step 11. 处理用户编辑

处理用户编辑和前面的一样,遵循三步走:

  1. service
  2. model
  3. component

先是 service,修改 src/services/users.js

export function patch(id, values) {
  return request(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(values),
  });
}

再是 model,修改 src/models/users.js

*patch({ payload: { id, values } }, { call, put, select }) {
  yield call(usersService.patch, id, values);
  const page = yield select(state => state.users.page);
  yield put({ type: 'fetch', payload: { page } });
},

最后是 component,详见 Commit

需要注意的一点是,我们在这里如何处理 Modal 的 visible 状态,有几种选择:

  1. 存 dva 的 model state 里
  2. 存 component state 里

另外,怎么存也是个问题,可以:

  1. 只有一个 visible,然后根据用户点选的 user 填不同的表单数据
  2. 几个 user 几个 visible

此教程选的方案是 2-2,即存 component state,并且 visible 按 user 存。另外为了使用的简便,封装了一个 UserModal 的组件。

完成后,切换到浏览器,应该就能对用户进行编辑了。

Step 12. 处理用户创建

相比用户编辑,用户创建更简单些,因为可以共用 UserModal 组件。和 Step 11 比较类似,就不累述了,详见 Commit


到这里,我们已经完成了一个完整的 CURD 应用。但仅仅是完成,并不完善,比如:

  • 如何处理错误,比如请求等
  • 如何处理请求超时
  • 如何根据路由动态加载 JS 和 CSS
  • ...

请期待下一篇。

(完)

装了啥

已更新 2019 版,详见 https://github.com/sorrycc/awesome-tools

经常被人问到我在用啥,所以记录下,以下是目前在用的软件和硬件。

由于这些经常会变,所以这篇文章也会做持续更新。

编辑器和 Terminal

我的编辑器是 Intellij Idea,内置功能很强大了,主要是省心,不必费精力去找插件等。然后也会辅助用 VSCode 去做一些临时文件的快速编辑。

  • 使用 Operator Mono Dank Mono 字体,Operator Mono 看久了有点厌
  • Intellij Idea 使用 material-theme-jetbrains,Theme 选 Material One Dark,字体依旧是 Dank Mono,Fallback Font 选 None,16 号,行距 1.2,效果图
  • Terminal 用 iTerm2 + zsh + oh-my-zsh 的组合,主题是 robbyrussell
  • 在 Intellij Idea, VSCode 和 iTerm2 里都配了 Operator Mono, 字号为 14 pt
  • 每个项目手动开启 ESlint,这样就会自动解析项目的 .eslintrc,并在编码时给出提示
  • iTerm2 里配 Run command.../usr/local/bin/idea \1 (),这样 Command + 点击文件路径,就会在 Intellij Idea 里打开
  • nvm 管理 Node.js 版本(记得清除历史版本,很占磁盘容量)

桌面应用

翻墙

  • google 云,绑信用卡一年免费,看 youtube 4K 流畅
  • rixcloud 备用,看 youtube 4K 流畅,有点贵,优点是稳定省心,老牌服务商,支持 surge 客户端
  • 软件在 Mac 下用 ShadowsocksX-NG-R,iPhone 下用 ShadowRocket(国区没,需切美区账号)
  • Proxifier,实现命令行翻墙,解决 iTerm2 下偶尔要连墙外网站的需求(比如 npm publish),我的配置

开发辅助

  • SourceTree,git 辅助,由于 git 高级操作命令没记住,就用 UI 操作了
  • Github Desktop,管理 github 仓库的变更和 PR,代替了 SourceTree 的部分工作,可以方便地把别人的 PR checkout 到本地验证
  • Ship 2,github issue 和通知管理
  • ColorSnapper2,取色
  • Dash,文档查看
  • Charles,Mac 和 iOS 下抓包用,支持 https
  • Paw,请求模拟
  • Gas Mask ,Hosts 管理

效率

输出

  • Bear,写作工具,颜值高,订阅了 Pro,和手机同步
  • OmniGraffle,画流程图
  • LICEcap ,GIF 录屏工具,准备尝试用 Kap 代替
  • ScreenFlow,视频录制和编辑,最近发现录屏还是自带的 QuickTime 更好用
  • XMind ZENXMind,脑图工具
  • Quiver,Markdown 笔记软件,在此之前一直用 Evernote ,已逐步切换到 Bear
  • Marp,用 Markdown 写 PPT,内部分享时用用

其他

Chrome 插件

在线应用

命令行

homebrew 安装。

硬件

早报汇总 @ 2017.9

早报 @ 2017.9.30

  1. React@16 改进了 React DevTools 里 Highlight Updated 的功能。https://twitter.com/dan_abramov/status/913730763169914882
  2. 可取消的 fetch 终于来了,目前只有 Firefox 57 支持,但 Chrome、Edge、Safari 都快了。https://developers.google.com/web/updates/2017/09/
  3. [email protected] 支持 Object Spread,https://twitter.com/wesbos/status/913421431609991168
  4. [email protected],支持 React 16。https://github.com/zeit/next.js/releases/tag/4.0.0-beta.1abortable-fetch,https://fetch-svg-abort.glitch.me/
  5. 基于 React 组件的可视化编程,付费的,目前功能不够完善,先不要买。https://twitter.com/getcompositor/status/913348539584995330,https://compositor.io/lab/
  6. 一组 State Container,挺实用的。https://github.com/renatorib/react-powerplug
  7. npm as cdn,一种简化的开发方式。但有个显而易见的问题,重复的依赖怎么处理?https://medium.com/@mikeal/ive-seen-the-future-it-s-full-of-html-2577246f2210

早报 @ 2017.9.29


早报 @ 2017.9.28

  1. React@16 特性视频,讲地很仔细,https://egghead.io/courses/leverage-new-features-of-react-16
  2. Podcast:关于命令行的。https://syntax.fm/show/013/the-command-line-for-web-developers
  3. componentDidCatch 的一个使用场景,捕获出错并打点到服务器。https://blog.bugsnag.com/react-16-error-handling/
  4. 学下设计模式、反模式、重构和 UML,这网站的切换体验很好。https://sourcemaking.com/
  5. 如何评价 React@16,总结的挺好,但不够细。https://www.zhihu.com/question/65920482/answer/236159084

早报 @ 2017.9.27

  1. React@16 发布,包含 fiber、ReactDOM.createPortal、componentDidCatch、SSR 性能提升、render 支持返回 fragment 和 string、尺寸减少等,推荐升级。https://facebook.github.io/react/blog/2017/09/26/react-v16.0.html
  2. Chrome 支持 import(),用于按需加载 es 模块。https://twitter.com/addyosmani/status/912556308649304064
  3. 自动分离出首屏样式。https://github.com/pocketjoso/penthouse

早报 @ 2017.9.26

  1. 输入 google.com 按回车后发生了什么?面试时常见问题。https://github.com/alex/what-happens-when
  2. Paul Irish 的 Debugging in 2017 with Node.js。https://www.youtube.com/watch?v=Xb_0awoShR8,https://docs.google.com/presentation/d/1i5JREE3hhtG4FDip97zFJpMG_qb8k6fb5BRF09eyYTM/view#slide=id.p (Slide)
  3. Electron the bad part,包括尺寸、更新、安全、代码保护等。https://hackernoon.com/electron-the-bad-parts-2b710c491547
  4. web developer roadhog,挺全的,图不错,用 Balsamiq Mockups 画的。https://github.com/kamranahmedse/developer-roadmap,https://balsamiq.com/products/mockups/
  5. 脚手架工具,简版的 yeoman,名字好记。https://github.com/saojs/sao
  6. 检测新的 DOM 节点。https://github.com/muicss/sentineljs

早报 @ 2017.9.25

  1. Dan 在 ZEIT Day 的演讲,https://www.youtube.com/watch?v=nl30vWYKs9A&feature=youtu.be&t=16300
  2. 部署 es2015 的代码到生产环境,省掉很多 polyfill 代码,Chrome 客户多的或者内部系统可以玩玩看,结合前几天分享的如何同时分发两个版本使用。https://philipwalton.com/articles/deploying-es2015-code-in-production-today/
  3. Compilers are the New Frameworks,深有同感,最近在做的一套多页 h5 方案,工具和框架紧密结合。https://tomdale.net/2017/09/compilers-are-the-new-frameworks/
  4. 为啥大家都开始转 VSCode 了?https://syntax.fm/show/012/why-is-everyone-switching-to-vs-code
  5. React 换许可证。https://code.facebook.com/posts/300798627056246/relicensing-react-jest-flow-and-immutable-js/
  6. Hyper 2.0 发布。https://twitter.com/zeithq/status/911511043763040258
  7. 以全局命令的方式运行 node_modules/.bin 下的命令,很实用的功能。https://www.youtube.com/watch?v=2WZ5iS_3Jgs&feature=youtu.be

~/.bashrc 或者 ~/.bash_profile 里配:

PATH=$PATH:./node_modules/.bin

早报 @ 2017.9.20


早报 @ 2017.9.19

  1. Desprited babel-preset-es2015,推荐用 babel-preset-env。https://twitter.com/piq9117/status/909492740768940032
  2. 《ECMAScript 6 入门》第三版,新增 decorator 章节。http://es6.ruanyifeng.com/
  3. 如何向用户同时分发 ES6 Modules 版本和普通版。https://www.youtube.com/watch?v=GWmO88hBbKY
<script nomodules ...>
<script type="modules" ...>
  1. IDE 的自动添加 import 的功能,相见恨晚,但 Intellij IDEA 只处理了 ES6 Module 的自动 import,最近有时间我会写个处理 node_modules 依赖的通用 auto-import。https://www.jetbrains.com/help/idea/auto-import.html,https://github.com/Galooshi/import-js
  2. 未来的 devtool 应该涨啥样,我们需要这样的脑洞。https://medium.freecodecamp.org/prototyping-the-future-of-devtools-f54ba4d51891
  3. 我不知道浏览器居然还能做这些,Speech API、Speech Recognition API、Geolocation API、Notification API、Push API、Battery Manager API、Media Recorder API、Web-audio API、Vibration API、Device orientation。https://www.youtube.com/watch?v=ZrRxdzAVXts,https://meow.sambego.be/

早报 @ 2017.9.18

  1. ES modules 详解。http://2ality.com/2017/09/native-esm-node.html
  2. 页面整体布局优先用 CSS Grid。http://gedd.ski/post/grid-beats-flexbox-for-full-page-layout/
  3. 为每个 PR 自动发 npm 包用于测试,这个思路不错,适合命令行工具。facebook/create-react-app#3124 (comment)
  4. 为函数内的赋值语句和 return 语句自动加 console 的 babel 插件,思路很新颖。https://github.com/tkh44/babel-plugin-sitrep
  5. GoogleChrome 官方的浏览器自动化库,可用于 UI 测试、爬虫等。https://github.com/GoogleChrome/puppeteer
  6. 布局管理的库,可用于 React,但需要额外引入 jQuery 。https://github.com/deepstreamIO/golden-layout/

早报 @ 2017.9.15

  1. React 16 RC 3,最后一个 RC 了,比较感兴趣的是其中任何不打包多个 Object.assign polyfill,polifill 重复是很常见的尺寸问题之一。facebook/react#10294 (comment)
  2. 记不住 React 的事件?可以查这份 Live CheetSheet 。https://reactarmory.com/guides/react-events-cheatsheet
  3. 如何让 import 输入更快,先写 from 再写接口就能有智能提示。作者给出了 VSCode 的配置方法,评论里有 ATOM 以及 WebStorm 的方法。http://2ality.com/2017/08/typing-import-statements.html
  4. Gif 格式的 devtool tips,你会发现居然有这么多功能都没用过。https://umaar.com/dev-tips/ ,还有视频分享资料,https://umaar.github.io/devtools-optimise-your-web-development-workflow-2016/,https://www.youtube.com/watch?v=Dyynkefld8o
  5. react-cosmos 发了 2.0 rc,一个用来写 React 组件的工具,https://github.com/react-cosmos/react-cosmos

早报 @ 2017.9.14

  1. Chrome 61 支持 ES Module,通过 <script type=module> 引入。隐隐感觉这功能会带来调试和打包工具的革新。https://www.chromestatus.com/feature/5365692190687232,https://paulirish.github.io/es-modules-todomvc/
  2. Babel 7.0@beta,支持通过 .babrlrc.js 配置、移除 babel 自身的 babel-runtime 依赖以减少安装尺寸、Deprecate ES20xx presets,推荐用 babel-preset-env 等等。https://babeljs.io/blog/2017/09/12/planning-for-7.0
  3. Node v8.5.0,fs.copyFileconsole.group()、正式支持 ES Module 等。https://nodejs.org/en/blog/release/v8.5.0/
  4. 编码界面的正则工具,Cool!https://regexly.chipto.io/ ,我之前用的是 http://refiddle.com/
  5. React FAQ,还是比较全的。https://reactfaq.site/,https://github.com/timarney/react-faq

早报 @ 2017.9.13

  1. coding webIDE 支持小程序,功能上只能说部分支持吧。通过多页实现,和单脚本内存共享数据肯定不是一回事,API 模拟不全,生命周期好像也不全。https://ide.coding.net/
  2. React-Router@4 和 @3 支持 React@16,remix-run/react-router@b0a9c75
  3. AtomIDE,其实是 atom 捆绑一些 packages 以适应各种语言的编码、定义跳转、自动提示、引用查找等,适合新人上手。http://blog.atom.io/2017/09/12/announcing-atom-ide.html
  4. 新的信息源,用于订阅新闻。https://www.google.com/alerts
  5. react for aria,如何把更新的组件通知读屏软件。http://almerosteyn.com/2017/09/aria-live-regions-in-react
  6. HTML 语法解析器,带插件机制,可以理解为 HTML 界的 Babel。https://github.com/reshape/reshape

早报 @ 2017.9.12

  1. 把 React Component 转成 Word 文档,样式没了。https://github.com/nitin42/redocx
  2. 关于如何避免“写一个 Module 感觉像写了一张无法支付的未来支票”,比如用脚本 commit、自动 publish、100% test coverage 等。https://medium.com/@mikeal/modern-modules-d99b6867b8f1,https://github.com/mikeal/r2/blob/master/package.json#L6
  3. dat 协议的 p2p 应用例子,基于 BreakerBrowser。https://github.com/taravancil/paste-dat,https://beakerbrowser.com/
  4. React@16 不再忽略 unknown 的 DOM 属性,https://facebook.github.io/react/blog/2017/09/08/dom-attributes-in-react-16.html
  5. webpack + babel 让 JS 打包越来越慢,但如何让调试快点呢?babel-standalone、systemjs、unpkg 等都是一些尝试。印象中 stackblitz.com 也是基于 systemjs + unpkg。http://2ality.com/2017/08/less-building-in-web-dev.html,https://github.com/unpkg/unpkg-demos

早报 @ 2017.9.8

  1. Yarn 1.0 发布,新功能很实用,比如适用于 monorepo 的 Workspaces,解决依赖的依赖更新问题的 Selective version resolutions 等,https://code.facebook.com/posts/274518539716230
  2. state-machine-component by preact 作者,通过 reducer + action 的方式组织 React Component 的内部 state。https://twitter.com/_developit/status/905250269306474497
  3. 管道操作符 |> 的 JavaScript 提案,适用于 lodash、rxjs 这种库,想尝鲜的可以配 babel 插件实现。https://github.com/tc39/proposal-pipeline-operator,https://www.npmjs.com/package/babel-plugin-transform-pipeline
  4. Web Payments 支付接口快要来了,Chrome Canary 可用,Webkit 也是 In Development 状态,https://twitter.com/wesbos/status/905815017819385857,https://webkit.org/status/#feature-payment-request
  5. React Native EU 2017,https://medium.com/@dschmidt1992/react-native-eu-2017-b091adc9aa9f

早报 @ 2017.9.7

  1. 利用 @std/esm 让 node 无需 .mjs 后缀就使用 ES6 modules。https://twitter.com/zachcodes/status/903251125520007168
  2. 新发布的一个 JavaScript runtime error tracker 服务,支持 AST 回溯到源码。https://javascript.studio/,https://medium.com/javascript-studio/javascript-studio-publicly-available-28522e325037
  3. Why CSS, not CSS-in-JS,同意,个人也不喜欢 CSS-in-JS,https://svelte.technology/blog/the-zen-of-just-writing-css
  4. react-idle,在 react 空闲时做点啥,比如加载脚本、问问用户是否还在、做一些耗 CPU 的事等,by React-Router 作者,https://reacttraining.com/react-idle/,https://cdb.reacttraining.com/announcing-react-idle-8fc0b9e2d33e
  5. React.ReducerComponent,在 React Component 里用 reducer 来更新 react 的 state,看看就好,另一种思路。https://twitter.com/jaredpalmer/status/905170062679662594

MobX 和 Redux 的比较

先要明白 mobx 和 redux 的定位是不同的。redux 管理的是 (STORE -> VIEW -> ACTION) 的整个闭环,而 mobx 只关心 STORE -> VIEW 的部分。

但作为两个目前最火的 React 应用框架库,人们习惯于把他们比较到一起。下面我们也来看下 mobx 和 redux 相比的优缺点。(据说每个列 3 点会让人更容易记住。。)

优点

基于运行时的数据订阅

mobx 的数据依赖始终保持了最小,而且还是基于运行时。而如果用 redux,可能一不小心就多订阅或者少订阅了数据。所以为了达到高性能,我们需要借助 PureRenderMixin 以及 reselect 对 selector 做缓存。所以,如果。

OverSubscription 的例子:

  1. 非实时计算
view() {
  if (count === 0) {
    return a;
  } else {
    return b;
  }
}

基于 redux 的方案,我们必须同时监听 count, a 和 b 。在 counte === 0 的时候,b 如果修改了,也会触发 view 。而这个时候的 b 其实是无意义的。

  1. 粗粒度 subscription
view() {
  todos[0].title
}

基于 redux,我们通常会订阅 todos,这样 todos 的新增、删除都会触发 view 。其实这里真正需要监听的是 todos 第一个元素的 title 属性是否有修改。

通过 OOP 的方式组织领域模型 (domain model)

OOP 的方式在某些场景下会比较方便,尤其是容易抽取 domain model 的时候。进而由于 mobx 支持引用的方式引用数据,所以可以非常容易得形成模型图 (model graph ),这样可以更好地理解我们的应用。

修改数据方便自然

mobx 是基于原生的 JavaScript 对象、数组和 Class 实现的。所以修改数据不需要额外语法成本,也不需要始终返回一个新的数据,而是直接操作数据。

缺点

缺最佳实践和社区

mobx 比较新,遇到的问题可能社区都没有遇到过。并且,mobx 并没有很好的扩展/插件机制。

随意修改 store

我们都知道 redux 里唯一可以改数据的地方是 reducer,这样可以保证应用的安全稳定;而 mobx 可以随意修改数据,触发更新,给人一种不安全的感觉。

最新的 mobx 2.2 加入了 action 的支持。并且在开启 strict mode 之后,就只有 action 可以对数据进行修改,限制数据的修改入口。可以解决这个问题。

逻辑层的限制

如果更新逻辑不能很好地封装在 domain class 里,用 redux 会更合适。另外,mobx 缺类 redux-saga 的库,业务逻辑的整合不知道放哪合适。

webpack2,升还是不升?

webpack2.2 即将发布 之际,我们来看下 webpack2 有哪些新特性。至于是否升级,大家心里应该有自己的打算吧。

优势

感觉 webpack2 最大的改进是 ES6 modules 和 Tree Shaking,其他都是配置方面的。

native ES6 import, export

用的时候注意要把 babel-preset-es2015 的 modules 关掉:

{
  "presets": [
    ["es2015", { "modules": false }]
  ]
}

Tree Shaking for ES6

这一特性通过 babel 插件的方式已可实现,比如 babel-plugin-importbabel-plugin-lodash ,所以现在开来已不觉得惊艳了。另外,他需要 npm 包的额外支持,打出一份 ES6 Module 的文件。

Needs Promise polyfill in old browsers

输出多 chunk 时需提供 promise-polyfill 。

webpack config can return a Promise

提供异步的 webpack 配置方式。

劣势

劣势挺明显,社区、文档、性能,每样都是痛点。

社区

插件和 loader 社区需要一段时间的适配。比如 ericclemmons/npm-install-webpack-plugin 目前还不支持 webpack2 。

文档

官方文档 看起来还不错,不过大部分人遇到 webpack 配置问题,通常会去 stackoverflow 等问答社区搜,而这些社区在 webpack2 还很少有积累,估计很难找到答案。

性能

把 roadhog 尝试 升级到 webpack2,并以 dva-example-user-dashboard 为例,比较了下 webpack1 和 webpack2 的性能,如下:

webpack1 webpack2
roadhog server 9s 14s
roadhog build 18s 19s
roadhog build --debug (不压缩) 9s 14s

慢了不是一点点。。

结论

暂不升级,等 webpack2 社区完善以及性能提升吧。

参考

(完)

支付宝前端应用架构的发展和选择

对 Roof 不感兴趣的同学可以直接从 Redux 段落读起。

下文说说我理解的支付宝前端应用架构发展史,从 roof 到 redux,再到 dva

Roof 应该是从 0.4 开始在项目里大范围推广的。

Roof 0.4

Roof 0.4 接触不多,时间久了已经没有太多印象了,记忆中很多概念是从 baobab 里来的,通过 cursor 订阅数据,并基于此设计了很多针对复杂场景的解决方案。

这种方式灵活且强大,现在想想如果这条路一走到底,或许比现在要好一些。但由于概念比较多,当时大家都比较难理解 cursor 这类的概念。并且 redux 越来越流行。。

Roof 0.5

然后有了 Roof 0.5,提供 createRootContainer 和 createContainer,实现类似 react-redux 里 Provider 和 connect 的功能,并隐藏了 cursor 的概念。

// 定义 state
createRootContainer({
  user: { name: 'chris', age: 30 }
})(App);

// 绑定 state
createContainer({
  myUser: 'user',
})(UserInfo);

这在一定程度上迎合了 redux 用户的习惯。但 redux 用户却并不满足,就算不能用 redux,也希望能在 roof 上使用上更多 redux 相关的特性。

还有个在这一阶段讨论较多的另一个问题是没有最佳实践,大家针对同一个问题通常有不同的解法。最典型的是异步请求的处理,有些人直接写从 Component 生命周期里,有些好一点的提取成 service/api,但还是在 Component 里调,还有些提取成 Controller 。

这是 library 相对于 framework 的略势,Roof 本质上是一个 library,要求他去解决所有开发中能想到的问题其实是不公平的。那么如何做的? 目前看起来有两种方案,1) boilerplate 2) framework 。这在之后会继续探讨。

Roof 0.5.5

在经历了几个 bugfix 版本之后,Roof 0.5.5 却是个有新 feature 的更新。感觉从这个版本起已经不是原作者的本意了,而是对于用户的妥协。

这个版本引入了一个新的概念:action

这也是从 redux (或者说 flux) 里而来的,所有用户操作都可以被理解成是一个 action,这样在 Component 里就不用直接调 Controller 或者 api/service 里的接口了,一定程度上做了解耦。

createActionContainer({
  myUser: 'user',
}, {
  // 绑定 actions
  userActions,
})(UserInfo);

这让 Roof 越来越像 redux,但由于没有引入 dispatch,在实际项目中遇到了不少坑。比较典型的是 action 之间的互相调用。

function actionA() {
  actionB();
}
function actionB() {}

还有 action 里更新数据之前必须重新从 state 里拉最新的进行更新之类的问题,记得当时还写过 issue 来记录踩过的坑。这是想引入 redux,但却只引入一半的结果。

Roof 0.5.6@beta

然后是 Roof 0.5.6@beta,这个版本的内核已经换成了 redux,引入 reducerdispatch 来解决上个版本遇到的问题。所以本质上他等同于 react-redux,看下 import 语句应该就能明白。

import { createStore, combineReducers } from 'redux';
import { createDispatchContainer, createRootContainer } from 'roof';

大家可能注意到这个版本有个 @beta,这也是目前 Roof 的最终版本。因为大家意识到既然已经这样了,为啥不用 redux 呢?

Redux

然后就有不少项目开始用 redux,但是 redux 是一个 library,要在团队中使用,就需要有最佳实践。那么最佳实践是什么呢?

理解 Redux

Redux 本身是一个很轻的库,解决 component -> action -> reducer -> state 的单向数据流转问题。

按我理解,他有两个非常突出的特点是:

  1. predictable,可预测性
  2. 可扩展性

可预测性是由于他大量使用 pure function 和 plain object 等概念(reducer 和 action creator 是 pure function,state 和 action 是 plain object),并且 state 是 immutable 的。这对于项目的稳定性会是非常好的保证。

可扩展性则让我们可以通过 middleware 定制 action 的处理,通过 reducer enhancer 扩展 reducer 等等。从而有了丰富的社区扩展和支持,比如异步处理、Form、router 同步、redu/undo、性能问题(selector)、工具支持。

Library 选择

但是那么多的社区扩展,我们应该如何选才能组成我们的最佳实践? 以异步处理为例。(这也是我觉得最重要的一个问题)

用地比较多的通用解决方案有这些:

redux-thunk 是支持函数形式的 action,这样在 action 里就可以 dispatch 其他的 action 了。这是最简单应该也是用地最广的方案吧,对于简单项目应该是够的。

redux-promise 和上面的类似,支持 promise 形式的 action,这样 action 里就可以通过看似同步的方式来组织代码。

但 thunk 和 promise 都有的问题是,他们改变了 action 的含义,使得 action 变得不那么纯粹了。

然后出现的 redux-saga 让我眼前一亮,具体不多说了,可以看他的文档。总之给我的感觉是优雅而强大,通过他可以把所有的业务逻辑都放到 saga 里,这样可以让 reducer, action 和 component 都很纯粹,干他们原本需要干的事情。

所以在异步处理这一环节,我们选择了 redux-saga

最终通过一系列的选择,我们形成了基于 redux 的最佳实践

新的问题

但就像之前所有的 Roof 版本一样,每个时代的应用架构都有自己的问题。Redux 这套虽然已经比较不错,但仍避免不了在项目中暴露自己的问题。

  1. 文件切换问题

    redux 的项目通常要分 reducer, action, saga, component 等等,我们需要在这些文件之间来回切换。并且这些文件通常是分目录存放的:

    + src
      + sagas
        - user.js
      + reducers
        - user.js
      + actions
        - user.js
    

    所以通常我们需要在这三个 user.js 中来回切换。(真实项目中通常还有 services/user.js 等) 不知大家是否有感觉,这样的频繁切换很容易打断编码思路?

  2. saga 创建麻烦

    我们在 saga 里监听一个 action 通常需要这样写:

    function *userCreate() {
      try {
        // Your logic here
      } catch(e) {}
    }
    function *userCreateWatcher() {
      takeEvery('user/create', userCreate);
    }
    function *rootSaga() {
      yield fork(userCreateWatcher);
    }

    对于 redux-saga 来说,这样设计可以让实现更灵活,但对于我们的项目而言,大部分场景只需要用到 takeEvery 和 takeLatest 就足够,每个 action 的监听都需要这么写就显得非常冗余。

  3. entry 创建麻烦

    可以看下这个 redux entry 的例子,除了 redux store 的创建,中间件的配置,路由的初始化,Provider 的 store 的绑定,saga 的初始化,还要处理 reducer, component, saga 的 HMR 。这就是真实的项目应用 redux 的例子,看起来比较复杂。

dva

基于上面的这些问题,我们封装了 dva 。dva 是基于 redux 最佳实践 实现的 framework,api 参考了 choo,概念来自于 elm 。详见 dva 简介

并且除了上面这些问题,dva 还能解决 domain model 组织和团队协作的问题。

来看个简单的例子:(这个例子没有异步逻辑,所以并没有包含 effects 和 subscriptions 的使用,感兴趣的可以看 Popular Products 的 Demo)

import React from 'react';
import dva, { connect } from 'dva';
import { Route } from 'dva/router';

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    ['count/add'  ](count) { return count + 1 },
    ['count/minus'](count) { return count - 1 },
  },
});

// 3. View
const App = connect(({ count }) => ({
  count
}))(function(props) {
  return (
    <div>
      <h2>{ props.count }</h2>
      <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
      <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
    </div>
  );
});

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

// 5. Start
app.start(document.getElementById('root'));

5 步 4 个接口完成单页应用的编码,不需要配 middleware,不需要初始化 saga runner,不需要 fork, watch saga,不需要创建 store,不需要写 createStore,然后和 Provider 绑定,等等。但却能拥有 redux + redux-saga + ... 的所有功能。

更多 dva 的详解,后面会逐步补充。

最后

从 Roof 到 Redux 再到 dva 一路走来,每个方案都有自己的优点和缺陷,后一个总是为了解决前一个方案的问题而生,感觉上是在逐步变好的过程中,这让我觉得踏实。

另外,感叹坚持走自己的路是件很困难的事情,尤其是积累了一定用户量之后。在害怕失去用户和保留本心之间需要有个权衡和坚守。

运行npm start出错

Failed to start the server, since you have enabled dllPlugin, but have not run
roadhog buildDllbeforeroadhog server`.

请问这个是什么错误

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.