为什么要写这篇文章?
以前想写来着,发现有太多这种文章,就不想去总结了,结果太长时间没做web开发了,就忘记了,在阿里面试被问了好几次,回答的都不很理想,所以还是那句话好记性不如烂笔头,更何况还没有好记性!
先看一个示例:
import React,{Component,PropTypes} from 'react'
import ReactDOM from 'react-dom'
import {createStore } from 'redux'
import {Provider,connect }from 'react-redux'
// React component
class Counter extends Component {
render() {
const {value,onIncreaseClick} = this.props
return (
<div>
<span>{value} </span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
const increaseAction = {
type: 'increase'
}
function counter(state = {
count: 0
},
action) {
const count = state.count
switch (action.type) {
case 'increase':
return {
count:
count + 1
}
default:
return state
}
}
const store = createStore(counter)
function mapStateToProps(state) {
return {
value: state.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () = >dispatch(increaseAction)
}
}
const App = connect(mapStateToProps, mapDispatchToProps)(Counter)
ReactDOM.render(
<Provider store = {store}>
<App/>
</Provider>,
document.getElementById('root'))
复制代码
这是Redux在react中的应用,其实redux和react是没有关系的,redux可以在其他任何框架中使用
这里只是通过react-redux将react和redux结合在一起了!
接下来我们先抛弃react我们来分析源码:
createStore源码
function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
function getState() {
return currentState
}
function subscribe(listener) {
nextListeners.push(listener)
}
function dispatch(action) {
currentState = currentReducer(currentState, action)
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i] listener()
}
return action
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer dispatch({
type: ActionTypes.INIT
})
}
dispatch({
type: ActionTypes.INIT
})
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
复制代码
简化后的代码可以看到createStore接受三个参数,后面两个参数先忽略,也就是createStore接受传入一个reducer
返回:
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
复制代码
可以很容易看到返回的store就是一个订阅发布设计模式
-
dispatch: 用于action的分发,改变store里面的state
-
subscribe: 注册listener,store里面state发生改变后,执行该listener
-
getState: 读取store里面的state
-
replaceReducer: 替换reducer,改变state修改的逻辑
subscribe传入function订阅
dispatch(action)发布消息,将action和当前的state传入定义好的reducer得到新的state
接着通知之前通过store.subscribe订阅消息的函数,这样看是不是特别简单
// 先写一个,成为reducer
function count (state, action) {
state=state || 2020;
switch (action.type) {
case 'add':
return {
year: state.year + 1
};
case 'sub':
return {
year: state.year - 1
}
default :
return state;
}
}
var store = createStore(count);
// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
console.log('the year is: ', store.getState().year);
});
var action = { type: 'add' };
// 改变store里面的方法
store.dispatch(action); // 'the year is: 2021
复制代码
很显然,只有一个 reducer 是 hold 不住我们整个应用中所有 action 操作的,会变得很难维护
怎么办呢,这时候要写多个reducer,就用到了
使用combineReducers
var reducer_0 = function(state = {},action) {
switch (action.type) {
case 'SAY_SOMETHING':
return {...state,
message: action.value
}
default:
return state;
}
}
var reducer_1 = function(state = {},action) {
switch (action.type) {
case 'SAY_SOMETHING':
return {...state,
message: action.value
}
case 'DO_SOMETHING':
// ...
case 'LEARN_SOMETHING':
// ...
default:
return state;
}
}
复制代码
和并多个reducer,使用combineReducers直接搞定
import {
createStore,
combineReducers
}
from 'redux'
var reducer = combineReducers({
user: reducer_0,
items: reducer_1
})
复制代码
接下来看下combineReducers是怎么做到的
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
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
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState: state
}
}
复制代码
combineReducers方法将多个子reducers合并为一个对象finalReducers
当dispatch(action)触发合并后的combination,combination通过key在调用各个子模块,返回state,最后合并为最新的nextState,是不是很简单
看到这里可能会有疑问,dispatch(action)后就是触发reducers,那异步请求怎么办呢?
那就想办法呗,既然reducer是获取更新State,异步请求是获取最新的数据,那只能在reducer之前加一层!
// 通常来说中间件是在某个应用中 A 和 B 部分中间的那一块,
// dispatch(action)-----> reducers
// 变成:
// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers
中间件middleware
要发送异步请求就需要中间件,先看一个最简单的中间件redux-thunk.
// 我们为异步 action creator 提供的中间件叫 thunk middleware// 它的代码在:https://github.com/gaearon/redux-thunk.
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}
复制代码
如上所述,中间件由三个嵌套的函数构成(会依次调用):
- 第一层向其余两层提供分发函数和 getState 函数(因为你的中间件或 action creator 可能需要从 state 中读取数据)
- 第二层提供 next 函数,它允许你显式的将处理过的输入传递给下一个中间件或 Redux(这样 Redux 才能调用所有 reducer)。
- 第三层提供从上一个中间件或从 dispatch 传递来的 action,
这个 action 可以调用下一个中间件(让 action 继续流动) 或者 以想要的方式处理 action。
代码非常简单,来结合一个例子看看中间件怎么使用:
import {
createStore,
applyMiddleware
} from 'redux'
//将redux-thunk代码直接放在这里方便阅读
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}
}
var middleware = applyMiddleware(thunkMiddleware)
var reducer = function(state = {},action) {
switch (action.type) {
case 'SAY':
return Object.assign(state, {
message: action.value
})
default:
return state
}
}
const store = createStore(reducer, undefined, middleware)
// 现在 store 的 middleware 已经准备好了,再来尝试分发我们的异步 action:
var asyncSayAction = function(message) {
return function(dispatch) {
setTimeout(function() {
console.log(new Date(), 'Dispatch action now:') dispatch({
type: 'SAY',
message
})
},
2000)
}
}
store.dispatch(asyncSayAction('Hi'));
复制代码
// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers
由上图可知dispatch是要先经过中间件的,之前提交dispatch(action)都是对象{type:'SAY'},
redux-thunk是通过提交action判读是不是function,决定下一步操作
接下来分析中间件是怎么加入redux的
applyMiddleware
function applyMiddleware(...middlewares) {
return function(createStore) {
return function(reducer, preloadedState, enhancer) {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) = >dispatch(action)
}
chain = middlewares.map(middleware = >middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
}
复制代码
根据createStore的传入参数调用enhancer(createStore)(reducer, preloadedState)
即applyMiddleware返回的函数
function(createStore) {
return function(reducer, preloadedState, enhancer) {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) = >dispatch(action)
}
chain = middlewares.map(middleware = >middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
这个函数先创建store,跟没有中间件的流程一样,接着看是怎么把中间件加入到
通过chain = middlewares.map(middleware => middleware(middlewareAPI))
传入第一层提供分发函数和 getState 函数(因为你的中间件或 action creator 可能需要从 state 中读取数据)
还是看redux-thunk
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action)
}
}
}
复制代码
接着通过 dispatch = compose(...chain)(store.dispatch)
组装中间件,返回的是一个包装函数,最好自己打断点调试看一下,
返回的包装函数为dispatch,当我们再次使用dispatch提交的时候就会先调用中间件,
中间件中的next代表下一个中间件,一直到最后一个中间件结束,调用的next是store.dispatch
然后出发reducer
dispatch(action) ---> middleware 1 ---> middleware 2(第一个next) ---> middleware 3(第二个next) ...store.dispatch(最后一个next) --> reducer
打断点可以清楚的看出上面redux-thunk中间件
由于这里只用到一个中间件redux-thunk,所以next直接是包装前dispatch,调用后直接触发reducer
到这里redux就结束了,是不是很简单,中间件的加入就是对原有dispatch的再包装,包装的代码有点难懂,要好好的理解闭包和高阶函数打断点才能看懂哟!!!
接下来看redux是怎么和react结合的:
基本使用
目前已经有现成的工具react-redux
来实现二者的结合:
react-redux提供了Provider connect两个工具
Provider -- 提供store
React通过Context属性,属性(props)直接给子孙component,无须通过props层层传递, Provider仅仅起到获得store,然后将其传递给子孙元素而已:
export default class Provider extends Component { getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } render() { return Children.only(this.props.children) }}
复制代码
是不是很简单!
接下来看看复杂的connect
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component
的函数(wrapWithConnect)
看看connect是怎么实现高阶组件,更新数据的
function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
const mapState = mapStateToProps let mapDispatch = mapDispatchToProps
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version
this.store = props.store || context.store
const storeState = this.store.getState()
this.state = {
storeState
}
}
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
}
}
componentDidMount() {
this.trySubscribe()
}
componentWillReceiveProps(nextProps) {
if (!pure || !shallowEqual(nextProps, this.props)) {
this.haveOwnPropsChanged = true
}
}
handleChange() {
const storeState = this.store.getState() this.setState({
storeState
})
}
render() {
this.renderedElement = createElement(WrappedComponent, this.mergedProps)
return this.renderedElement
}
}
Connect.displayName = connectDisplayName Connect.WrappedComponent = WrappedComponent Connect.contextTypes = {
store: storeShape
}
Connect.propTypes = {
store: storeShape
}
return hoistStatics(Connect, WrappedComponent)
}
}
复制代码
将connect代码简化后可以看到,HOC高阶组件就是对自定义组件的封装
封装后的组件,通过获取redux的store,然后通过this.store.subscribe监听数据变化
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
}
}
复制代码
一但数据有变化触发handleChange
调用setState触发render,在render中获取需要的参数合并传递给自定义组件完成更新:
handleChange() {
const storeState = this.store.getState() this.setState({
storeState
})
}
复制代码
到这里就接近尾声了,只要理解原理,以后再使用是不是就很容易上手1
最后从网上搞了张图,非常好!