1、middleware
middleware 的函数签名是 ({ getState, dispatch }) => next => action
函数签名:(或者类型签名,抑或方法签名)定义了 函数或方法的输入与输出。
问题
dispatch(actions.getOrderList(tab));
dispatch 的参数是什么?
dispatch 源码:
function dispatch(action) {
// 这里要求我们的 action 是一个纯对象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 略
}
那么这个是怎么实现的呢?,其实是用到中间件 redux-thunk ,文章主要讲 Redux middleware 的实现,顺带提到解释文章开头的问题。
例子
假设现在有两个 middleware:
const log = ({getState, dispatch}) => (next) => (action) => {
console.log('action: ', action);
return next(action);
}
// 这里跟 `redux-thunk` 源码雷同
const thunk = ({getState, dispatch}) => (next) => (action) => {
if( typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
中间件的调用如下:
const store = createStore(rootReducer,applyMiddleware(thunk, log));
rootReducer 就是我们全局的 reducer。接着看 applyMiddleware 的源码。
export default function applyMiddleware(...middlewares) {
return (createStore) => (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))
return {
...store,
dispatch
}
}
}
还有 createStore 的源码
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
// 略
经过转换,enhancer 就是 applyMiddleware。接下来看 enhancer(createStore)(reducer, preloadedState)
得到以下:
const enhancer = (createStore) => (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))
return {
...store,
dispatch
}
}
const store = createStore(rootReducer,enhancer);
=> const store = enhancer(createStore)(reducer, preloadedState);
这里有两个步骤:
const store_1 = enhancer(createStore);
const store_2 = store_1(reducer, preloadedState);
store_1得到:
const store = (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))
return {
...store,
dispatch
}
store_2即:
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//middlewares = [thunk, log]
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 执行上面那句,得到
chain = [(next) => (action) => {
console.log('action: ', action);
return next(action);
}, (next) => (action) => {
if( typeof action === 'function') {
action(dispatch, getState);
}
return next(action);
}]
dispatch = compose(...chain)(store.dispatch) // #1
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)))
}
reduce api 自己查作用。
// 假设
func = [a, b, c];
funcs.reduce((a, b) => (...args) => a(b(...args)))
=> (...args) => a(b(c(...args)));
//所以两个中间件得到
thunk(log(args))
//compose return
return (...args) => thunk(log(args));
所以 #1 得到:
dispatch = ((...args) => thunk(log(args)))(store.dispatch)
dispatch = thunk(log(store.dispatch))
/*
log and thunk
[(next) => (action) => {
console.log('action: ', action);
return next(action);
}, (next) => (action) => {
if( typeof action === 'function') {
action(dispatch, getState);
}
return next(action);
}]
*/
dispatch = (action) => {
if( typeof action === 'function') {
action(dispatch, getState);
}
return ((action) => {
console.log('action: ', action);
return store.dispatch(action);
})(action);
}
所以中间件的链式调用主要是通过 next 实现的。当一个中间件执行完成后,调用下一个中间件,即 next,在例子thunk(log(store.dispatch)) 中,thunk 的 next 就是 log,log 的 next 就是 store.dispatch,在 compose 已经完成 next 参数的传值,最后调用 dispatch 传进的参数为 action。
2、react-redux
React 和 Redux,一个是负责 UI,一个是负责数据管理,其实两者并没有关联,它们之间的关联是使用了 react-redux。
react-redux 最核心的两个是 Provider 和 connect。
先讲一下 React 组件通信的方式:
- 父子组件通信:父组件通过
props 传递数据给子组件
- 子父通信: 父组件通过
props 传递事件给子组件,子组件在相应的地方调用该事件,将通信的数据通过函数参数传回到父组件函数中
- 观察者模式:通过在 componentDidMount 注册事件,在 componentWillUnmount 解绑事件,可在别的组件中发布该事件
- context 上下文
栗子:
//父组件:
class Menu extends React.Component{
getChildContext(){
return {name: 'bb'};
}
}
Menu.childContextTypes = {
name: Protypes.string
}
//子子子组件:
Child.contextTypes = {
name: Protypes.string
}
this.context.name 可访问父组件的 context
第四种方式就是 connect 获取全局 store 的方式。
Provider
Provider 用于包含最顶层的入口组件,例如:
const Root = (
<Provider store={store}>
<App/>
</Provider>
);
ReactDOM.render(Root, window.document.getElementById("app"));
store 是通过 createStore 创建的。
Providor 源码:
export function createProvider(storeKey = 'store', subKey) {
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
constructor(props, context) {
super(props, context)
this[storeKey] = props.store;
}
// 定义子组件能读取的属性
getChildContext() {
return { [storeKey]: this[storeKey], [subscriptionKey]: null }
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
if (this[storeKey] !== nextProps.store) {
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired,
}
// 定义子组件读取的属性的类型
Provider.childContextTypes = {
[storeKey]: storeShape.isRequired,
[subscriptionKey]: subscriptionShape,
}
return Provider
}
export default createProvider()
Provider 模块的功能很简单,从最外部封装了整个应用,并向 connect 模块传递 store。
那么真正连接 React 和 Redux 的是 connect。
connect
Redux 的运作是这样的,首先创建一个全局的 store 用于维护 state,如果要改变 state,则通过 dispatch 一个 action,reducer 通过相应的 action 去更新 state。
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version
this.state = {}
this.renderCount = 0
// 这里便是获取到 Provider 中的 store
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)
invariant(this.store,
`Could not find "${storeKey}" in either the context or props of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
)
this.initSelector()
this.initSubscription()
}
// 略
}
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
}
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes
这段代码便是通过 context 的方式,获取到 Provider 中的全局 store,
总的步骤为
- connect 通过 context 获取 Provider 中的 store,通过 store.getState() 获取整个 store tree 上所有 state。
- connect 模块的返回值 wrapWithConnect 为 function。
- wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent。
调用方式
const ProductCounterBox = connect(state => ({
isLogin: state['is_login']
}))(ProductCounter);
这时 ProductCounter 组件的 props 便包含 isLogin;
React 响应 store
Redux 中还包含 subscribe 函数,用于注册事件,并在 dispatch 执行时发布事件。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 将事件 push 到容器中,在 dispatch 遍历
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
function dispatch(action) {
// 略
// 这里遍历事件并触发
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
那在 React-Redux 中注册了什么事件?
componentDidMount() {
//略
this.subscription.trySubscribe()
}
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange)
: this.store.subscribe(this.onStateChange)
this.listeners = createListenerCollection()
}
}
this.store.subscribe(this.onStateChange) 将事件注册到全局。
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs()
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState)
}
}
在 onStateChange 中 setState,便触发 render 从而重新渲染子组件
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
selector.props 保存的是组件需要的 props。
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}
在 onStateChange 被触发的时候,会调用 this.selector.run(this.props)。这时候会通过 store.getState() 拿到最新的 state,与旧的 state 做比较,决定是否重新渲染组件。
总结:
文章解释了以下:
- 支持 dispatch 传函数参数的中间件 redux-thunk
- Redux 中间件的执行
- React and Redux 的关联 react-redux
- react-redux 的核心 api Providor and connect
- 组件通信
End
By BBChen 😛
参考
1、middleware
问题
dispatch的参数是什么?dispatch 源码:
那么这个是怎么实现的呢?,其实是用到中间件
redux-thunk,文章主要讲 Redux middleware 的实现,顺带提到解释文章开头的问题。例子
假设现在有两个 middleware:
中间件的调用如下:
rootReducer就是我们全局的reducer。接着看applyMiddleware的源码。还有
createStore的源码经过转换,
enhancer就是applyMiddleware。接下来看enhancer(createStore)(reducer, preloadedState)得到以下:
这里有两个步骤:
store_1得到:store_2即:接着看
compose:reduceapi 自己查作用。所以
#1得到:所以中间件的链式调用主要是通过
next实现的。当一个中间件执行完成后,调用下一个中间件,即 next,在例子thunk(log(store.dispatch))中,thunk 的 next 就是 log,log 的 next 就是 store.dispatch,在 compose 已经完成 next 参数的传值,最后调用 dispatch 传进的参数为 action。2、react-redux
react-redux 最核心的两个是
Provider和connect。先讲一下 React 组件通信的方式:
props传递数据给子组件props传递事件给子组件,子组件在相应的地方调用该事件,将通信的数据通过函数参数传回到父组件函数中栗子:
第四种方式就是
connect获取全局store的方式。Provider
Provider用于包含最顶层的入口组件,例如:store是通过createStore创建的。Providor源码:Provider模块的功能很简单,从最外部封装了整个应用,并向connect模块传递store。那么真正连接 React 和 Redux 的是
connect。connect
Redux 的运作是这样的,首先创建一个全局的 store 用于维护 state,如果要改变 state,则通过 dispatch 一个 action,reducer 通过相应的 action 去更新 state。
这段代码便是通过 context 的方式,获取到
Provider中的全局 store,总的步骤为
调用方式
这时
ProductCounter组件的props便包含isLogin;React 响应 store
Redux 中还包含
subscribe函数,用于注册事件,并在dispatch执行时发布事件。那在 React-Redux 中注册了什么事件?
this.store.subscribe(this.onStateChange)将事件注册到全局。在
onStateChange中setState,便触发render从而重新渲染子组件selector.props保存的是组件需要的 props。在
onStateChange被触发的时候,会调用this.selector.run(this.props)。这时候会通过store.getState()拿到最新的 state,与旧的 state 做比较,决定是否重新渲染组件。总结:
文章解释了以下:
End
By BBChen 😛
参考