Ashley’s Blog

A blogging framework for Ashley.

Redux

Redux 三大原则

  • 单一数据源。 整个应用的 state 被储存在一棵 object tree 中,它只有一个单一的 store 。
  • State 是只读的。 惟一改变 state 的办法就是触发 action,action 是一个描述要发生什么的对象。
  • 纯函数的形式来执行修改。 为了描述 action 如何改变 state tree ,你需要编写 reducers。

同 Flux 的关系

Redux 没有 dispatcher 的概念,它依赖纯函数来替代事件处理器。Redux 设想你永远不会变动你的数据。 细节有所不同:分别体现在 reducer 和 state 上。

Action

Actions 是把数据从应用传到 store 的有效载荷。它是 store 数据的惟一来源。 Actions 表明有事情发生了,包含了更新 state 所需要的足够多的信息。 Action 本质是 JavaScript 普通对象。

1
2
3
4
5
6
7
8
{
      type: 'ADD_TODO',
      text: 'Build my first Redux app'
}
{
    type: 'DELETE_TODO',
    index: 5
}

Action 创建函数

1
2
3
4
5
6
function addTodo(text) {
  return {
     type: ADD_TODO,
     text
   };
}

Reducer

reducer 就是一个函数,接收旧的 state 和 action,返回新的 state。

1
(previousState, action) => newState

不要修改 state。

在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。

1
2
3
4
5
6
7
8
9
10
{
  visibilityFilter: 'SHOW_ALL',
  todos: [{
    text: 'Consider using Redux',
    completed: true,
  }, {
    text: 'Keep all state in a single tree',
    completed: false
  }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function todoApp(state = initialState, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return Object.assign({}, state, {
      visibilityFilter: action.filter
    });
  case ADD_TODO:
    return Object.assign({}, state, {
      todos: [...state.todos, {
        text: action.text,
        completed: false
      }]
    });
  case COMPLETE_TODO:
    return Object.assign({}, state, {
      todos: [
        ...state.todos.slice(0, action.index),
        Object.assign({}, state.todos[action.index], {
          completed: true
        }),
        ...state.todos.slice(action.index + 1)
      ]
    });
  default:
    return state;
  }
}

combineReducers

一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  };
}

function todos(state = [], action) {
  switch (action.type) {
  case ADD_TODO:
    return [...state, {
      text: action.text,
      completed: false
    }];
  case COMPLETE_TODO:
    return [
      ...state.slice(0, action.index),
      Object.assign({}, state[action.index], {
        completed: true
      }),
      ...state.slice(action.index + 1)    
    ];
  default:
    return state;
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return action.filter;
  default:
    return state;
  }
}

最后,Redux 提供了 combineReducers() 工具类来做上面 todoApp 做的事情,这样就能消灭一些样板代码了

1
2
3
4
5
6
7
8
import { combineReducers } from 'redux';

const todoApp = combineReducers({
  visibilityFilter,
  todos
});

export default todoApp;

Store

Store 就是把action and reducers联系到一起的对象。 Store 有以下职责:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createStore } from 'redux';
import todoApp from './reducers';

let store = createStore(todoApp);

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

// 发起一系列 action
store.dispatch(addTodo('Learn about actions'));
store.dispatch(addTodo('Learn about reducers'));
store.dispatch(addTodo('Learn about store'));
store.dispatch(completeTodo(0));
store.dispatch(completeTodo(1));
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED));

// 停止监听 state 更新
unsubscribe();

数据流

  • 调用 store.dispatch(action)。
  • Redux store 调用传入的 reducer 函数。
  • 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  • Redux store 保存了根 reducer 返回的完整 state 树。

With React

Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。 Redux 还是和 React 和 Deku 这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。

1
npm install --save react-redux

只在最顶层组件里使用 Redux。

内部组件应该像木偶一样保持“呆滞”,所有数据都通过 props 传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import todoApp from './reducers'

let store = createStore(todoApp)

let rootElement = document.getElementById('root')
render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class App extends Component {
  render() {
    // Injected by connect() call:
    const { dispatch, visibleTodos, visibilityFilter } = this.props;
    return (
      <div>
        <AddTodo
          onAddClick={text =>
            dispatch(addTodo(text))
          } />
        <TodoList
          todos={this.props.visibleTodos}
          onTodoClick={index =>
            dispatch(completeTodo(index))
          } />
        <Footer
          filter={visibilityFilter}
          onFilterChange={nextFilter =>
            dispatch(setVisibilityFilter(nextFilter))
          } />
      </div>
    );
  }
}

// Which props do we want to inject, given the global state?
function select(state) {
  return {
    visibleTodos: selectTodos(state.todos, state.visibilityFilter),
    visibilityFilter: state.visibilityFilter
  };
}

// Wrap the component to inject dispatch and state into it
export default connect(select)(App);

异步action 及 middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                                                                                  ┌──────────────┐
                    ┌─────────────┐                                           ┌──▶│ subReducer 1 │
               ┌───▶│Middleware 1 │                                           │   └──────────────┘
               │    └─────────────┘                                           │           │       
               │           │                                                  │           ▼       
┌─────────────┐    │           │              ┌───────────────┐    ┌──────────┐   │   ┌──────────────┐
│   action'   │────┘           ▼          ┌──▶│store.dispatch │───▶│ reducer  │───┘   │ subReducer m │
└─────────────┘         ┌─────────────┐   │   └───────────────┘    └──────────┘       └──────────────┘
                    │Middleware n │   │                                                   │       
                    └─────────────┘   │                                                   │       
                           │          │                                                   ▼       
                           │          │                                           ┌──────────────┐
                           └──────────┘                                           │    state     │
                           plain action                                           └──────────────┘

通过使用指定的 middleware,action creator 除了返回 action 对象外还可以返回函数。这时,这个 action creator 就成为了 thunk。

当 action creator 返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { createStore, applyMiddleware } from 'redux';
import { selectReddit, fetchPosts } from './actions';
import rootReducer from './reducers';

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware, // 允许我们 dispatch() 函数
  loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志
)(createStore);

const store = createStoreWithMiddleware(rootReducer);

store.dispatch(selectReddit('reactjs'));
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export function fetchPosts(reddit) {

  // Thunk middleware 知道如何处理函数。
  // 这里把 dispatch 方法通过参数的形式参给函数,
  // 以此来让它自己也能 dispatch action。

  return function (dispatch) {

    // 首次 dispatch:更新应用的 state 来通知
    // API 请求发起了。

    dispatch(requestPosts(reddit));

    // thunk middleware 调用的函数可以有返回值,
    // 它会被当作 dispatch 方法的返回值传递。

    // 这个案例中,我们返回一个等待处理的 promise。
    // 这并不是 redux middleware 所必须的,但是我们的一个约定。

    return fetch(`http://www.reddit.com/r/${reddit}.json`)
      .then(response => response.json())
      .then(json =>

        // 可以多次 dispatch!
        // 这里,使用 API 请求结果来更新应用的 state。

        dispatch(receivePosts(reddit, json))
      );

      // 在实际应用中,还需要
      // 捕获网络请求的异常。
  };
}

redux-thunk 支持异步的 middleware 包装了 store 的 dispatch() 方法,以此来让你 dispatch 一些除了 action 以外的其他内容,如函数。你所使用的任何 middleware 都可以以自己的方式解释你 dispatch 的任何内容,并继续传递 actions 给下一个 middleware。

1
2
3
4
5
6
7
8
9
10
function thunkMiddleware(_ref) {
  var dispatch = _ref.dispatch;
  var getState = _ref.getState;

  return function (next) {
    return function (action) {
      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    };
  };
}

Web Socket

Client

var a = new WebSocket("ws://localhost:8080/abcd");
a.onmessage = function(event){
    var data = event.data;
    console.log(event);
    console.log(data);
};
a.onopen = function(){
    console.log('open');
}
a.onclose = function(){
    console.log('close');
}
//a.send("data");
//a.close();

Server

Use WS

I didn’t use Socket.io

1
2
3
4
5
6
7
8
9
10
11
12
13
var WebSocketServer = require('ws').Server
wss = new WebSocketServer({ port: 8080, path:'/abcd' });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
console.log('received: %s', message);
ws.send('I received' + message + 'is that you sended?')
ws.send('connection will close in 8s');
setTimeout(function(){ws.close()},8000)
  });

  ws.send('connection works fine');
});

Web Components

HTML Templates
1
2
3
4
5
6
7
8
9
<template id="radiogroup">
  <input type="radio" name=""> <label></label>
  <input type="radio" name=""> <label></label>
  <style>
  label{
    color:red;
    }
  </style>
</template>
Custom Elements and Shadow DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var template = document.querySelector('#radiogroup');
var appInfoProto = Object.create(HTMLElement.prototype);
appInfoProto.createdCallback = function() {
    var root = this.createShadowRoot();
    // template.content
    var clone = template.content.cloneNode(true);
    var myInputNodeList = clone.querySelectorAll('input');
    for (var i = 0; i < myInputNodeList.length; ++i) {
      var item = myInputNodeList.item(i);
      item.setAttribute('id',this.getAttribute('data'+i));
      item.setAttribute('name',this.getAttribute('name'));
    }
    var myLabelNodeList = clone.querySelectorAll('label');
    for (var i = 0; i < myLabelNodeList.length; ++i) {
      var item = myLabelNodeList.item(i);
      item.setAttribute('for',this.getAttribute('data'+i));
      item.innerHTML = this.getAttribute('message'+i)
    }
    root.appendChild(clone);

}
document.registerElement('my-radiogroup', {
    prototype: appInfoProto
});
Use Custom Question in your HTML
1
2
3
4
5
<my-radiogroup id="group" data0="id0" message0="player0" data1="id1" message1="player1" name="basketball"></my-radiogroup>
<label for=""> I am innocence</label>
<my-radiogroup id="group" data0="leon" message0="Messi" data1="me" message1="Ashley" name="football"></my-radiogroup>

<my-radiogroup id="group" data0="A" message0="A" data1="B" message1="B" data2="C" message2="C" data3="D" message3="D"name="Charctor"></my-radiogroup>

Talk About Git

学习 Git 的第一条规则就是,你不能谈及自己在学习 Git1。因为这个工具由 Linus Torvalds 主导的 Linux 内核社区设计开发,并且由最酷最炫的 GitHub 发扬光大,从头到脚围绕着高大上的光环,全世界聪明的程序员都会用(至少声称自己会用)它来管理自己的代码。如果你在跟人聊天时不经意透露了自己「正在学习使用 Git」,无疑所有人都会投来高贵冷艳充满优越感的目光,并且告诉你:「连我们的设计师都会用 Git 管理他的 PSD 了,你竟然才在学怎么用?」所以,永远不要谈及自己在学习它——是的,你已经熟练地掌握了使用 Git 的所有要领。

学习 Git 的第二条规则是,你不能觉得它概念难懂,命令太多。因为全世界聪明的程序员都在公共场合大谈自己是如何在行驶的飞机上单枪匹马搞定分支合并的冲突,并且一下飞机就风驰电掣地连上机场 Wi-Fi,成功 push 了代码,发起了 pull request。所以,你一定要在心里默念,我是全世界聪明的程序员之一,Git 这点东西显然不在我的话下,少焉,流淌于键盘之上,玩转于股掌之间2。

From git101

Some Standard Events

blur

FocusEvent
DOM L3
An element has lost focus (does not bubble).

change

Event
DOM L2, HTML5
An element loses focus and its value changed since gaining focus.

click

MouseEvent
DOM L3
A pointing device button has been pressed and released on an element.

input

Event
HTML5
The value of an element changes or the content of an element with the attribute contenteditable is modified. IE9 support

select

UIEvent DOM L3
Some text is being selected.

No business with input select element

Do Not Change Backbone Object’s Cid

Yesterday when I debugged some old JS code, I found an interesting thing that some event’s callback function ran two times.

1 JQuery Event

on

1
2
3
4
5
6
7
jQuery.fn.extend({

  on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
          ...... //ignore some code
          jQuery.event.add( this, types, fn, data, selector )
      }
  })

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add: function( elem, types, handler, data, selector ) {
      elemData = data_priv.get( elem );//get the cache of the element
      eventHandle = elemData.handle = function( e ) {
          jQuery.event.dispatch.apply( elem, arguments )
      }
      elem.addEventListener( type, eventHandle, false );//when event trigger, always run eventHandle/dispatch, do not run handler directly
      elemData.events[ type ] = jQuery.extend({
          type: type,
          origType: origType,
          data: data,
          handler: handler,
          guid: handler.guid,
          selector: selector,
          needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
          namespace: namespaces.join(".")
      }, handleObjIn ); //the true callback stored in the cache, and it must be runned by the dispatch function 
  }

dispatch

1
2
3
4
5
6
7
8
dispatch: function( event ) {
  // Make a writable jQuery.Event from the native event object
  event = jQuery.event.fix( event );
  handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] ;//find the right type event from the cache
  // Determine handlers
  handlerQueue = jQuery.event.handlers.call( this, event, handlers );
  handleObj.handler.apply( matched.elem, args );
  

2 Backbone Event delegate

we can see that backbone change the origin event name that we write in events like

1
2
3
events:{
  'click #saveButton1': 'saveForm',
}

3 The truth

someone change the view’s cid privately after initialize:

1
this.cid = model.cid;

So when the view delegate Events, the undelegateEvents doesn’t work as purposes. The events who have the old event name could not be found and be off. after delegate, some events have a copy of themselves.

Thus, they run two times.

CSS Grid Layout

CSS Grid Layout

Two-dimensional grid-based layout system aligns elements into columns and rows.

Basic definition

1
2
3
4
5
6
7
8
9
.wrapper {
  display: grid;
  grid-template-columns: 100px 60px 100px 60px 100px;
  grid-template-rows: 50px 70px;
}

.b, .d, .f, .h, .j {
  background-color: red;
}

Grid Area

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.a{
  grid-column-start: 1;
  grid-column-end: 2; 
  grid-row-start: 1;
  grid-row-end: 2;
}
.a{
  grid-column: 1 / 2; 
  grid-row: 1 / 2;
}
.a{  
  //notice the sequence is row-start/column-start/row-end/column-end
  grid-area: 1 / 1 / 2 / 2;
}
So
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.wrapper {
  display: grid;
  grid-template-columns: 100px 10px 100px 10px 100px 10px 100px;
  grid-template-rows: auto 10px auto 10px auto;
}
.a{
  grid-area: 1 / 1 / 2 / 2;
}
.b {
  grid-area: 1 / 3 / 2 /4;
}
.c { 
  grid-area: 1 / 5 / 2 / 6;
}
.d { 
  grid-area: 1 / 7 / 2 / 8;
}
.e { 
  grid-area: 3 / 1 / 4 / 2;
}
.f {
  grid-area: 3 / 3 / 4 / 4;
}
.g {
  grid-area: 3 / 5 / 4 / 6;
}
.h {
  grid-area: 3 / 7 / 4 / 8;
}
.i {
  grid-area: 5 / 1 / 6 / 2;
}
.j {
  grid-area: 5 / 3 / 6 / 4;
}
looks like this

Repeat and Span

1
2
3
//repeat function
grid-template-columns:repeat(6, (col) 100px (gutter) 10px); 
grid-template-rows: repeat(4, (row) auto (gutter) 10px );
1
2
3
//span merge lines
grid-column: col / span gutter 2; 
grid-row: row;

Grid Template Area

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.sidebar {
    grid-area: sidebar;
}
.sidebar2 {
    grid-area: sidebar2;
}
.content {
    grid-area: content;
}
.header {
    grid-area: header;
}
.footer {
    grid-area: footer;
}

@media only screen and (min-width: 400px) and (max-width: 540px)  {
    .wrapper {
        display: grid;
        grid-template-columns: 20% 5% auto;
        grid-template-rows: auto;
        grid-template-areas: 
      "header header header" 
         "sidebar . content"
         "sidebar2 sidebar2 sidebar2"
         "footer footer footer";
    }
}

@media only screen and (min-width: 540px) {
    .wrapper {
        display: grid;
        grid-template-columns: 100px 20px auto 20px 100px;
        grid-template-rows: auto;
        grid-template-areas: 
      "header header header header header" 
         "sidebar . content . sidebar2"
         "footer footer footer footer footer";
            max-width: 600px;
    }
}

Backbone View in View

大 view 里面需要包含一些小view 绑定事件遇到了些问题,不知道能否在小 view inital 或者 render 的时候就能绑定大 view 的事件, 现在的做法是

1
2
3
4
//In BigView
new smallview();
smallview.bind('',bigview.function1);
smallview.bind('',bigview.function2);

每次 new smallview 的时候都要写绑定事件

目前想在 smallview initial 或者 render 的时候进行自动绑定 能否在不把 BigView 传入smallView的条件下 实现这样的绑定呢? 不喜欢这样的写法 感觉互相引用总是怪怪的 bigview.a = smallview smallview.b = bigview

Promise Part 1

Read from Here

function Promise(fn) {
    var value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        deferreds.push(onFulfilled);
    };

    function resolve(value) {
        deferreds.forEach(function (deferred) {
            deferred(value);
        });
    }

    fn(resolve);
}

Promise 做了以下几件事情

  1. 定义了通过 then 添加回调函数
  2. 定义了触发回调执行的 resolve 方法
  3. 把2中定义的 resolve 传入 fn 中,并执行 fn,而 fn里面会在特定的时间执行 resolve

此外,Promises/A+ 规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。为解决这两个问题,可以通过 setTimeout 将 resolve 中执行回调的逻辑放置到 JS 任务队列末尾:

对于 then 的改进

this.then = function (onFulfilled) {
    if (state === 'pending') {
        deferreds.push(onFulfilled);
        return this;
    }
    onFulfilled(value);
    return this;
};

后续添加的回调也可以立即执行 还是没写完 下次再继续吧