Skip to content

Commit b018b98

Browse files
committed
use context changed bits to observe state slices in parts
1 parent ede6245 commit b018b98

File tree

3 files changed

+157
-57
lines changed

3 files changed

+157
-57
lines changed

src/components/Context.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
import React from 'react'
22

3-
const Context = React.createContext(null)
3+
const INDEX_SIZE = 31-3;
4+
5+
const allMarked = () => 0xFFFF << 2;
6+
7+
export const createHashFunction = (state) => {
8+
if (Array.isArray(state)) {
9+
return allMarked;
10+
}
11+
const keys = {};
12+
Object
13+
.keys(state)
14+
.forEach((key, index) => keys[key] = 1 << (3 + index % INDEX_SIZE))
15+
16+
return (hash, key) => hash | keys[key];
17+
}
18+
19+
const Context = React.createContext(null, (prev, next) => {
20+
let result = 1; // one bit always changed 1 to enable non pure connect
21+
22+
if (typeof prev.state !== 'object' || Array.isArray(next.state)) {
23+
return 0xFFFFFF
24+
}
25+
// foo has been changed
26+
if (prev.store !== next.store) {
27+
result |= 2
28+
}
29+
if (prev.hashFunction !== next.hashFunction) {
30+
result |= 4
31+
}
32+
Object
33+
.keys(prev.state)
34+
.forEach(key => {
35+
if (prev.state[key] !== next.state[key]) {
36+
result = next.hashFunction(result, key)
37+
}
38+
})
39+
return result
40+
})
441

542
export default Context

src/components/Provider.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React, { Component } from 'react'
22
import PropTypes from 'prop-types'
3-
import Context from './Context'
3+
import Context, { createHashFunction } from './Context'
44

55
const ContextProvider = Context.Provider
66

77
class Provider extends Component {
88
constructor(props) {
99
super(props)
10+
const state = props.store.getState()
1011
this.state = {
11-
state: props.store.getState(),
12-
store: props.store
12+
state,
13+
store: props.store,
14+
hashFunction: createHashFunction(state)
1315
}
1416
this.unsubscribe = null
1517
}
@@ -33,9 +35,11 @@ class Provider extends Component {
3335
if (lastProps.store !== this.props.store) {
3436
if (this.unsubscribe) this.unsubscribe()
3537
this.unsubscribe = this.props.store.subscribe(this.triggerUpdateOnStoreStateChange.bind(this))
38+
const state = this.props.store.getState()
3639
this.setState({
37-
state: this.props.store.getState(),
38-
store: this.props.store
40+
state,
41+
store: this.props.store,
42+
hashFunction: createHashFunction(state)
3943
})
4044
}
4145
}
@@ -46,12 +50,12 @@ class Provider extends Component {
4650
}
4751

4852
this.setState(prevState => {
49-
const newState = prevState.store.getState()
50-
if (prevState.state === newState) {
53+
const state = prevState.store.getState()
54+
if (prevState.state === state) {
5155
return null
5256
}
5357
return {
54-
state: newState
58+
state
5559
}
5660
})
5761
}

src/components/connectAdvanced.js

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import hoistStatics from 'hoist-non-react-statics'
22
import invariant from 'invariant'
3-
import React, { Component, PureComponent } from 'react'
3+
import React, {Component, PureComponent} from 'react'
44
import propTypes from 'prop-types'
5-
import { isValidElementType } from 'react-is'
5+
import {isValidElementType} from 'react-is'
66

77
import Context from './Context'
88

99
const ReduxConsumer = Context.Consumer
1010

11+
const createGetter = (source, callback) => {
12+
const getter = {}
13+
14+
Object
15+
.getOwnPropertyNames(source)
16+
.forEach(key => getter[key] = {
17+
get: () => callback(key),
18+
enumerable: true
19+
})
20+
21+
return getter
22+
};
23+
1124
export default function connectAdvanced(
1225
/*
1326
selectorFactory is a func that is responsible for returning the selector function used to
@@ -87,41 +100,34 @@ export default function connectAdvanced(
87100

88101
const displayName = getDisplayName(wrappedComponentName)
89102

90-
let PureWrapper
91-
92-
if (withRef) {
93-
class PureWrapperRef extends Component {
94-
shouldComponentUpdate(nextProps) {
95-
return nextProps.derivedProps !== this.props.derivedProps
96-
}
97103

98-
render() {
99-
let { forwardRef, derivedProps } = this.props
100-
return <WrappedComponent {...derivedProps} ref={forwardRef} />
101-
}
102-
}
103-
PureWrapperRef.propTypes = {
104-
derivedProps: propTypes.object,
105-
forwardRef: propTypes.oneOfType([
106-
propTypes.func,
107-
propTypes.object
108-
])
104+
class PureWrapper extends Component {
105+
shouldComponentUpdate(nextProps) {
106+
return nextProps.derivedProps !== this.props.derivedProps
109107
}
110-
PureWrapper = PureWrapperRef
111-
} else {
112-
class PureWrapperNoRef extends Component {
113-
shouldComponentUpdate(nextProps) {
114-
return nextProps.derivedProps !== this.props.derivedProps
115-
}
116108

117-
render() {
118-
return <WrappedComponent {...this.props.derivedProps} />
109+
componentDidUpdate(prevProps) {
110+
if (prevProps.observedBits !== this.props.observedBits) {
111+
this.props.setObservedBits(this.props.observedBits)
119112
}
120113
}
121-
PureWrapperNoRef.propTypes = {
122-
derivedProps: propTypes.object,
114+
115+
render() {
116+
let {forwardRef, derivedProps} = this.props
117+
return withRef
118+
? <WrappedComponent {...derivedProps} ref={forwardRef}/>
119+
: <WrappedComponent {...derivedProps}/>
123120
}
124-
PureWrapper = PureWrapperNoRef
121+
}
122+
123+
PureWrapper.propTypes = {
124+
observedProps: propTypes.number,
125+
setObservedBits: propTypes.func,
126+
derivedProps: propTypes.object,
127+
forwardRef: propTypes.oneOfType([
128+
propTypes.func,
129+
propTypes.object
130+
]),
125131
}
126132

127133
const selectorFactoryOptions = {
@@ -151,30 +157,70 @@ export default function connectAdvanced(
151157
)
152158
this.generatedDerivedProps = this.makeDerivedPropsGenerator()
153159
this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
160+
this.setObservedBits = this.setObservedBits.bind(this)
161+
162+
this.state = {
163+
observedBits: 0xFFFFFFFF
164+
}
165+
}
166+
167+
setObservedBits(bits) {
168+
if (!connectOptions.observer && (bits || !selectorFactory)) {
169+
this.setState(state => {
170+
if (state.observedBits !== bits) {
171+
return {
172+
observedBits: 6 | bits
173+
}
174+
}
175+
return null;
176+
})
177+
}
154178
}
155179

156180
makeDerivedPropsGenerator() {
157181
let lastProps
158182
let lastState
159183
let lastDerivedProps
160184
let lastStore
185+
let lastHashFunction
161186
let sourceSelector
162-
return (state, props, store) => {
187+
let stateGetter
188+
let observedBits
189+
return (state, props, store, hashFunction) => {
163190
if ((connectOptions.pure && lastProps === props) && (lastState === state)) {
164191
return lastDerivedProps
165192
}
166193
if (store !== lastStore) {
167194
lastStore = store
168195
sourceSelector = selectorFactory(store.dispatch, selectorFactoryOptions)
169196
}
197+
198+
if (hashFunction !== lastHashFunction) {
199+
stateGetter = createGetter(state, key => {
200+
observedBits = hashFunction(observedBits, key)
201+
return lastState[key]
202+
})
203+
lastHashFunction = hashFunction
204+
}
170205
lastProps = props
171206
lastState = state
172-
const nextProps = sourceSelector(state, props)
173-
if (lastDerivedProps === nextProps) {
174-
return lastDerivedProps
207+
208+
observedBits = 0
209+
const couldProxyState = typeof state === 'object' && !Array.isArray(state)
210+
if (couldProxyState) {
211+
const stateProxy = {}
212+
Object.defineProperties(stateProxy, stateGetter);
213+
lastDerivedProps = sourceSelector(stateProxy, props)
214+
return {
215+
derivedProps: lastDerivedProps,
216+
observedBits
217+
}
218+
}
219+
lastDerivedProps = sourceSelector(state, props)
220+
return {
221+
derivedProps: lastDerivedProps,
222+
observedBits: 0xFFFFFFFF
175223
}
176-
lastDerivedProps = nextProps
177-
return lastDerivedProps
178224
}
179225
}
180226

@@ -185,14 +231,21 @@ export default function connectAdvanced(
185231
`or pass a custom React context provider to <Provider> and the corresponding ` +
186232
`React context consumer to ${displayName} in connect options.`
187233
)
188-
const { state, store } = value
189-
const { forwardRef, props } = this.props
190-
let derivedProps = this.generatedDerivedProps(state, props, store)
234+
const {state, store, hashFunction} = value
235+
const {forwardRef, props} = this.props
236+
let {derivedProps, observedBits} = this.generatedDerivedProps(state, props, store, hashFunction)
191237
if (connectOptions.pure) {
192-
return <PureWrapper derivedProps={derivedProps} forwardRef={forwardRef} />
238+
return (
239+
<PureWrapper
240+
derivedProps={derivedProps}
241+
observedBits={observedBits}
242+
setObservedBits={this.setObservedBits}
243+
forwardRef={forwardRef}
244+
/>
245+
)
193246
}
194247

195-
return <WrappedComponent {...derivedProps} ref={forwardRef} />
248+
return <WrappedComponent {...derivedProps} ref={forwardRef}/>
196249
}
197250

198251
renderWrappedComponent(value) {
@@ -202,19 +255,25 @@ export default function connectAdvanced(
202255
`or pass a custom React context provider to <Provider> and the corresponding ` +
203256
`React context consumer to ${displayName} in connect options.`
204257
)
205-
const { state, store } = value
206-
let derivedProps = this.generatedDerivedProps(state, this.props, store)
258+
const {state, store, hashFunction} = value
259+
let {derivedProps, observedBits} = this.generatedDerivedProps(state, this.props, store, hashFunction)
207260
if (connectOptions.pure) {
208-
return <PureWrapper derivedProps={derivedProps} />
261+
return (
262+
<PureWrapper
263+
derivedProps={derivedProps}
264+
observedBits={observedBits}
265+
setObservedBits={this.setObservedBits}
266+
/>
267+
)
209268
}
210269

211270
return <WrappedComponent {...derivedProps} />
212271
}
213272

214273
render() {
215-
if (this.props.unstable_observedBits) {
274+
if (this.state.observedBits) {
216275
return (
217-
<Consumer unstable_observedBits={this.props.unstable_observedBits}>
276+
<Consumer unstable_observedBits={this.state.observedBits}>
218277
{this.renderWrappedComponent}
219278
</Consumer>
220279
)
@@ -245,7 +304,7 @@ export default function connectAdvanced(
245304
}
246305

247306
function forwardRef(props, ref) {
248-
return <Connect props={props} forwardRef={ref} />
307+
return <Connect props={props} forwardRef={ref}/>
249308
}
250309

251310
const forwarded = React.forwardRef(forwardRef)

0 commit comments

Comments
 (0)