React Router Complicated Params No Using Query

react router complicated params

We always facing a problem when building SPA apps with react & react router between diff route paths —— list page and detail pages need to share some data:

we can just solve it with url params just in path, query, or search just like below

1
2
3
4
5
6
7
8
9
10
11
12
// path params
<Route path="querylist/:taskId" component={QueryList} />

// and we can get the params like this
const { taskId } = this.props.params

// query & search params
<Link target="_blank" to={ { pathname: '/crawler/group/duplicate/' + ${row.id}, query: { taskId: 12345 } } }>duplicate list</Link>

<Link target="_blank" to={ { pathname: '/crawler/group/duplicate/' + ${row.id}, search: querystring.stringify({ taskId: 12345 }) } }>duplicate list</Link>

const { query, search } = this.props.location

but the disadvantage is obviously:

1.the url is just ugly

2.we should concern about the character set in url, it may exist the reality that browser not support such params in url

solutions

redux solution

actually we just want a centralized state manage solution, and we can just solve it with redux. But actually I don’t like solution like this, if I suggest solution like this, there’s no this post.

2 reasons below:

1.redux can be cumbersome with some app state like fetching data, submiting data

2.there’s much template code(repeat code) using redux

so I quit redux

location state

actually react-router location support custom state passing with paths, we can just handle like this

1
2
3
4
5
this.props.router.push({ pathname: '/crawler/normalize/' + taskId,
  state: Object.assign({ taskName: '123' }, this.props.location.state) })

// and can access
const state = this.props.location.state

Just little tricks.

Redux React Boilerplate

Long time no articles. Last one year working on html5 apps, hybrid apps, using backbone. Faced some problems with backbone, I thought more about frontend. several problems below with backbone

1. backbone model is weak and error-prone in complex projects

when model is listened in many views like the accountModel, you can’t imagine what things will happen, it’s terrible.

2. missing data binding

this leads to every change should update view manual, it’s too disgusting.

3. hard to test

backbone’s event driven makes developer manipulate DOMs themselves,It’s hard to test.

4. code reuse

code can’t reuse effictively because of manipulating DOMs

Compared to react and the idea of flux, a new frontend technology stack comes. React is an awesome MVC View framework, and the flux programming idea is also greatly decoupling business which the above first problem can be solved.

Here is a redux and react demo - LianExchange

1. redux provide store with react

react router using history, the entry just bind redux store and react router, using history to enable router, using Provider to bind store with react

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
import { Provider } from 'react-redux'
import { Router, Route } from 'react-router'
import { createHistory } from 'history'

import App from './containers/App'
import Buy from './containers/Buy'

import configure from './store'

const store = configure()
const history = createHistory()
syncReduxAndRouter(history, store)

//if you just want route with hash, just
//<Router location="hash"></Router>

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
      </Route>
      <Route path="/buy" component={Buy}>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

We can see how to generate redux store below and bind with react.

2. redux apply http request middleware and create store

Redux createStore can only handle synchronize action like the todo app, we using redux middleware to handle async action like ajax or some what.

1
2
3
4
5
6
7
8
9
10
11
12
13
  import { createStore, compose, applyMiddleware, combineReducers } from 'redux'
  import rootReducer from '../reducers'
  import apiMiddleware from '../middlewares/apiMiddleware'
  const create = window.devToolsExtension
    ? window.devToolsExtension()(createStore)
    : createStore

  const finalCreateStore = compose(
    applyMiddleware(apiMiddleware)
  )(create)

  // const store = create(rootReducer, initialState)
  const store = finalCreateStore(rootReducer)

here we create redux store, and bind to react, then how can we bind the redux state and action to react, look below, using redux connect and bindActionCreators

3. bind redux state and actions with react this.props

container/Buy/index.js

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
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as Coins from '../../actions/coins'
import style from './style.css'
import Header from '../../components/Header'
import Footer from '../../components/Footer'

class Buy extends Component {
  render() {
    const { actions, children, storage } = this.props

    return (
      <div>
        <Header />
        <Footer />
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    storage: state.coins
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Coins, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Buy)

we can bind redux state the react components or just html5 pages- mapStateToProps, developers can dispatch actions or reading data from this.props, and the parent props can pass sub data using this.props

4. simple reducers describe state change process

reducers/account.jsx

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
import ActionTypes from '../constants/ActionTypes'

const initState = {
  accountReady: false,
  accountError: false,
  account: {}
};

export default function account(state = initState, action) {
  switch (action.type) {
    case ActionTypes.ACCOUNT_LOAD: {
      return {
        ...state,
        accountReady: false
      }
    }
    case ActionTypes.ACCOUNT_LOAD_SUCCESS: {
      return {
        ...state,
        account: action.data,
        accountReady: true,
        accountError: false
      }
    }
    case ActionTypes.ACCOUNT_LOAD_ERROR: {
      return {
        ...state,
        account: action.data,
        accountReady: true,
        accountError: true
      }
    }
    default: {
      return state;
    }
  }
}

5. sync actions and async actions

actions/account.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import ActionTypes from '../constants/ActionTypes'
import {requestBase, baseAPI} from '../app/configs'

export function loadAccount() {
  return {
    types: [
      ActionTypes.ACCOUNT_LOAD,
      ActionTypes.ACCOUNT_LOAD_SUCCESS,
      ActionTypes.ACCOUNT_LOAD_ERROR
    ],
    requestSettings: {
      method: 'GET',
      url: requestBase.lianCoinUrl + baseAPI.me
    },
    requestParams: {Includes:['Accounts','Profile','BankCards']}
  }
}

Reducers only describe the data transfer process which how state A transfers to state B, and then the react components re-renders the view. Sync actions just like so, then reducers can change the state immediately

1
2
3
4
5
export function filterAccount() {
  return {
    type: ActionTypes.ACCOUNT_FILTER
  }
}

async actions should supply request url, method, params and so on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function loadAccount() {
  return {
    types: [
      ActionTypes.ACCOUNT_LOAD,
      ActionTypes.ACCOUNT_LOAD_SUCCESS,
      ActionTypes.ACCOUNT_LOAD_ERROR
    ],
    requestSettings: {
      method: 'GET',
      url: requestBase.lianCoinUrl + baseAPI.me
    },
    requestParams: {Includes:['Accounts','Profile','BankCards']}
  }
}

6. redux http middleware

async actions generally has there types [load, success, error] correspond to the actions in middlewares/apiMiddleware.js const [PENDING, FULFILLED, REJECTED] = action.types, the the middleware do request using superagent or some other http request library you like, the callback can dispatch actions afterwards, the next is just redux dispatch, above in the containers/Buy/index.js we bind dispatch to the async action creator bindActionCreators(Coins, dispatch)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   case 'GET': {
      return superagent.get(url + '&' + querystring.stringify(params))
        .end((err, res) => {
          if(err || !res.body) {
            next({
              type: REJECTED,
              params
            })
          } else {
            next({
              type: FULFILLED,
              params,
              data: res.body.data
            })
          }
        })
    }

then reducers still accept actions and change the state, and afterwards react re-render views.

Factorial Division

1. factorial problem

When playing on code war, meeting a problem which is called factorial division. Calculating the result of n!/d! (n>d). It’s easy, here is my solution:

1
2
3
4
5
6
7
function factorialDivision() {
  var result = 1;
  for(var i=n; i>d;i--) {
      result *= i;
  }
  return result;
}

Here is another solution using Recursive:

1
2
3
function factorialDivision(n, d) {
  return n==d && 1 || n * factorialDivision(n-1,d)
}

Excellent!

Login With Password Remembered

login with password remembered

With password remembered, the password must be encrypted in some way and stored in the cookie, actually there’s 3 cookies in header when user browser request for certification. $.cookie('cn')- username $.cookie('ct')- time stamp last login $.cookie('ctoken')- encrypted password or some other info required to be certificated by server

Then we know ctoken is important, there’s problem- how to ensure the password is not cracked by hackers, below is a way to do some calculation like or with two strings.

Think about this: if we just simply encrypt the password with MD5, sha1… and store in browser, is that safe? Maybe, but the most keys are cracked with md5, or sha1…, so we should encrypt it with some more complicate way:

1
2
3
4
5
6
7
8
9
10
11
 var user = $.trim($("#username").val());
 var pass = $.trim($("#password").val());
 var sk = new Date().getTime().toString();
 $.cookie('cn', user, { expires: 7, path: '/' });
 $.cookie('ck', sk, { expires: 7, path: '/' });
 var phash = CryptoJS.MD5(pass).toString();
 var cthash = CryptoJS.MD5(user + sk + phash).toString();

 var token = xorString(phash, cthash);
 $.cookie('token', token, { expires: 7, path: '/' });
 //then request the server for validation

what’s xorString, it creates a token for server validation. The xorString gets two params - phash which is the password encrypted in MD5, another is cthash which is the username + timestamp + MD5(password) encrypted in MD5. We can see the xorString is handling the two strings in a special way. What will it?

1
2
3
4
5
6
7
8
        var hex = "0123456789abcdef";
        var xorString = function (str, key) {
            var rs = "";
            for (var i = 0; i < str.length; i++) {
                rs += hex[hex.indexOf(str[i]) ^ hex.indexOf(key[i])];
            }
            return rs;
        };

It’s easy, just a or operation with each MD5 result, it’s an MD5 result again, but not the result of password. And although others know the token is some or result. It’s nearly impossible for them to crack it.

Of course, we can calculate the right token easily in the server side. Here’s a version of C#:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    private static string StringXor(string str, byte[] key)
    {
        for (var i = 0; i < key.Length; i++)
        {
            key[i] = (byte)(Convert.ToByte(str.Substring(i * 2, 2), 16) ^ key[i]);
        }
        return BitConverter.ToString(key).Replace("-", "").ToLower();
    }
  private bool auth() {
      string cn = "";
      string ct = "";
      string token = "";

      string password = "" //select from the db
      byte[] cctoken;
                using (var md5 = MD5.Create())
                {
                    cctoken = md5.ComputeHash(Encoding.ASCII.GetBytes(cn + ct + password));
                }
                return token.Equals(StringXor(password, cctoken)) ? true : false;
}

We can store the ‘password’ safely in the browser.

Node Crawler

1. Node crawler

It’s been a long time when my last blog. This time I’m going to start an open source project called node-crawler. Let’s get straight to the point, the project includes three parts:

1.1 Admin Dashboard

It’s a distribute crawling service, the admin can check the crawler status, manage the crawling data, control the crawler client, manage the client config and deliver the client to different servers through the platform. Or some functions like data analyze or data statics. (This backend mainly developed with angularJs ).

1.2 Backend Service

Nodejs backend service which is responsible for collecting the uploaded data. Mainly include some functions like filter the data, analyze the crawling result and store into the database.

1.3 Crawler Client

The client is responsible for crawling the pages, execute scripts, control spider amount,. Think that the backend service send some configs, the client read it and do the crawling job the server send it.

Actually the client should be smart to avoid preventing by the crawling target server, It should have a lot of strategies like changing the ip, changing the agent, limit the spider speed..and so on.

The server send message to check the client and start the client, send config like to start the client:

1
2
3
4
5
var clientConfig = {
  speed: 100, //100/s to access the target website
  workers: 10, //send 10 workers together crawling the website
  //some other configs
}

the client start to crawling work as the config:

1
2
3
4
5
6
var crawlerConfig = {
  target: "http://www.taobao.com/p/xxxxx", //taget crawling website
  element: '', //target element
  attr: '', //the real data to grab
  //some other configs about the target web page
}

This blog is just an introduction of the crawling system. Other blogs will introduce how to implement it! Welcome!

Javascript 闭包

Javascript闭包

说到javascript的闭包,就要先说一下传统语言的一些特征,如C,Java,C#等等,void Test() {int i = 0;} ,当Test方法调用结束时,执行栈弹出,局部变量被回收,而js的闭包,则是提供了一种在test这个执行域(函数)消亡时,仍能够访问i的方式。

1
2
3
4
5
6
function greeting(name) {
var text = 'Hello, ' + name; // local variable  
return function() { alert(text); } // 每次调用时,产生闭包,并返回内部函数对象给调用者  
}
var sayHello=greeting("Closure");
sayHello() // 通过闭包访问到了局部变量text  

1.闭包原理

闭包的原理涉及到ECMAScript语言的一些特征,下面进行详述

1.1 Execution Context

控制权转移到一段可执行代码时候,就会产生一个Execution Context(简称EC)。

一段可执行代码在ECMAScript里的定义可视为一个function(eval暂不提,strict mode也将其排除在外),即函数调用的时候,就会通过压栈的方式将一个个function的ECStack压入栈中,该function return的时候就从栈中pop出来,用个简单的例子来形容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function foo(flag) {
  if (flag) {
    return;
  }
  foo(true);
})(false);

// 压栈情况,globalContext视环境而定,如果是浏览器,就是window
ECStack = [
  <foo> functionContext
  globalContext
];
// 压入递归方法foo
ECStack = [
  <foo> functionContext  recursively
  <foo> functionContext
  globalContext
];
//再弹出至只剩globalContext
ECStack = [
  globalContext
];

而每个context又是由什么组成的呢, context = { VO = {}, //variable object,函数调用时的内部变量的集合,包括arguments,内部方法等. this , //不赘述了,简而言之就是caller(a.b()的a),当然new是另一情况了 scope chain = [] //见下文,用于变量查找 } 附上 VO, this, 闭包的产生主要是Scope Chain原因,但是强烈推荐以上几篇文章,对于理解ECMAScript的机制有很大帮助,能让人顿悟很多东西。

1.2 Scope

Scope上下文,即是在执行语句时候能够访问到的所有变量的集合,Javascript是允许inner function存在的,因而其Scope就是一个链表形式的存在,其最顶端就是window(浏览器环境),然后每定义一个function,就定义一个Scope Object,保存一个outer reference,以访问上一级变量集合,形成一个层级式的链,处于最底部的function会一层一层向上找变量,这也是为什么不要使用t = 'without var is global var',会大大降低执行效率。附上ECMA-262

而每个Scope保存的out reference是由function定义时决定的,而不是调用执行时决定的,如

1
2
3
4
5
6
7
8
9
10
(function() {var right = 5;
var s = function() {
   alert(right);
}
var o = function() {
var right = 4;
s();
}
o();
})();

alert结果是5而非4,如果把最上级的right定义注释掉,则right is not defined。所以这个时候是不是能理解闭包的原理了,虽然greeting方法已经消亡,但是返回的匿名函数的Scope保存有其上一级的outer reference,也就是text变量等的集合。

下一篇文章会介绍一下使用闭包的场景和相应的危害,欢迎拍砖。

Hello

Welcome to my blog!

It’s been a long time since my last wordpress blog dropped down. Thanks the terrible US VPS I’v been always using, it just dropped down before I transferred my important backups.

Put it aside, I didn’t want to write blogs for a long time since the accident. It’s popular setting a blog using github pages & jekyll because of github’s free static pages hosting service and powerful

CDN service. So just a new try!