Baisc Usage of Redux-Observable

First of All

createEpicMiddleware(rootEpic)

createEpicMiddleware is used to create an instance of the actual redux-observable middleware, and you should provide a single, root Epic

Arguments

  1. rootEpic: Epic: The root Epic

  2. [options: Object]: The optional configuration.
  • dependencies: If given, it will be injected as the 3rd argument to all Epics,

  • adapter: An adapter object which can transform the input/output streams provided to your epics, Usually used to adapt a stream library other than RxJS v5, like adapter-rxjs-v4 or adapter-most Options:

    *   `input: ActionsObservable => Any`: Transforms the input stream of actions, `ActionsObservable` that is passed to your root Epic(transformation takes place before it is passed to the root Epix)
    
    *   `output: any => Observable`: Transforms the return value of root Epic(transform takes place after the root epic returned it)
    

Returns

MiddlewareAPI: An instance of the redux-observable middleware

Example

import { createStore, applyMiddleware, compose } from 'redux'
import { createEpicMiddleware } from 'redux-observable'
import { rootEpic, rootRotuer } from './modules/root'

const epicMiddleware = createEpicMiddleware(rootEpic)

export default function configureStore () {
  const store = createStore(
    rootRotuer,
    applyMiddleware(epicMiddleware)
  )
  return store
}
`</pre>

### `combineEpics(...epic)`

`combineEpics` allows you to take multiple epics and combine them into a single one

#### Arguments

`...epics: Epic[]`: The epics to combine

#### Return

`(Epic)`: An Epic that merges the output of every Epic provided and passes along the `ActionObservable` and redux store as arguments

### Example

<pre>`import { combineEpics } from 'redux-observable'
import pingEpic from './ping'
import fetchUserEpic from './fetchUser'

export default combineEpics(
  pingEpic,
  fetchUser
)
`</pre>

### `EpicMiddleware`

An instance of the redux-observable middleware

To create it, pass your root Epic to `createEpicMiddleware`

`replaceEpic(nextEpic)`: Replaces the epic currently by the middleware, it is an advanced API. You might need this if your app implement code splitting and you want to load some of the epics dynamically or you're using hot reloading.

### Create Epic

<pre>`const pingEpic = action$ =&gt; action$.ofType('PING').mapTo({type: 'PONG'})
`</pre>

### Cancellation

<pre>`import { ajax } from 'rxjs/observable/dom/ajax'

const fetchUserEpic = action$ =&gt; action$.ofType('FETCH_USER').mergeMap(
  action =&gt; ajax.getJSON(`/api/user/${action.payload}`)
    .map(res =&gt; fetchUserFulfilled(res))
    .takeUntil(action$.ofType('FETCH_USER_CANCELLED'))
)
`</pre>

### Handling Error

<pre>`import { ajax } from 'rxjs/observable/dom/ajax'

const fetchUserEpic = action$ =&gt; action.ofType('FETCH_USER').mergeMap(
  action =&gt; ajax.getJSON(`/api/users/${action.payload}`)
  .map(res =&gt; fetchUserFulfilled(res))
  .catch(err =&gt; Observable.of({
    type: 'FETCH_USER_REJECTED',
    payload: err.xhr.response,
    error: true,
  }))
)
`</pre>

### dependencies Injecting

<pre>`import { createEpicMiddleware, combineEpics } from 'redux-observable'
import { ajax } from 'rxjs/observable/dom/ajax'
import rootEpic from './somewhere'

const epicMiddleware = createEpicMiddleware(rootEpic, {
  dependencies: { getJSON: ajax.getJSON }
})

const fetchUserEpic= (action$, store, { getJSON }) =&gt; action$.ofType('FETCH_USER').mergeMap(
  (action) =&gt; getJSON(`/api/users/${action.payload}`)
    .map(res =&gt; ({
      type: 'FETCH_USER_REJECTED',
      payload: res
    }))
)
`</pre>

### Adding New Epics Asynchronously/Lazily

<pre>`import { BehaviorSubject } from 'rxjs/BehaviorSubject'
import { combineEpics } from 'redux-observable'

const epic$ = new BehaviorSubject(combineEpics(epic1, epic2))
const rootEpic = (action$, store) =&gt; epic$.mergeMap(epic =&gt; epic(action$, store))

// sometime later ... add another Epic, keeping the state of the old ones...
epic$.next(asyncEpic1)
// and again later add another
epic$.next(asyncEpic2)

Functor 函子

class Functor {
  constructor (val) {
    this.__val = val
  }

  map (fn) {
    return new Functor(fn(__this.value))
  }
}

即将范畴 Functor@val 通过 fn 映射为范畴 Function@fn(val)

Functor 本身具有 map 方法, 通过各种函数作为运算符, 映射成新的Functor

Throttle 函数

function throttle (fn, threshold, scope) {
  threshold || threshold = 250
  var last, timer
  return function () {
    var ctx = scope || this
    var now = +new Date()
    var args = arguments
    if (last &amp;&amp; now - last - threshold &lt; 0) {
      // hold on it
      clearTimeout(timer)
      timer = setTimeout(function () {
        last = now
        fn.apply(ctx, args)
      }, threshold)
    } else {
      last = now
      fn.apply(ctx, args)
    }
  }
}
`</pre>

<pre>`Elm.addEventListener('click', throttle(function(event){
  // ...
}, 1000))

假设第一次执行 throttle(fn0), fn0会立即执行, 并记录 throttle 开启的时间last

第二次执行, 假设为 throttle(fn1), 如果距离 now 未超出时限 threshold, 则开启定时器, threshold 后执行 fn1, 并设置 last

如果在开启定时器后, threshold 时限内执行 throttle(fn2) 则用于执行 fn1 的定时器会被清空, 开启 fn2 的定时器, 并设置 last

简而言之, 执行 throttle 的时候, 如果上一次的定时器未被执行, 则会被清除, 并重新开始计时

Debounce 函数

function debounce (fn, delay) {
  var timer = null
  return function () {
    var context = this, args = arguments
    clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}
`</pre>

Example:

<pre>`Elm.addEventListener('click', debounce(function(event){
  ...
}, 250))

可以看出, 传入 debounce 的所有函数共享计时器 timer, 因此如果在 delay 时间内再次调用 debounce(fn), 即上一次 fn 为执行而新的 fn 传入时, 上一次的 fn 的 timer 会清空, 对新传入的 fn 进行计时

其结果即是,

在 delay 时限内复数次调用 debounce 会刷新计时器

React-Router 中 Props 的传递

原本写

{children}
`</pre>

的地方改写为

<pre>`{children &amp;&amp; React.cloneElement(children, {
  prop, // 需要传入的 props
})}

即可

官方案例

Babel-Polyfill 入门

yarn add babel-polyfill

import 'babel-polyfill'
`</pre>

babel-polyfill 提供了全局访问 API, 如 Promise, Set 以及 Map, 但是 polyfill 提供的 api 可能会污染到全局作用域, 特别在你把代码导报为第三方库给其他人用时, 或者你无法控制代码运行环境时, 都可能存在问题

`babel` 提供了 `transform-runtime` 插件来解决此问题, `transform-runtime` 提供一个沙盒机制来组织全局变量的污染

> 需要注意, transform-runtime 依赖于 babel-runtime

<pre>`# transform-runtime 只应用于开发环境
yarn add --dev babel-plugin-transform-runtime 

# 其依赖包 babel-runtime 需要打包到生产环境
yarn add babel-runtime
`</pre>

<pre>`// .babelrc
{
  "plugins": ["transform-runtime"]
}

or

{
  "plugins": [
    ["transform", {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime",
      }]
  ]
}

解决 CommonsChunkPlugin 的 Hash 变化问题

原文:

Manifest File
But, if we change application code and run webpack again, we see that the hash for the vendor file changes. Even though we achieved separate bundles for vendor and main bundles, we see that the vendor bundle changes when the application code changes. This means that we still don’t reap the benefits of browser caching because the hash for vendor file changes on every build and the browser will have to reload the file.

The issue here is that on every build, webpack generates some webpack runtime code, which helps webpack do it’s job. When there is a single bundle, the runtime code resides in it. But when multiple bundles are generated, the runtime code is extracted into the common module, here the vendor file.

To prevent this, we need extract out the runtime into a separate manifest file. Even though we are creating another bundle, the overhead is offset by the long term caching benefits that we obtain on the vendor file.

非翻译:

每次打包时 vendor 文件的 hash 也会变化的原因在于, webpack 打包时必定会产生 runtime code.

当我们需要打包出 vendor.js 的时候, 尽管公共代码没有变化, 但是 webpack 会生成新的 runtime code 并注入 vendor.js, 导致其 hash 变化.

解决方法:

将 vendor.js 中的 runtime code 单独打包出来, 这样只有 bundle.js 和 runtime code 的 hash 会变化, vendor.js 的代码不会变化.

具体做法:

new webpack.optimize.CommonsChunkPlugin({
  name: ['vendor', 'manifest'],
}

这样就可以把 runtime code 打包进 manifest.js, 此后该类操作不会改变 vendor.js 的 hash 了

Webpack 2 Config 文件入门

webpack 2 config 文件入门

webpack.config.js 的基本结构

var path = require('path')

module.exports = {
  entry:  {
    home: './home.js',
    about: './about.js',
    contact: './contact.js',
  }, // 入口文件
  // string | [string] | object {&lt;key&gt;: string | [string] }
  // 一个页面搭配一个入口文件
  output: {
    // output 包含一系列关于打包后文件的配置
    // output.chunkFilename: string, 这个参数确定了加载 chunk 的名称
    // output.crossOriginLoading: boolean | string, 只在 target 参数指定为 web 的时候使用, 因为此时通过 jsonp 加载模块
    // crossOriginLoading: false(default) , 禁止跨域加载
    // crossOriginLoading: 'anonymous', 允许无 credential 的跨域加载
    // crossOriginLoading: 'use-credentials', 允许有 credential 的跨域加载
    // 
    // output.devtoolFallbackModuleFilenameTemplate: string | function(info)
    // output.hotUpdateChunkFilename: string, 指定热刷新的 chunk 的名称, 仅支持 id 和 hash 占位符
    // hotUpdateChunkFilename: '[id].[hash].hot-update.js'
    // output.sourceMapFilename: string, 仅当 devtool 使用 source map 时生效, 默认为[ file].map, 可选占位符有[name], [id], [hash], [chunkhash]

    path: path.resolve(__dirname, 'dist'), // 打包后文件存储路径, 必须是绝对路径, 可以用 path 模块生成

    publicPath: /assert/, // 解析文件中相对路径时依赖的根目录
    // 该字段设定了输出文件的 public Url
    // 
    // 默认的, 相对路径会基于 html 文件位置或&lt;base&gt;标签进行解析, 添加 publicPath 后, 各个 loaders 会在每个相对路径前添加 publicPath, 即相对路径基于 publicPath 进行解析
    // 该值一般以'/'结尾
    // 默认值为''
    // simple rule: 该参数的值为html 页面到 output.path 的相对值
    // path: path.resolve(__dirname, 'public/assets'),
    // publicPath: 'https://cdn.example.com/assets/',
    // publicPath 也会对 webpack-dev-server 生效
    // publicPath: 'https://cdn.example.com/assets/' cdn, 并且应该使用 https
    // publicPath: '//cdn.example.com/assets/', cdn, 使用相同协议
    // publicPath: '/assets/', 相对于服务器根路径
    // publicPath: 'assets/', 相对于 html 页面
    // publicPath: '../assets/', 相对于 html 页面
    // publicPath: '', 相对于 html 页面(同一路径)

    filename: 'bundle.js', // 打包后的文件名称
    // 对于单一入口文件, filename 可以为静态名称'bundle.js'
    // 对于多入口文件, 可以使用参数生成特定名称
    // [name], [id], [hash], [chunkhash]
    // 可以为文件添加路径: 'js/[name]/bundle.js'

    library: 'MyLibrary', // 输出库的名称

    libraryTarget: 'umd', // 输出库的类型
  },
  module: {
    // module.noParse: RegExp | [RegExp], 符合正则的字段被 import, require, define 后不会被处理, 可以提高打包速度
    // noParse: /jquery|lodash/
    //
    rules: [
      // 模块应用规则(loaders, parser 的设定等)
      // module.rules: array
      // 一条 Rule 可以分为三部分, Conditions, Results, Nested Rules

      // Conditions, 有两个输入值, 
      // 1\. The resource: 文件引用的绝对路径经过 resolve 规则解析后的结果
      // 2\. The issuer: 引用 resource 的模块到文件的绝对路径
      // 比如, 'app.js' 中 'import ./style.css', resource 是 '/path/to/style.css', issuer 是 '/path/to/app.js'
      // 在 rule 中, test, include, exclude 和 resource 作用于resource, 而 issuer 族谱用于 issuer

      // Results
      // Rule Results 仅在 Rule Conditions 满足时起作用
      // Rule Results 有二种输出值
      // Applied Loaders: 一个数组, 其元素是应用于 resource 的 loaders
      // Parser options: 一个 options 对象, 作为当前 module 的 parser
      // loader, options, use 用于 loaders,
      // query, loaders 用于提高兼容性
      // enforce 属性指定 loader 类型, 可选值有: normal, pre, post
      // parser 属性影响 parser options

      // Nested Rules
      // 在 rules 和 oneOf 属性中可以指定 nested rules

      // Rule.enforce: 'pre' | 'post', 指定 loader 的类型, 默认为 normal
      // 还有一种 inline loader 应用于 inline import/require, 
      // 所有 loaders 按照 post, inline, normal, pre 排序并依次执行
      // 所有 normal loaders 可以通过在 request 中添加前缀 '!' 进行忽略
      // 所有 normal 和 pre loaders 可以通过在 request 中添加前缀 '-!' 进行忽略
      // 所有 normal, post 和 pre loaders 可以通过在 request 前添加 '!!' 进行忽略

      // Rule.exclude
      // Rule.exclude 是 Rule.resource.exclude 的缩写
      // 
      // Rule.include
      // Rule.include 是 Rule.resource.include 的缩写
      //
      // Rule.issuer
      //
      // Rule.loader
      // Rule.loader 是 Rule.use: [{loader}] 的别名
      // 
      // Rule.loaders
      // Rule.loaders 是 Rule.use 的别名, 他的存在仅为了兼容 webpack1, 应使用 Rule.use
      // 
      // Rule.oneOf
      // 一个数组, 其元素为一系列 rules, 仅第一个符合条件的 rule 会起作用
      // 
      // Rule.options/ Rule.query
      // Rule.options 和 Rule.query 都是 Rule.use: [{options}] 的缩写
      // Rule.query 的存在仅为了兼容 webpack1, 应使用 Rule.options
      // 
      // Rule.parser
      // 一个 parser options 的对象, 所有要使用的 parser options 都集中在此
      // 每个不同的 parser options 都会创建对应 parser, 并且 plugins 可以根据 parser options 应用.  当 parser options 中没有设置或设置为 true 时, 大部分 plugins 默认只应用他们自己的 parser plugins
      // parser: {
      // amd: false, // disable AMD
      // commonjs: false, // disable CommonJS
      // harmony: false, // disable ES6 Harmony import/exports
      // requireInclude: false, // disable require.include
      // requreEnsure: false, // disable require.ensure
      // }

      // Rule.resource
      // Rule.rules, 一个 Rules 组成 的数组
      // Rule.test, Rule.resource.test 的缩写
      // Rule.use, 一系列应用于 modules 的 UseEntries, 每一个 entry 指定一个 loader
      // use: ['style-loader'] 是 use: [{loader: 'style-loader'}] 的缩写
      // 可以链式传入多个 loaders, 从右向左依次执行
      // use: [
      //   {loader: 'style-loader'},
      //   {loader: 'css-loader', options: { importLoader: 1}},
      //   {loader: 'less-loader', options: { noIeCompat: true}},
      // ]

      // Condition
      // Condition 可以为以下之一:
      // String: 比如 exclude: 'node_modules'
      // RegExp: 比如 test: /\.js$/, exclude: /node_modules/
      // function: 返回 true 或 false
      // Condition Array: 至少有一个满足时刻认为满足 condition
      // Object: 所有属性都满足时可认为满足
      // 
      // {test: Condition}, {include: Condition}, {exclude: Condition}, {and: [Condition]}, {or: [Condition]}, {not: Condition}
      // 
      // UseEntry: object
      // 该属性必须有一个字符串 loader.
      // 该属性相对 context 的值解析 loader
      // 该属性拥有 options 属性, 该属性为 string 或 object, 该属性会作为 loader options 被传给 loader
      // 考虑到兼容性, 目前仍支持 query 作为 options 的别名
      // 

      {
        test: /\.jsx?$/, // 通过正则表达式选择要处理的文件
        include: [ // 限定文件范围
          path.resolve(__dirname, 'app')
        ],
        exclude: [ // 排除文件范围
          path.resolve(__dirname, 'demo')
        ]
        // 最佳实践
        // test 中使用正则表达式
        // include 和 exclude 中使用绝对路径
        // 优先使用 include 而不是 exclude
        issuer: { test, include, exclude },
        // issuer 的应用范围
        enforce: 'pre',
        enforce: 'post',
        loader: 'babel-loader',
        // 使用 babel-loader 处理符合条件的文件
        // webpack2 中 -loader 不再可以省略
        options: {
          presets: ['es2015']
        }
        // babel-loader 的配置
      },
      {
        test: /\.html$/,
        use: [
          // 应用多个 loader 及相关配置
          'htmllint-loader',
          {
            loader: 'html-loader',
            options: {
              // ...
            }
          }
        ]
      },

      {
         oneOf: [ /* rules */ ],
         // 仅应用 rules 数组中的一条规则
      },

      {
        rules: [ /* rules */ ],
        // 应用所有 rules
      },

      {
        resource: { and: [ /* conditions */ ]}
        // 当所有 conditions 都满足时才认为符合条件
      },

      { resource: { or: [ /* conditions */ ]}, },
      { resource: [ /* conditions */] },
      // 当某一 condition 满足时即可认为符合条件

      {
        resource: { not: /* condition */ }
        // 当某一 condition 不满足时可认为符合条件
      }
    ],
    // rules 结束
  },
  resolve: {
    // 解析模块 request 时的配置
    // (不会应用于解析 loader )
    modules: [
      'node_modules',
      path.resolve(__dirname, 'app')
    ],
    // 解析 modules 时的路径
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 可以省略的扩展名
    alias: {
      // 模块别名
      // 为 import 或 require 创建别名
      // Utilities: path.resolve(__dirname, 'src/utilities')
      // import Utility from '../../utilities/utility' 可以缩写为 
      // import Utility from 'Utilities/utility'
      // 末尾添加$可以进行精确匹配, 类似正则
      // 
      'module': 'new-module',
      // 别名 module 可以表示 new-module, 包括文件与路径, 比如 module/path/file 会被解析为 new-module/path/file
      'only-module$': 'new-module',
      // 别名 only-module(处于尾部) 会被解析成 new-module, 因此 module/path/file 不会被解析
    },
    // aliasFields: string
    // 指定一个 field, 比如 browser, 来执行特定的 alias
    // aliasFields: ['browser']
    // 
    // resolve.descriptionFiles: array
    // 用于描述的 JSON 文件, 默认为
    // descriptionFiles: ['package.json']
    // 
    // resolve.enforceExtension: boolean
    // 如果为 true, webpack 不会处理没有扩展名的文件, 默认为 false, 及允许 require('./foo') 这种写法
    //
    //resolve.enforceModuleExtension: boolean
    //默认为 false, 是否允许 modules(loaders) 省略扩展名
    //
    //resolve.extensions: array, 自动解析指定的扩展名
    //默认为
    // extensions: ['.js', '.json']
    // 因此可以使用 import File from ('../path/to/file') 这种写法
    // 
    // resolve.mainFields: array
    // 当 import 一个包时, 比如 import * as D3 from 'd3', 这个设置决定package.json中的哪个 field 对应的包的会被检索, 默认值基于 target 的值
    // 当 target 指定为 webworker, web 或不指定
    // mainFields: ['browser', 'module', 'main']
    // 对于其他 target, 
    // mainFields ['module', 'main']
    // 例如, D3的 package 包括这些 fields
    // {
    // ...
    // main: 'build/d3.Node.js',
    // browser: 'build/d3.js',
    // module: 'index'
    // ...
    // }
    // 
    // resolve.mainFiles: array
    // 这里的 filename 会在解析路径的时候用到, 默认为
    // mainFiles: ['index']
    // 
    // resolve.modules: array
    // 限定 webpack 搜索与解析的范围
    // 可以使用绝对路径偶相对路径, 但是他们的行为有少许不同.
    // 相对路径的检索方式与 Node 检索 module 相近, 首先检索当前路径及父路径
    // 绝对路径则只会检索给定路径
    // 默认值为 modules: ['node_modules']
    //
    // 如果要添加路径, 应当添加在 node_modules 前面
    // modules: [path.resolve(__dirname, 'src'), 'node_modules']
    // 
    // resolve.unsafeCache: regex | array | boolean
    // 默认为 true, 即允许模块缓存, 即使这种行为是不安全的
    // 
  },

  performance: {
    // 此配置决定 webpack 的通知功能
    // 主要用于对文件尺寸的控制
    hints: 'warning', // enum
    // hints: boolean | 'error' | 'warning'
    // 设定 webpack 何时显示通知
    // 默认为 warning

    maxAssetSize: 200000, // int (in bytes)
    // maxAssetSize: int, 这个选项限制了单个 asset 的尺寸, 默认为250000

    maxEntrypointSize: 400000, // int (in bytes)
    // maxEntrypointSize: int, 限制初始加载时单个 entry 的最大请求数, 默认为250000

    assetFilter: function (assetFilename) {
      return assetFilename.endsWith('.css') || assetFilename.endsWith('.js')
    }
    // assetFilter: function, 这个属性允许 webpack 控制使用哪些文件来计算 performance hint, 默认为
    // function (assertFilename) {
    //   return !(/\.map$/.test(assetFilename))
    // }
    //
    // 可以自己设定 filter function
    // assetFilter: function(assetFilename) {
    //   return assetFilename.endsWith('.js')
    // }
  },

  devtool: 'source-map', // enum, string | false
  // 可以提高 debug 的便利性, 但是会影响打包速度
  // 面向开发有: eval, inline-source-map, eval-source-map, cheap-module-eval-source-map
  // 面向生产有: source-map, hidden-source-map, cheap-source-map, nosources-source-map

  context: __dirname, // 使用绝对路径
  // webpack 的根路径, loaders 的配置文件(.babelrc, postcss.config.js)和 entry 都是基于 context 来解析的
  // 该属性值默认为当前目录, 但是强烈建议书写这一字段, 可以使你的配置文件独立于当前工作目录

  target: 'web', // enum
  // 打包文件运行环境
  // webpack 可以针对多个 environment 或 target 编译
  // target: string, (async-node|electron|electron-renderer|node|node-webkit|web|webworker)

  externals: ['react', /^@angular\//],
  // 不必打包的内容
  // externals: string| RegExp | function | array | object
  // 例如要使用 jquery 的 cdn, 则可以这样配置
  // externals: {
  //   jquery: 'jQuery'
  // }
  // import $ from 'jquery'
  // 
  // externals: {
  //   substract: ['./math', 'substract']
  // }
  // 会形成一个负责结构, ./math 是父模块, 而你的 bundle 仅请求其子模块 substract
  // 
  // externals: {
  //   react: 'react',
  //   lodash: {
  //     commonjs: 'lodash',
  //     amd: 'lodash',
  //     root: '_'
  //   }
  // }
  // 

  watch: boolean
  // webpack 可以监控文件并在他们变化时进行重新编译
  // 默认为 false

  watchOptions: object, 
  // 配置 watch 的模式
  // {
  //   aggregateTimeout: number, 从第一次检测到文件变化到进行重新编译的延迟时间, 这个段时间内的其他变化会被整合进此次编译
  //   ignored: string | RegExp, 忽视某些文件的变化, 减轻 CPU 负担
  //   poll: boolean | number, 每隔1000ms 进行一次检测
  // }

  stats: {
    // 待补充
  },

  devServer: {
    // 这一部分的设置会被 webpack-dev-server 采用
    contentBase: path.resolve(__dirname, 'dist'), // 指定 serve 目录为 dist
    compress: true, // 开启 gzip 压缩
    port: 9000, // 指定端口
    // 当 devServer 启动时, 控制台会打印
    // http://localhost:9000
    // webpack result is served from /build/
    // content is served from dist/
    // 这几行指出了 server 的位置和 served 内容
    // 如果你使用了 Node.js API, 那么这部分会被忽略
    // 
    // devServer.filename: string, 这个选项可以减少 lazy mode 下的编译次数
    // 默认的, 在 lazy mode 下, 每一个请求会进行一次新的编译 
    // 添加 filename 后, devServer 仅在请求 filename 时进行编译, 比如
    // lazy: true,
    // filename: bundle.js
    // 此时, devServer 仅在请求/ bundle.js 时进行编译
    // filename 仅在 lazy mode 中起作用
    // devServer.headers: object, 在所有 request 中添加请求头
    // headers: {
    //   'X-Custom-Foo': 'bar'
    // }
    // 
    // devServer.historyApiFallback: boolean, object
    // 当使用 Html5 History Api 时, index.html 可能得到404响应
    // 
    // devServer.hot: boolean, 开启 webpack 模块热替换
    // 
    // devServer.host - CLI only
    // 指定 host
    // 
    // devServer.https: boolean | object, devServer 默认false, 即使用 http
    // 
    // devServer.inline - CLI only
    // 切换 devServer 的模式, 默认使用 inline 模式, 即脚本会直接注入到你的 bundle 以实现重载, 设置 devServer.inline: false 可以启用 iframe 模式
    // 
    // 
  },

  plugins: [
    // 待补充
  ]
}

Webpack 2 起步

虽然和 webpack1 一样, 还是过一遍

创建一个 bundle

mkdir webpack-demo &amp;&amp; webpack-demo

npm init -y

yarn add --dev webpack@beta

yarn add lodash
`</pre>

#### 创建 app/index.js

<pre>`// app/index.js
import _ from 'lodash'
function component () {
  let element = document.createElement('div');

// use lodash
  element.innerHtml = _.join(['Hello', 'webpack'], ' ')

  return element
}

document.body.appendChild(component())
`</pre>

#### 创建 index.html

<pre>`&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;webpack 2 demo&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;script src="public/bundle.js"&gt;&gt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
`</pre>

#### 创建打包后文件

<pre>`./node_modules/.bin/webpack app/index.js public/bundle.js
`</pre>

webpack 会自动处理包的依赖关系

### 添加配置文件

<pre>`var path = require('path')

module.exports = {
  entry: './app/index.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js'
  }
}
`</pre>

通过 webpack 命令行进行打包

<pre>`webpack --config webpack.config.js
`</pre>

`webpack` 会默认执行名为 `webpack.config.js` 的配置文件, 因此仅输入`webpack` 即可进行打包

### 通过 yarn 执行打包

在 `yarn init` 生成的 `package.json` 中添加

<pre>`{
  ...
  "scripts": {
    "build": "webpack"
  }
  ...
}

即可将webpack指令添加进 yarn 的快捷方式, 此时执行 yarn build 即可打包

Stacking Context 成因及特性

会形成Stacking Context 的元素

  • root 元素( html )

  • position 不是 static 且 z-index 不是 auto 的元素

  • flex item 且 z-index 不是 auto 的元素

  • opacity 小于 1 的元素

  • transform 不是 none 的元素

  • mix-blend-mode 不是 normal 的元素

  • filter 不是 none 的元素

  • isolation 为 isolate 的元素

  • mobile webkit 和 chrome 22+ 上的 fixed 元素

  • 在 will-change 属性上指定值为上述任意属性的元素

  • 指定 -webkit-overflow-scrolling 为 touch 的元素

Stacking Context 的特性

  • stacking context 可以嵌套

  • 每个 Stacking context 相对于兄弟元素都是完全独立的, 其内部规则不会影响到外部

  • 每个 stacking context 元素都会被父级 stacking context 视为一个 stacking 规则

为已经定位的元素( absolute 或 relative) 指定 z-index 可以改变其 parent stacking context 中 z 的偏移量

一个值得注意的坑: 如果一个元素不是通过absolute 或 relative 实现 stacking context, 那么他的 z-index 为 0 , 仅高于 auto, 且无法改变.

Web 坐标整理

坐标分类

  1. 文档坐标

  2. 窗口坐标

scroll

Element.scrollTop: 设置或获得一个元素距离他容器顶部的像素距离, 当一个元素的容器没有产生垂直方向的滚动条, 则他的 scrollTop 的值默认为0

获得滚动距离
var intElementScrollTop = someElement.scrollTop;

scrollTop可以被设置为任何整数, 但是一下情况会报错:
1. 如果一个元素不能被滚动(没有溢出容器或本身不可滚动)的时候设置其 scrollTop 为 0;

  1. 设置 scrollTop 的值小于 0, scrollTop 被设为 0

  2. 如果设置了超出这个容器可以滚动的值, scrollTop 会被设为最大值

其实就是溢出距离

Element.scrollHeight: 计量元素内容高度的只读属性, 包括 overflow 样式属性导致的视图中不可见内容

没有垂直滚动条的情况下, scrollHeight 值与元素视图填充所有内容所需要的最小值 clientHeight 相同, 包括元素的 padding, 但不包括 margin

其实就是溢出元素的本身高度

!()[https://developer.mozilla.org/@api/deki/files/840/=ScrollHeight.png]

判断元素是否滚动到底部

element.scrollHeight - element.scrollTop === element.clientHeight

clientTop

Element.clientTop: 一个元素顶部边框的宽度, 不包括顶部外边距或内边距, 只读属性

clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度 来计算

其实就是可视区域高度

mouseEvent.clientX: 只读属性, 事件发生时应用客户端区域的水平坐标, 与页面无关

mouseEvent.x 是 mouseEvent.clientX 的别名

window

window.innerHeight: 浏览器窗口的视口( viewport ) 高度, 如果存在滚动条, 则要包括在内, 只读属性

window.outerHeight: 整个浏览器窗口的高度

offset

mouseEvent.offsetX: read-only property provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node

page

event.PageX: read-only property returns the horizontal coordinate of the event relative to the whole document

文档坐标

screen

mouseEvent.screenX: read-only property provides the horizontal coordinate of the mouse pointer in the global(screen) coordinate.

Simple CheetSheet of Flex

Flex

Parent
  1. display: flex

  2. flex-direction: row | row-reverse | column | column-reverse

  3. flex-wrap: nowrap | wrap | wrap-reverse

  4. flex-flow: <flex-direction> || <flex-wrap>

  5. justify-content: flex-start | flex-end | center | space-between | space-around

  6. align-items: flex-start | flex-end | center | stretch | baseline (single row)

  7. align-content: flex-start | flex-end | center | stretch | space-between | space-around (multi-row)

Children(flex-item)
  1. order: <integer>

  2. flex-grow: <number>(0 default)

  3. flex-shrink: <number>(0 default)

  4. flex-basis: <length> | auto (auto default)

  1. flex: none || <flex-grow><flex-shrink><flex-basis>

  2. align-self: auto | flex-start | flex-end | center | stretch | baseline

notice: float, clear, vertical-align are unavailable in flex item

Usage of Redux-Action

Usage

createAction(type, payloadCreator = Identity, ?metaCreator)

import { createAction } from 'redux-action'
`</pre>

Wraps an action creator so that its return value is the payload of a Flux Standard Action. If no payload creator is passed, or if it's not a function, the identity function is used.

### Example

<pre>`let increment = createAction('INCREMENT', amount =&gt; amount);
// same as 
increment = createAction('INCREMENT');

expect(increment(42)).to.deep.equal({
  type:'INCREMENT',
  payload: 42,
});
`</pre>

If the payload is an instance of an Error Object, redux-actions will automatically set `action.error` to true.

### Example

<pre>`const increment = createAction('INCREMENT');

const error = new TypeError('not a number');
expect(increment(error)).to.deep.equal({
  type: 'INCREMENT',
  payload: error,
  error: true,
})
`</pre>

`createAction` also return its `type` when used as type in `handleAction` or `handleActions`

### Example

<pre>`const increment = createAction('INCREMENT')

// as parameter in handleAction:

handleAction(increment, {
  next(state, action){...},
  throw(state, action) {...},
});

const reducer = handleActions({
  [increment]: (state, action) =&gt; ({
    counter: state.counter + action.payload
  })
})
`</pre>

### createActions(?actionsMap, ?...identityActions)

<pre>`import { createActions } from from 'redux-actions'
`</pre>

Returns an object mapping action types to action creator. The keys of this object are camel-cased from the keys in `actionsMap` and the string literals of `identityActions` the values are the action creators.

### combineActions(...actionTypes)

Combine any number of action types or action creators, `actionTypes` is a list of positional arguments which can be action type strings, symbols, or action creators.

This allows you to reduce multiple distinct actions with the same reducers.

<pre>`const { increment, decrement } = createActions({
  INCREMENT: amount =&gt; ({amount}),
  DECREMENT: amount =&gt; ({amount: -amount}),
}) 

const reducer = handleAction(combineActions(increment, decrement), {
  next: (state, { payload: { amount }}) =&gt; ({ ...state, counter: state.counter + amount}),
  throw: state =&gt; ({ ...state, counter: 0}),
}, { counter: 10 })

expect(reducer(undefined, increment(1)).to.deep.equal({ counter: 11 }))
expect(reducer(undefined, decrement(1)).to.deep.equal({ counter: 9 }))
expect(reducer(undefined, increment(new Error)).to.deep.equal({ counter: 0 }))
expect(reducer(undefined, decrement(new Error)).to.deep.equal({ counter: 0 }))

Flux Standard Action

Example:

  1. A basic Flux Standard Action

    {
    type: ‘ADD_TODO’,
    payload: {

    text: 'Do something',
    

    }
    }
    `

    An FSA that represents an error, analogous to a rejected Promise:

    `{
    type: ‘ADD_TODO’,
    payload: new Error(),
    error: true,
    }

Notice:
1. An action MUST

  • be a plain JavaScript Object
  • have a type property
  1. An action MAY:
  • have a error property
  • have a payload property
  • have a meta property
  1. An action MUST NOT include properties other than type, payload, error, meta

Type

The type of an action identifies to the consumer the nature of the action that has occurred. Two actions with same type MUST be strictly equivalent (using ===). By convention, type is usually string constant or a Symbol.

payload

The optional payload property MAY be any type of value. It represents the payload of the action. Any information about the action that is not the type or status of the action should be part of the payload filed.

By convention, if error is true, the payload SHOULD be an error object. This is akin to rejecting a promise with an error object.

error

The optional error property MAY be set to true if the action represents an error.

An action whose error is true is analogous to a rejected Promise. By convention the payload SHOULD be an error object.

If error has any other value besides true, including undefined and null, the action MUST NOT be interpreted as an error.

meta

The optional meta property MAY be any type of value. It is intended for any extra information that is not part of the payload.

Promise 基本点

Promise 基本用法

创建 Promise

var promise = new Promise(function(resolve, reject){
  // do something async
  if(/* everything ok */){
    resolve("OK"); // it will return Promise.resolve("OK"), and the downstream will get "OK" as params
  } else {
    reject(Error("Error")); // it will return Error("Error") and the downstream will get Error("Error") as params
  }
})
`</pre>

Promise 构造器接受一个函数作为参数, 传入两个回调函数resolve 和 reject, 在这个函数参数中做一些一步操作, 成功后调用 resolve 将 Promise 对象的状态设置为 resolved 并返回一个 Promise 对象用于链式调用, 失败后调用 reject 将 Promise 对象的状态设置为 rejected 并返回一个 Promise 对象用于链式调用

使用时:

<pre>`promise.then(function(result){}).catch(function(err){})

Promise API Reference

静态方法:

  • Promise.resolve(promise), 返回一个 Promise

  • Promise.resolve(thenable), 从 thenable 对象创建一个新的 Promise, 一个 thenable (类 Promise) 对象是一个带有” then” 方法的对象.

  • Promise.resolve(obj), 创建一个以 obj 为肯定结果的 Promise

  • Promise.reject(obj), 创建一个以 obj 为否定结果的 Promise, 为了一致性和调试方便, obj 应该是一个 Error 实例对象.

  • Promise.all(array), 创建一个 Promise, 当且仅当数组中的所有 Promise 都 resolved 之后才设为 resolved, 若其中存在 rejected, 则设置状态为rejected

  • Promise.race(array), 创建一个 Promise, 当数组首先出现 resovled 或 rejected 的时候设置为同状态

JavaScript 事件与性能

事件处理程序本质上是一种函数, 是一种对象, 存放在内存中, 设置大量的事件处理程序会使内存中的对象变多, Web 程序的性能会变得越来越差.

为了更好利用事件处理程序, 出现了事件委托, 用来提升性能

事件委托

Event Delegate: 把若干个子节点上的相同事件的处理函数绑定到父节点, 从父节点统一处理子节点冒泡上来的事件, 这种技术叫做事件委托.

举例:

&lt;ul id='parent-list'&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
&lt;/ul&gt;
`</pre>

通过父节点监听冒泡事件可以处理子节点上的对应事件

<pre>`var parentList = document.getElementById('parent-list');
parentList.addEventListener('click', function(){
  var target = event.target; // li
  if(target.nodeName.toLowerCase() === 'li'){
    alert(target.firstChild.nodeValue);
  }
}, false)

因为事件委托依赖事件冒泡机制, 所以并不是所有事件都可以委托

最适合的事件: click, mousedown, mouseup, keydown, keyup, keypress

移除事件处理程序

每当将事件处理程序制定给元素时, 运行中的浏览器代码与支持页面交互的 Js 代码之间就会建立一个连接, 这种连接越多, 页面执行越慢

如果使用完后不再使用的事件处理程序, 应当释放掉
`


var button = document.getElementById(‘button’);
button.onclick = function(){
button.onclick = null;
event.target.firstChild.nodeValue = “Submitting”
}

JavaScript 停止冒泡和阻止浏览器默认行为

事件兼容

function myFn(e){
  var evt = e ? e:window.event
}
`</pre>

### JS 停止冒泡

<pre>`function myFn(e){
  window.event ? window.event.cancelBubble = true : e.stopPropagation();
}
`</pre>

### JS 阻止默认行为

<pre>`function myFn(e){
  window.event ? window.event.returnValue = false : e.preventDefault();
}

补充

event 表示事件的状态, 例如, event.target 是触发事件的对象

Koa-Router

  • Express 式路由使用app.get, app.put, app.post
  • Named URL parameters and regexp captures
  • String or regular expression route matching
  • Named Routes with URL generation
  • Responds to OPTIONS request with allowed methods
  • Support for 405 Method Not Allowed and 501 Not Implemented
  • Multiple route middleware
  • Multiple routers

安装

npm install koa-router
`</pre>

### 使用

<pre>`var koa = require("koa"),
    router = require("koa-router"),
    app = koa();

app.use(router(app))
`</pre>

After the router has been initialized you can register routes:

<pre>`app.get("/users/:id", function*(next){
  var user = yield User.findOne(this.params.id);
  this.body = user;
});
`</pre>

### 多路由

You can use multiple routers and sets of routes by omitting the `app` argument. For example, separate routes for two versions of an API:

<pre>`var koa = require("koa"),
    mount = require("mount"),
    Router = require("koa-router");

var app = koa();

var APIv1 = new Router();
var APIv2 = new Router();

APIv1.get("/sign-in", function*(){
  // ...
})
APIv2.get("/sign-in", function*(){
  // ...
})

app.use(mount("/v1", APIv1.middleware())).use(mount("/v2", APIv2.middleware()));
`</pre>

### 链

The http methods(get, post, etc) return their `Router` instance, so routes can be chained as you're used to with express:

<pre>`var api = new Router();

api.get("/foo", showFoo).get("/bar", showBar).post("/foo", createFoo)

编写 Koa 中间件

Koa middlewares are simple function which return a GeneratorFunction, and accept another (middleware). When the middleware is run by an “upstream” middleware, it must manually yield to the “downstream” middleware.

For example if you wanted to track how long it takes for a request to propagate through Koa by adding an x-Response-Time header field the middleware would look like the following:

function *responseTime(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set("X-Response-Time", ms+"ms");
}
app.use(responseTime);
`</pre>

Here is another way to write the same thing, inline:

<pre>`app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set("X-Response-Time", ms+"ms");
});
`</pre>

### 中间件最佳实践

Imcluding Middleware accepting options, named middleware for debugging, among others

#### 中间件选项

When creating public middleware it's useful to conform to the convention of wrapping the middleware in a function that accepts options, allowing users to extend functionality. Even if your middleware accepts no options, this is still a good idea to keep things uniform.

Here our contrived `logger` middleware accepts a `format` string for customization, and returns the middleware itself:

<pre>`function logger(format){
  format = format || ":method\":url\"";
  return function*(next){
    var str = format.replace(":method", this.method).replace(":url", this.url);
    console.log(str);
    yield next;
  }
}
`</pre>

app.use(logger());
app.use(logger(":method:url"))

<pre>`
### 命名中间件
Naming middlewares is optional, however it's useful for debugging purpose to assign a name
`</pre>

function logger(format){
  return function *logger(next){
    // ...
  }
}

<pre>`### 结合多个中间件
Sometimes you want to "compose" multiple middleware into a single middleware for easy re-use or exporting. To do so, you may chain them together with `.call(this, next)`, then return another function that yields the chain

function _random(next) {
if (‘/random’ == this.path) {
this.body = Math.floor(Math.random()_10);
} else {
yield next;
}
};

function *backwards(next) {
if (‘/backwards’ == this.path) {
this.body = ‘sdrawkcab’;
} else {
yield next;
}
}

function *pi(next) {
if (‘/pi’ == this.path) {
this.body = String(Math.PI);
} else {
yield next;
}
}

function *all(next) {
yield random.call(this, backwards.call(this, pi.call(this, next)));
}

app.use(all);
`

Koa 使用

安装

Koa 当前需要 node 0.11.x 并开启 –harmony(或– harmony-generators), 因为他依赖于 ES6的 generator 特性
node --harmony my-koa-app.js

应用

Koa 应用是一个包含中间件 generator 方法数组的对象. 当请求到来时, 这些方法会以 stack-slice 的顺序执行.

Koa 的一大设计理念是, 通过其他底层中间件层提供高级”语法糖”, 而不是 Koa, 大大提高了框架的互操作性和健壮性, 并让中间件开发变得有趣.

简单例子

var koa = require("koa");
var app = koa();
app.use(function*(){
  this.body = "Hello World";
});
app.listen(3000);
`</pre>

**注**: 与普通 function 不同, generator function 以 function* 的形式声明.

### 编写级联代码

koa 中间件以一种更加传统的方式级联.

以往的 Node 开发中, 级联是通过回调实现的, 用户友好度上有欠缺

Koa 借助 generator 实现了中间件架构

Koa 的执行代码的形式不再是简单的将控制权依次移交给一个又一个方法直到结束, 而是像一个回形针, 用户请求中间件, 遇到 yield next 关键字的时候, 会被传递给下游中间件(downstream), 在 yield next 捕获不到 downstream 的时候, 逆序返回执行代码(upstream)(yield next 后面的代码)

<pre>`var koa = require("koa");
var app = koa();

// x-response-time

app.use(function* (next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set("X-Response-Time", ms+"ms");
})

// logger
app.use(function* (next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log("%s %s - %s", this.method, this.url, ms)
});

// response
app.use(function* (){
  this.body = "Hello World";
});

app.listen(3000);
`</pre>

### 应用配置

应用配置是 app 实例的属性, 目前支持以下配置:
- app.name 应用名
- app.env 执行环境, 默认是`NODE_ENV`或者`development`字符串
- app.proxy, 当该属性为 true 时, `proxy header` 参数会被添加到信任列表中

### app.listen(...)

一个 Koa 应用跟 HTTP Server 不是1-to-1关系, 一个或多个 Koa应用可以被加载到一块, 组成一个更大的包含一个 HTTP server 的应用

该方法创建并返回一个 http server, 并支持传递固定参数

<pre>`var koa = require("koa")
var app = koa()
app.listen(3000)
`</pre>

方法 app.listen(...)是一个语法糖, 等价于

<pre>`var http = require("http")
var koa = require("koa")
var app = koa()
http.createServer(app.callback()).listen(3000)
`</pre>

这意味着可以在多个端口使用同一个 app

<pre>`http.createServer(app.callback()).listen(3000)
http.createServer(app.callback()).listen(3001)
`</pre>

### app.callback()

返回一个回调方法能用于 http.createServer()来处理请求, 也可以将这个回调函数挂载到 Connect/Express 应用上

### app.use(function)

将给定的 function 当做中间件加载到应用中

### app.keys=

设置 Cookie 签名密钥

### 错误处理

除非应用执行环境(NODE_ENV)被配置为"test", Koa 都会将所有错误信息输出到 stderr, 如果想自定义错误处理逻辑, 可以定义一个"错误事件"来坚挺 Koa App 中的错误:

<pre>`app.on("error",function(err){
  log.error("server error", err);
})
`</pre>

当 req/res 周期中出现任何错误且无法响应客户端时, koa 会把 Context(上下文)实例作为第二个参数传递给 error 事件:

<pre>`app.on("error", function(err, ctx){
  log.error("server error", err, ctx)
})
`</pre>

### 上下文

Koa 的 Context 把 node 的 request 和 response 对象封装在一个单独对象, 并提供许多开发 web 应用和 APIs 有用的方法. 那些 HTTP Server 开发中使用非常频繁操作, 直接在 Koa 里实现, 而不是放在更高层次的框架, 这样中间件就不需要重复实现这些通用的功能.

每个请求会创建自己的 Context 实例, 在中间件中作为 receiver 引用, 或通过 this 标示引用:

<pre>`app.use(function* (){
  this; // is the Context
  this.request; // is a koa Request
  this.response; // is a koa Response
})

Context 的许多访问器和方法直接委托为他们的 ctx.request 或 ctx.response 的等价方法, 用于访问方便, 是完全相同的, 比如 ctx.type 和 ctx.length 委托与 response 对象, ctx.path 和 ctx.method 委托与 request.

请求

Koa Request 对象是 node 普通 request 对象之上的抽象, 提供了日常 Http Server 中更多有用的功能

API

  • request.header
  • request.headers: req.header 的别名
  • request.method: 请求方法
  • request.method=: 设置请求方法, 实现中间件的时候非常有用
  • request.length: 将请求的 Content-length 返回为数字, 或 undefined
  • request.url: 获取请求 URL
  • request.url=: 设置请求 URL

响应

Koa Response 对象是 node 普通 response 对象之上的抽象, 提供了日常 Http Server 中有用的功能

API

….

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×