react-redux源码分析及实现原型_下

上一次我们讲解了Provider、connect、selectorFactory。这次主要分析 connectAdvanced 这个核心API。

connectAdvanced

在开始之前我们先来看一个工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function makeSelectorStateful(sourceSelector, store) {
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
}

上篇讲过 selector 会将新的值和缓存的值做比较,如果变化,将重新求值并返回,如果没变化,返回缓存的旧值。makeSelectorStateful 函数是对 selector 的封装。正如其名字一样,使selector stateful

再介绍一下hoist-non-react-statics这个库,作用是避免在使用HOC时,导致类的static方法丢失的问题。详情见react doc

Subscription是实现react与redux绑定的类,在接下来会用到我们先来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export default class Subscription {
constructor(store, parentSub, onStateChange) {
this.store = store
this.parentSub = parentSub
this.onStateChange = onStateChange
this.unsubscribe = null
this.listeners = nullListeners
}

addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}

notifyNestedSubs() {
this.listeners.notify()
}

isSubscribed() {
return Boolean(this.unsubscribe)
}

trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange)
: this.store.subscribe(this.onStateChange)

this.listeners = createListenerCollection()
}
}

tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}

重点在 trySubscribe 方法,如果 parentSub 存在就将回调函数绑定在父组件上,否则绑定在store.subscribe中
原因是这样可以保证组件的更新顺序,从父到子。

然后可以开始 connectAdvanced

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
export default function connectAdvanced(
selectorFactory,
{
shouldHandleStateChanges = true,
storeKey = 'store',
// 传递给 selectorFactory 的参数
...connectOptions
} = {}
) {
return function wrapWithConnect(WrappedComponent) {

//...

const selectorFactoryOptions = {
...connectOptions,
shouldHandleStateChanges,
// ...
}

class Connect extends Component {
constructor(props, context) {
super(props, context)

this.version = version
this.state = {}
this.renderCount = 0
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)、

// selector 与 subscription 的初始化
this.initSelector()
this.initSubscription()
}

getChildContext() {
// 如果组件从props里获得store,那么将 context 中的 subscription 传递下去
// 否则就将传递此组件中的 subscription
// 子组件使用祖先组件的 subscription 可以保证组件的更新顺序(父 -> 子)。
// 另外 将store通过props传递下去,这种场景是什么。。。
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}

componentDidMount() {
// shouldHandleStateChanges === Boolean(mapStateToProps)
// 如果没有 mapStateToProps 组件不需要监听store变化
if (!shouldHandleStateChanges) return

// 由于 componentWillMount 会在ssr中触发,而 componentDidMount、componentWillUnmount不会。
// 如果将subscription放在 componentWillMount中,那么 unsubscription 将不会被触发,将会导致内存泄漏。
this.subscription.trySubscribe()

// 为了防止子组件在 componentWillMount 中调用dipatch 所以这里需要在重新计算一次
// 因为子组件的 componentWillMount 先于组件的 componentDidMount 发生,此时还没有执行 trySubscribe
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}

componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}

shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}

componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
this.subscription = null
this.notifyNestedSubs = noop
this.store = null
this.selector.run = noop
this.selector.shouldComponentUpdate = false
}

initSelector() {
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}

initSubscription() {
if (!shouldHandleStateChanges) return

const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))

// 这里是防止组件在通知过程中卸载,此时this.subscription就为null了。这里将notifyNestedSubs拷贝一次。
// 并且在componentWillUnmount 中 this.notifyNestedSubs = noop,
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}

onStateChange() {
this.selector.run(this.props)

if (!this.selector.shouldComponentUpdate) {
// 如果不需要更新则通知子组件
this.notifyNestedSubs()
} else {
// 如果需要更新则在更新之后,再通知子组件
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate

// 组件更新
this.setState(dummyState)
}
}

notifyNestedSubsOnComponentDidUpdate() {
// 避免重复通知
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}

isSubscribed() {
return Boolean(this.subscription) && this.subscription.isSubscribed()
}

render() {
const selector = this.selector
selector.shouldComponentUpdate = false

if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}

/* eslint-enable react/no-deprecated */

Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes

return hoistStatics(Connect, WrappedComponent)
}
}

over~ 是不是觉得react-redux很简单?
接下来我会将react技术栈中常用的库(react, react-router, redux, redux-saga, dva)源码分析一遍,喜欢的话。可以watch me!