React-Redux 工作原理
原理
- 发布/订阅
connect
过的组件订阅 state
变化,dispatch
通知所有订阅者更新
- 渲染时机
每次 dispatch
,reducer
产生一个新 state
对象,所有 connect
组件判断是否更新,因为是数据的浅比较,所以所需对象引用变了,则该组件重新渲染
- 性能问题
v5.0 之前存在着一个问题:父(祖先)子(孙)组件都订阅,子(孙)组件可能受到父(祖先)组件渲染影响,而导致多次渲染。v5.0 之后 发布/订阅 模块重写,解决了之前的问题,主要特点:增加层级(嵌套)观察者,保证事件通知与组件更新的顺序
v5 源码浅析
简单分析下 v5 是如何解决性能问题的:
观察者
// 源码:src/utils/Subscription.js
// 添加层级订阅
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
// 订阅
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange) // 订阅到父(向上找到最开始的祖先)组件
: this.store.subscribe(this.onStateChange) // 订阅到 redux
this.listeners = createListenerCollection()
}
}
// 通知层级订阅
notifyNestedSubs() {
this.listeners.notify()
}
// 通知
notify() {
const listeners = current = next
for (let i = 0; i < listeners.length; i++) {
listeners[i]()
}
}
订阅
通过 context
传递 subscription
,组件订阅前检查,如果父(祖先)组件已经订阅,则将子组件的回调函数 stateChange
订阅到父(向上找到最开始的祖先)组件的观察者,否则订阅到 redux store
(上面的 trySubscribe
方法)
// 源码:src/components/connectAdvanced.js
getChildContext() {
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}
componentDidMount() {
...
this.subscription.trySubscribe() // 订阅
}
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.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
发布更新
- 父(祖先)组件需要更新,则执行
setState
重新render
, 并在ComponentDidUpdate
时通知子(孙)组件更新(触发回调函数) - 父(祖先)组件不需要更新,直接通知子(孙)组件更新
// 源码:src/components/connectAdvanced.js
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs() // 通知
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState) // 更新自身
}
}
notifyNestedSubsOnComponentDidUpdate() {
// 在 componentDidUpdate 方法通知,确保自身与子组件渲染之后再通知
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}
疑问与总结
父(祖先)组件setState
明明可能会让子(孙)组件更新,为什么还需要通知一次?
必要性:因为存在这样的场景:子组件可能不更新(
ShouldComponentUpdate
返回false
),则需要更新的孙子组件没有受setState
影响,为了保证所有订阅的组件都能得到数据更新,所以需要通知无副作用:假设子组件因为父组件
render
而重新render
,因为通知是在父组件componentDidUpdate
方法触发,而子组件因为已经渲染过,数据得到了更新,所以就算再收到了通知也不会再渲染了