为何要写这篇文章?html
之前想写来着,发现有太多这种文章,就不想去总结了,结果太长时间没作web开发了,就忘记了,在阿里面试被问了好几回,回答的都不很理想,因此仍是那句话好记性不如烂笔头,更况且尚未好记性!react
先看一个示例:git
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能够在其余任何框架中使用github
这里只是经过react-redux将react和redux结合在一块儿了!web
接下来咱们先抛弃react咱们来分析源码:面试
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接受传入一个reducerredux
返回:设计模式
return {
dispatch,
subscribe,
getState,
replaceReducer,
}复制代码
能够很容易看到返回的store就是一个订阅发布设计模式bash
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,就用到了
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以前加一层!
要发送异步请求就须要中间件,先看一个最简单的中间件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)
}
}复制代码
代码很是简单,来结合一个例子看看中间件怎么使用:
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的再包装,包装的代码有点难懂,要好好的理解闭包和高阶函数打断点才能看懂哟!!!
目前已经有现成的工具react-redux
来实现两者的结合:
react-redux提供了Provider connect两个工具
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是一个高阶函数,首先传入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
最后从网上搞了张图,很是好!