React
Redux三大基本原则
-
单一数据源受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举 -
state是只读的唯一改变 state 的方法就是触发 action -
使用纯函数来执行修改为了描述 action 如何改变 state tree ,你需要编写 reducers,并且reducers中不能包含副作用
Redux中的一些概念
- state: 全局的状态对象,唯一且不可变。
- store: 调用createStore 函数生成的对象,里面封入了定义在createStore内部用于操作全局状态的方法,用户通过这些方法使用Redux。
- action: 描述状态如何修改的对象,固定拥有一个type属性,通过store的dispatch方法提交。
- reducer: 实际执行状态修改的纯函数,由用户定义并传入,接收来自dispatch的
action作为参数,计算返回全新的状态,完成state的更新,然后执行订阅的监听函数。
- storeEnhancer:
createStore的高阶函数封装,用于加强store的能力,redux提供的applyMiddleware是官方实现的一个storeEnhancer。 - middleware: dispatch的高阶函数封装,
由applyMiddleware把原dispatch替换为包含middleware链式调用的实现
用法示例
首先来看一下源码的example里面的一个最基本的用例
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'
const store = createStore(counter)
const rootEl = document.getElementById('root')
const render = () => ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>,
rootEl
)
render()
//订阅渲染函数,当状态变更时,执行render
store.subscribe(render)
Redux源码解读
createStore
createStore 是一个大的闭包环境,里面定义了store本身,以及store的各种api。所有的state,reducer,action等都在这里面维护
如果有storeEnhancer,则应用storeEnhancer,storeEnhancer从字面上理解,就是一个store的增强器,最后也是返回store
如果没有storeEnhancer,则初始化state(通过dispatch一个随机值),并返回store提供的各种方法
getState
其实就是返回当前的state
dispatch
dispatch做了两件事:
- 基于reducer计算state
- 调用订阅的函数
subscribe
订阅监听函数,并返回取消订阅函数
applyMiddleware
applyMiddleware是官方实现的一个storeEnhance,用于在dispatch过程中添加中间件,核心原理就是对原有的dispatch进行包装
用法:
const logger1 = ({dispatch, getState}) => next => action => {
console.log('middleware1 start')
next(action);
console.log('middleware1 end')
}
const logger2 = ({dispatch, getState}) => next => action => {
console.log('middleware2 start')
action = next(action);
console.log('middleware2 end')
}
const store = createStore(rootReducer, preloadState, applyMiddleWare(logger1, logger2));
源码分析
我们可以看到,applyMiddleware的本质就是通过compose,包装中间件,以实现链式调用。
那么,compose是什么呢?compose是函数式编程中的一种思想,,它创建一个从右到左的数据流,右边函数执行的结果作为参数传入左边。
redux-thunk
redux-thunk 使 redux 支持asyncAction ,它经常被用于一些异步的场景中。 redux-thunk本质就是dispatch一个thunk函数,只需判断action是否为函数即可
用法:
// 下面就是一个 thunk。
function makeASandwichWithSecretSauce(forPerson) {
// 控制反转!
// 返回一个接收 `dispatch` 的函数。
// Thunk middleware 知道如何把异步的 thunk action 转为普通 action。
return function(dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
// Thunk middleware 可以让我们像 dispatch 普通 action一样 dispatch 异步的 thunk action。
store.dispatch(makeASandwichWithSecretSauce('Me'))
原理
React-redux
Redux实现了全局的状态管理,并且它与具体的UI框架不耦合,因此也可以用在其他UI框架中。React-redux是官方实现的React UI bindings,它仅用于React,并且其中做了许多工作来避免不必要的重新渲染。 它提供了两种使用Redux的方式:HOC和Hooks,分别对应Class组件和函数式组件。下面分别看看这两种方式的实现原理
HOC方式
用法
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}
TodoApp = connect(mapStateToProps, mapDispatchToProps)(TodoApp)
const store = createStore(reducer)
render(
<Provider store={store}>
<TodoApp />
</Provider>,
document.getElementById('root')
)
原理
Provider原理
Provider主要做了两件事情
- 订阅
redux的subscribe()事件 - 将
Subscription实例传入Context方便子级订阅
可以看到上面出现的createSubscription,这个函数创建一个订阅器,有发布订阅功能
connect connect建立组件和状态之间的连接,状态变化,组件更新,其中有关于避免重新渲染的处理
//connect.js
//遍历执行函数,将arg作为参数传入,如果有结果,则return
function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg)
if (result) return result
}
return (dispatch, options) => {
throw new Error(
`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
options.wrappedComponentName
}.`
)
}
}
function strictEqual(a, b) {
return a === b
}
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory,
} = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
//依次执行mapStateToPropsFactories里的函数
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
return connectHOC(selectorFactory, {
methodName: 'connect',
getDisplayName: (name) => `Connect(${name})`,
shouldHandleStateChanges: Boolean(mapStateToProps),
// 这些参数最终传入到selectorFactory中
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
})
}
}
可以看到我们传入的mapStateToProps,mapDispatchToProps、mergeProps实际上是通过了一个match()函数的包装校验
这里就以mapStateToPropsFactories也就是defaultMapStateToPropsFactories为例
顺着mapStateToPropsFactories看,可以发现执行到了wrapMapToPropsFunc函数中
//wrapMapToProp.js
//如果dependsOnOwnProps存在的话,就根据dependsOnOwnProps来确定,如果不存在,就根据mapToProps的参数个数来确定
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
export function wrapMapToPropsFunc(mapToProps, methodName) {
return function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
//dependsOnOwnProps属性决定当组件的props改变时是否需要重新调用
proxy.dependsOnOwnProps = true
//这里用了代理,detectFactoryAndVerify执行一次后,就会被mapToProps代替,目的是只需确定一次dependsOnOwnProps的值就行
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
// 第一次调用后就会被我们传入的mapToProps覆盖掉
proxy.mapToProps = mapToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
//如果props是一个函数的情况
if (typeof props === 'function') {
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, methodName)
return props
}
return proxy
}
}
确定dependsOnOwnProps的值是为了后面selectorFactory做铺垫,,selectorFactory的作用是根据state或props的变化,判断是否需要调用我们的mapStateToProps、mapDispatchToProps返回新的数据,这里就进行了一次缓存来提升性能,避免重复计算
selectorFactory
selectorFactory中就用到了dependsOnOwnProps,根据state和props变化尽可能重复计算和避免重复更新
//selectorFactory.js
//pure为false,则每次都会调用mapStateToProps方法获得新的数据
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
//pure为true,会判断值是否相同,不相同才调用,pure默认为true
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
//至少运行一次
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
//第一次调用Selector初始化把值都存下来,方便后面的比较
function handleFirstCall(firstState, firstOwnProps) {
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
function handleNewPropsAndNewState() {
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewProps() {
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
const stateChanged = !areStatesEqual(nextState, state)
//这里的state指的是store中的对象
state = nextState
ownProps = nextOwnProps
//根据state和props的变化的不同情况,执行不同的处理
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
)
}
//pure浅比较调用pureFinalPropsSelectorFactory,里面会对比是否需要更新,默认为true
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
回到connect中,可以看到最终调用的是connectAdvanced这个函数,进入这个函数里面看看
import hoistStatics from 'hoist-non-react-statics'
import React, { useContext, useMemo, useRef, useReducer } from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import { createSubscription } from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
import { ReactReduxContext } from './Context'
const EMPTY_ARRAY = []
const NO_SUBSCRIPTION_ARRAY = [null, null]
const stringifyComponent = (Comp) => {
try {
return JSON.stringify(Comp)
} catch (err) {
return String(Comp)
}
}
function storeStateUpdatesReducer(state, action) {
const [, updateCount] = state
return [action.payload, updateCount + 1]
}
function useIsomorphicLayoutEffectWithArgs(
effectFunc,
effectArgs,
dependencies
) {
useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies)
}
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
//获取wrapper props和child props,以便于之后的比较
lastWrapperProps.current = wrapperProps
lastChildProps.current = actualChildProps
renderIsScheduled.current = false
// 如果更新来自store,则清空引用并且通知子级更新
if (childPropsFromStoreUpdate.current) {
childPropsFromStoreUpdate.current = null
notifyNestedSubs()
}
}
function subscribeUpdates(
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch
) {
if (!shouldHandleStateChanges) return
let didUnsubscribe = false
let lastThrownError = null
//每当store的订阅更新传递到此组件都会运行这个回调
const checkForUpdates = () => {
if (didUnsubscribe) {
return
}
const latestStoreState = store.getState()
let newChildProps, error
try {
//计算子props
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
} catch (e) {
error = e
lastThrownError = e
}
if (!error) {
lastThrownError = null
}
// If the child props haven't changed, nothing to do here - cascade the subscription update
if (newChildProps === lastChildProps.current) {
if (!renderIsScheduled.current) {
notifyNestedSubs()
}
} else {
//如果子props发生了变化,则执行渲染
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error,
},
})
}
}
//实际订阅的是最近的父级或者是store
subscription.onStateChange = checkForUpdates
//订阅
subscription.trySubscribe()
checkForUpdates()
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
if (lastThrownError) {
throw lastThrownError
}
}
return unsubscribeWrapper
}
const initStateUpdates = () => [null, 0]
export default function connectAdvanced(
{
getDisplayName = (name) => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
//false的时候dispatch里组件也不会更新
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
context = ReactReduxContext,
...connectOptions
} = {}
) {
if (process.env.NODE_ENV !== 'production') {
if (renderCountProp !== undefined) {
throw new Error(
`renderCountProp is removed. render counting is built into the latest React Dev Tools profiling extension`
)
}
if (withRef) {
throw new Error(
'withRef is removed. To access the wrapped instance, use a ref on the connected component'
)
}
const customStoreWarningMessage =
'To use a custom Redux store for specific components, create a custom React context with ' +
"React.createContext(), and pass the context object to React Redux's Provider and specific components" +
' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
'You may also pass a {context : MyContext} option to connect'
if (storeKey !== 'store') {
throw new Error(
'storeKey has been removed and does not do anything. ' +
customStoreWarningMessage
)
}
}
const Context = context
return function wrapWithConnect(WrappedComponent) {
if (
process.env.NODE_ENV !== 'production' &&
!isValidElementType(WrappedComponent)
) {
throw new Error(
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${stringifyComponent(
WrappedComponent
)}`
)
}
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent,
}
//是否要缓存值,默认为true
const { pure } = connectOptions
function createChildSelector(store) {
//确定根据props和store中的state变更是否要重新执行mapStateToProps和mapDispatchToProps
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
//如果是非"pure"模式,则不需要缓存值,为了避免条件调用hooks,包装一层
const usePureOnlyMemo = pure ? useMemo : (callback) => callback()
//这是经过包装后,渲染在页面上的组件
function ConnectFunction(props) {
const [propsContext, reactReduxForwardedRef, wrapperProps] =
useMemo(() => {
const { reactReduxForwardedRef, ...wrapperProps } = props
return [props.context, reactReduxForwardedRef, wrapperProps]
}, [props])
const ContextToUse = useMemo(() => {
// Memoize the check that determines which context instance we should use.
// 缓存应该使用自带的context还是用户传入的context
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
const contextValue = useContext(ContextToUse)
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
if (
process.env.NODE_ENV !== 'production' &&
!didStoreComeFromProps &&
!didStoreComeFromContext
) {
throw new Error(
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
}
const store = didStoreComeFromProps ? props.store : contextValue.store
const childPropsSelector = useMemo(() => {
// The child props selector needs the store reference as an input.
// Re-create this selector whenever the store changes.
// createChildSelector需要store作为参数,在store改变的时候会重新创建
return createChildSelector(store)
}, [store])
const [subscription, notifyNestedSubs] = useMemo(() => {
//这个时候组件不会更新
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
//如果组件的store是从props里来的,就不需要传入context里的subscription
//通过这个订阅store来让组件更新
const subscription = createSubscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
//通知子组件更新的方法
const notifyNestedSubs =
subscription.notifyNestedSubs.bind(subscription)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
return contextValue
}
//否则将当前组件的subscription放入context里,确保子组件在当前组件更新完之前不会更新
return {
...contextValue,
subscription,
}
}, [didStoreComeFromProps, contextValue, subscription])
//我们需要在redux store更新的时候强制让包装组件更新
//正常情况下组件重新的渲染就是因为调用了forceComponentUpdateDispatch,而调用这个就是在订阅的事件中
const [[previousStateUpdateResult], forceComponentUpdateDispatch] =
useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
// Propagate any mapState/mapDispatch errors upwards
if (previousStateUpdateResult && previousStateUpdateResult.error) {
throw previousStateUpdateResult.error
}
const lastChildProps = useRef()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false)
//计算出真正的props
const actualChildProps = usePureOnlyMemo(() => {
//这次渲染也许是因为redux store更新产生了新props触发的
// 然而,我们也可能在这之后得到父级传入的propsn
//如果我们得到一个新的child props,和一个相同的父级传入的props,我们知道我们应该使用新的child props(不用重新计算)
//但是,如果我们有新的包装的props,这些可能会更改子props,我们就必须重新计算
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {
return childPropsFromStoreUpdate.current
}
return childPropsSelector(store.getState(), wrapperProps)
//因为previousStateUpdateResult的改变,意味者这个组件发生了重新渲染,才会重新计算actualChildProps
}, [store, previousStateUpdateResult, wrapperProps])
// useIsomorphicLayoutEffectWithArgs会根据是服务端还是浏览器端来决定到底调用useEffect还是useLayoutEffect
/**
* // 这里主要是初始化值,用做以后更新时的对比
// 还有就是调用自身的notifyNestedSubs,让子组件也更新
*/
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs,
])
/**
* 只会在store或者subscription改变时候重新订阅
这里主要绑定订阅事件
*/
useIsomorphicLayoutEffectWithArgs(
subscribeUpdates,
[
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch,
],
[store, subscription, childPropsSelector]
)
const renderedWrappedComponent = useMemo(
() => (
<WrappedComponent
{...actualChildProps}
ref={reactReduxForwardedRef}
/>
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
const renderedChild = useMemo(() => {
if (shouldHandleStateChanges) {
return (
<ContextToUse.Provider value={overriddenContextValue}>
{renderedWrappedComponent}
</ContextToUse.Provider>
)
}
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
}
//如果是pure模式,确保包裹组件只会在props改变时重渲染
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = ConnectFunction.displayName = displayName
if (forwardRef) {
const forwarded = React.forwardRef(function forwardConnectRef(
props,
ref
) {
return <Connect {...props} reactReduxForwardedRef={ref} />
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)
}
}
由上面可以看到,connect实际上会创建Provider,实现分层订阅,父组件如果需要渲染,才需要通知子组件再渲染
Hooks方式
用法
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider,useSelector,useDispatch } from 'react-redux'
const store = createStore(reducer)
const Child = () => {
const searchWords = useSelector(state => state.searchWords)
const dispatch = useDispatch()
const handleChange = (e) => {
dispatch({ type: 'UPDATE_SEARCH', payload: e.target.value })
}
return (
<div>
<input type="text" value={searchWords} onChange={handleChange} />
</div>
)
}
render( <Provider store={store}> <Child /> </Provider>, document.getElementById('root') )
原理
我们知道,Provider提供了全局的store和subscription(用于订阅),那么接下来就是需要订阅store中state的变化,useSelector,这个hook向redux subscribe了一个listener,当状态变化时被触发。
useSelector源码
//默认的比较函数
const refEquality = (a, b) => a === b
function useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub
) {
const [, forceRender] = useReducer((s) => s + 1, 0)
const subscription = useMemo(
() => createSubscription(store, contextSub),
[store, contextSub]
)
//缓存之前的结果,便于之后的比较
const latestSubscriptionCallbackError = useRef()
const latestSelector = useRef()
const latestStoreState = useRef()
const latestSelectedState = useRef()
const storeState = store.getState()
let selectedState
try {
if (
selector !== latestSelector.current ||
storeState !== latestStoreState.current ||
latestSubscriptionCallbackError.current
) {
const newSelectedState = selector(storeState)
if (
latestSelectedState.current === undefined ||
!equalityFn(newSelectedState, latestSelectedState.current)
) {
selectedState = newSelectedState
} else {
selectedState = latestSelectedState.current
}
} else {
selectedState = latestSelectedState.current
}
} catch (err) {
if (latestSubscriptionCallbackError.current) {
err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
}
throw err
}
useIsomorphicLayoutEffect(() => {
latestSelector.current = selector
latestStoreState.current = storeState
latestSelectedState.current = selectedState
latestSubscriptionCallbackError.current = undefined
})
useIsomorphicLayoutEffect(() => {
function checkForUpdates() {
//检查是否需要更新
try {
const newStoreState = store.getState()
if (newStoreState === latestStoreState.current) {
//如果storeState前后相等,则返回,不更新
return
}
const newSelectedState = latestSelector.current(newStoreState)
if (equalityFn(newSelectedState, latestSelectedState.current)) {
//如果计算出的selectedState前后相等,则返回,不更新
return
}
latestSelectedState.current = newSelectedState
latestStoreState.current = newStoreState
} catch (err) {
latestSubscriptionCallbackError.current = err
}
//真正用于更新
forceRender()
}
subscription.onStateChange = checkForUpdates
//订阅全局的subscription,全局的subscription订阅的store的变化
subscription.trySubscribe()
checkForUpdates()
return () => subscription.tryUnsubscribe()
}, [store, subscription])
return selectedState
}
可以看到,相对于HOC的方式,Hooks的方式代码更加精炼,也更加清晰
总结
Redux基于不可变数据的理念,并通过compose的方式实现中间件的链式调用。中间件的机制使得redux的扩展性非常强,这是非常好一种设计理念。 Redux只实现了状态管理,把状态更新的监听能力暴露出去。其他的比如状态缓存,状态对比,更新视图都交给特定的UI bindings,比如React redux,职责明确,值得借鉴。