←首页

最近因为项目原因,学习新的技术栈,不得不吐槽一句,前端现在切换技术栈就像学了新的语言,新的语法,运行机制,生命周期,性能优化方法。总体上看,现在的前端分两派,一类加分号,一类不加分号,切换时都想骂人。

总之最近开始学 React 了,那么最开始先把全家桶都领出来实操一遍。在写 Vue 的时候,我们常用 Vuex 来解决组件通信和前端页面之前数据对象存储。那对应的 React 里面使用的是 Redux 。 这篇文章主要通过 React 使用 Redux 的方式,配合源码讲解 Redux 如何工作。

首先来看,通常如何使用在 React 里面如何使用 Redux。

Store

首先通过 redux 的 createStore 方法创建一个 store,store 内部存储数据。

1
2
3
import { createStore } from 'redux'
// 暂时忽略 reducer, preloadedState 用来初始化 state
const store = createStore(reducer, preloadedState)

store 实例主要有三个方法:

  • getState() ,每个 store 都通过闭包拥有一个独立的 state ,通过 getState 方法返回当前 store 存储的 state。
  • dispatch(), state 只能通过 dispatch 方法来触发修改,dispatch 接受 Obejct 类型的 action 对象,每个 action 都已一个 type 对象用以区分。
  • subscribe(),这个方法可以监听每次 dispatch,并返回一个 unsubscribe 方法,调用 unsubscribe 来取消订阅。

Action

store 数据只允许 Action 事件来触发修改,这样做的好处是让操作可回溯,只要存下来 Action 和 store state 就能时光旅行。通常会把 Action 包一层,做数据处理,然后再返回一个 Action 供 dispath 。

1
2
3
4
5
6
7
8
let nextTodoId = 0;
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})

store.dispatch(addTodo('something'))

Reducer

上面提到 Store 并没有暴露 state 对象,所以没办法直接修改,只能通过 dispatch Action。而 Action 只负责传递事件和数据,谁来进行数据处理呢? 答案就是 Reducer 啦。 在 createStore 的时候,我们已经把 reducer 传进去了。

Reducer 是一个 Function,接受两个参数 state, action。然后经过一波操作之后返回一个新的 state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
default:
return state
}
}

问题思考

Redux 如何初始化 State

初始化 state 有两种途径,一种是 createStore 的第二个参数,另外一种则是在 reduer 里面给 state 赋默认值。createStore 时,store 内部会 dispatch 一个 ActionTypes.INIT 事件,reducer switch action.type 走到 default,返回默认值。

复杂一点的 Reducer

state 的更新是在 dispatch 之后调用 reducer 返回新的 state 。简化后的 dispatch 如下:

1
2
3
4
5
6
7
8
9
10
11
12
function dispatch(action) {
// 得到新的 state
currentState = currentReducer(currentState, action)
// 通知所有监听者
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

return action
}

所以 reducer 最终只有一个,对于多个 reducer,Redux 提供 combineReducers 方法。combineReducers 只是将 reducers 封装一层,组合返回新的 reducer

1
2
3
4
5
6
7
8
9
10
11
return function combination(state = {}, action) {
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
return nextState
}
}

中间件

Redux 支持使用中间件,在 createStore 时传入,每个中间件会传入 store,返回新的 dispatch 方法,而每个 dispatch 方法应该返回处理后的 state。

比如自己写一个 Logger Middleware

1
2
3
4
5
6
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}

使用方法:

1
2
3
4
5
6
import { createStore, applyMiddleware } from 'redux'

let store = createStore(
todoApp,
applyMiddleware(logger)
)

Redux Middleware 的实现方式很巧(niu)妙(bi)。首先是 createStore(reducer, preloadedState, enhancer) 方法里面会判断是否传入了 enhancer ,如果有就 return enhancer(createStore)(reducer, preloadedState),让 enhancer 来创建 store 。

1
2
3
4
5
6
7
8
// applyMiddleware 内部实现

const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

compose 方法将多个 function 串起来,

1
2
3
4
5
6
7
8
9
10
11
12
13
// (...args) => f(g(h(...args))).

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

如何结合 React 进行绑定

综合以上内容,我们知道 store 有 subscribe 方法,有 dispatch 方法。
那么就可以在 React 的生命周期里面去订阅 store 变化,判断更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
componentWillMount() {
this.unsubscribe = store.subscribe(() => {
// 判断是否需要更新
this.forceUpdate()
})
}
componentWillMount() {
this.unsubscribe && this.unsubscribe()
}
render() {
const storeState = store.getState()
return (
<span>{storeState.count}</span>
)
}

End

Redux 本身代码并不多,但是很多实现都很巧妙,推荐阅读。然后 React 里面使用 Redux 通常会使用 react-redux ,也刚看完源码,过几天再整理。

如需转载,请注明出处: http://w3ctrain.com / 2018/03/14/reading-source-code-of-redux/

helkyle

我叫周晓楷

我现在是一名前端开发工程师,在编程的路上我还是个菜鸟,w3ctrain 是我用来记录学习和成长的地方。