头图: @Linksphotograph

前言

啊~该来的还是来了...要速成 React 才好干活。

redux

Redux 这个库本质上和 react 没有关系,是一个不依赖于任何第三方框架的库,其提供的是一种思想的实现,其精神是将所有的状态都集中到一个 store 中进行管理,通过 action 改变 store,然后触发 View 的更新。

store 中储存着 statestore.dispatch() 可以传入一个 action,redux 接收到 action 之后就会对 actionreducereducer 就是 action 被应用的目标函数。

理念里面,Reducer 应该是个纯函数,即对一个输入重复应用该输入,永远产生相同的输出。

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用 Date.now() 或者 Math.random() 等不纯的方法,因为每次会得到不一样的结果

基础使用

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    case 'DECREMENT': return state - 1;
    default: return state;
  }
};

const store = createStore(reducer);
let unsubscribe = store.subscribe(() => {
    console.log(store.getState());
});
store.dispatch({type: 'INCREMENT'});    // 输出 1
unsubscribe();       // 调用返回的这个方法可以取消订阅

中间件的应用与 applyMiddleware()

createStore 的第三个参数传入即可

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(...middlewares)
);

applyMiddleware 的源代码如下:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

compose()

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

其实就是对传入的函数做嵌套处理。

const a = fn1(fn2(fn3(fn4(x))))            // 嵌套的调用
const b = compose(fn1,fn2,fn3,fn4)(x)      // 两者等价

redux-thunk 中间件

可以让 store.dispatch 函数的参数支持一个函数,而不是一个对象。

store.dispatch((dispatch, getState) => {
    dispatch({type: 'START'})
    // do something async
    asyncFunc().then(v => {
        dispatch({type: 'END', result: v})
    })
})

就让异步的操作可以方便发起 action。

react-router

实现 SPA 的核心 module,和 Vue 里面的差不多。

使用

需要的地方放置 Router 组件,通过 routes 参数传入一个路由参数组件即可。该对象指定好每个路由的 path 和对应的 component

indexRoute 用于定义一个路径的默认页面,例如访问 / 时,没有指定 children 是啥,那么就 fallback 到 indexRoute

const routeConfig = [
  { path: '/',
    component: App,
    indexRoute: { component: Dashboard },
    childRoutes: [
      { path: 'about', component: About },
      { path: 'inbox',
        component: Inbox,
        childRoutes: [
          { path: '/messages/:id', component: Message },
          { path: 'messages/:id',
            onEnter: function (nextState, replaceState) {
              replaceState(null, '/messages/' + nextState.params.id)
            }
          }
        ]
      }
    ]
  }
]

React.render(<Router routes={routeConfig} />, document.body)

选择 history 实现模式

react-router 提供了三种路由模式:

  • browserHistory:浏览器的 history API 实现的,生成真实的路径
  • hashHistory:使用 hash change 的事件实现,生成 /#/xxx 这样的路径
  • createMemoryHistory:路由不在地址上体现

都可以直接从 react-router 中直接 import。通过 Router 组件的 history 参数传入即可。

import { browserHistory, Router } from 'react-router'
return (<Router history={browserHistory} />)

监听路由与操作路由

查阅 history 这个库的 API, react-router 也是依赖这个库来实现的 hisotry 方案,https://github.com/rackt/history

react-router-redux

目的是将 historystore 同步,增强 history 对象后再移交给 react-router 做路由,实现原理是对 history 对象打补丁。

注册 reducer

需要一个 router-reducer 来相应 dispatch 的更新。库提供了一个默认的,可以通过

import { routerReducer } from 'react-router-redux'

获取到这个 reducer,注册这个中间件即可。

其源代码如下:

export const LOCATION_CHANGE = ‘@@router/LOCATION_CHANGE’

export function routerReducer( state ) {
  if (type === LOCATION_CHANGE) {
    return { …state, locationBeforeTransitions: payload }
  }

  return state
}

申明同步

import { browserHistory } from ‘react-router’
import { syncHistoryWithStore } from ‘react-router-redux’

const history = syncHistoryWithStore(browserHistory, store)

 
// syncHistoryWithStore 可以接受第三个参数
{
    selectLocationState: (state) => state.routing // 默认 state 中存放 history 的位置
}

产生的 history 对象对外行为同 react-routerhistory 对象的行为一致。可以 push(), goBack() 等操作。

react-redux

终于是时候把这两个大杀器整合在一起了。

写好一个组件之后,要调用 connect 方法将这个组件变成容器组件。

import { connect } from 'react-redux'
const connectedComponent = connect(
    mapStateToProps,
    mapDispatchToProps
)(component);

mapStateToProps

mapStateToProps 是个函数,它返回一个对象。每次 state 更新的时候都会触发一次执行,这个函数的作用是将 state 转换为组件的 props,从而触发组件内部 UI 的更新渲染。

mapStateToProps = (state, ownProps) => {     // state 是 store 的 state,ownProps 是组件当前的 props 对象
    xxx: state.xxx                           // xxx 会成为组件 props 的元素
}

mapDispatchToProps

mapDispatchToProps 也是一个函数,也可以是一个对象。

传入方法时,方法返回一个对象,对象每个元素是个方法,可以完成 dispatch 操作,可以不返回元素:

mapDispatchToProps = (dispatch, ownProps) => {
    onXXXX: () => {                          // onXXX 会成为组件 props 的元素
        dispatch({
            type: 'XXXX'
        })
    }
}

传入对象时,对象每一个元素都是一个方法,调用该方法要返回一个 action(如果结合 redux-chunk,可以返回一个方法),这个 action 会被自动 dispatch

const mapDispatchToProps = {
  onXXXX: () => {
    type: 'XXXXXXXX',
  };
}

<Provider> 组件

如果嵌套过深,靠 connect 一层层传递 state 就是一个嵌套地狱。

<Provider store={store}>
    <YourComponent />
</Provider>

然后组件内部就可以通过 this.context 获取到 store 对象

const {store} = this.context;

小结

可能写的不是很详细,因为这篇博客是我个人学习的时候随手笔记的那种风格。里面的说法也不一定正确,是我自己理解的产物。