Skip to content

Commit

Permalink
use context changed bits to observe state slices in parts
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Sep 16, 2018
1 parent ede6245 commit b018b98
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 57 deletions.
39 changes: 38 additions & 1 deletion src/components/Context.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import React from 'react'

const Context = React.createContext(null)
const INDEX_SIZE = 31-3;

const allMarked = () => 0xFFFF << 2;

export const createHashFunction = (state) => {
if (Array.isArray(state)) {
return allMarked;
}
const keys = {};
Object
.keys(state)
.forEach((key, index) => keys[key] = 1 << (3 + index % INDEX_SIZE))

return (hash, key) => hash | keys[key];
}

const Context = React.createContext(null, (prev, next) => {
let result = 1; // one bit always changed 1 to enable non pure connect

if (typeof prev.state !== 'object' || Array.isArray(next.state)) {
return 0xFFFFFF
}
// foo has been changed
if (prev.store !== next.store) {
result |= 2
}
if (prev.hashFunction !== next.hashFunction) {
result |= 4
}
Object
.keys(prev.state)
.forEach(key => {
if (prev.state[key] !== next.state[key]) {
result = next.hashFunction(result, key)
}
})
return result
})

export default Context
20 changes: 12 additions & 8 deletions src/components/Provider.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Context from './Context'
import Context, { createHashFunction } from './Context'

const ContextProvider = Context.Provider

class Provider extends Component {
constructor(props) {
super(props)
const state = props.store.getState()
this.state = {
state: props.store.getState(),
store: props.store
state,
store: props.store,
hashFunction: createHashFunction(state)
}
this.unsubscribe = null
}
Expand All @@ -33,9 +35,11 @@ class Provider extends Component {
if (lastProps.store !== this.props.store) {
if (this.unsubscribe) this.unsubscribe()
this.unsubscribe = this.props.store.subscribe(this.triggerUpdateOnStoreStateChange.bind(this))
const state = this.props.store.getState()
this.setState({
state: this.props.store.getState(),
store: this.props.store
state,
store: this.props.store,
hashFunction: createHashFunction(state)
})
}
}
Expand All @@ -46,12 +50,12 @@ class Provider extends Component {
}

this.setState(prevState => {
const newState = prevState.store.getState()
if (prevState.state === newState) {
const state = prevState.store.getState()
if (prevState.state === state) {
return null
}
return {
state: newState
state
}
})
}
Expand Down
155 changes: 107 additions & 48 deletions src/components/connectAdvanced.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'
import React, { Component, PureComponent } from 'react'
import React, {Component, PureComponent} from 'react'
import propTypes from 'prop-types'
import { isValidElementType } from 'react-is'
import {isValidElementType} from 'react-is'

import Context from './Context'

const ReduxConsumer = Context.Consumer

const createGetter = (source, callback) => {
const getter = {}

Object
.getOwnPropertyNames(source)
.forEach(key => getter[key] = {
get: () => callback(key),
enumerable: true
})

return getter
};

export default function connectAdvanced(
/*
selectorFactory is a func that is responsible for returning the selector function used to
Expand Down Expand Up @@ -87,41 +100,34 @@ export default function connectAdvanced(

const displayName = getDisplayName(wrappedComponentName)

let PureWrapper

if (withRef) {
class PureWrapperRef extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.derivedProps !== this.props.derivedProps
}

render() {
let { forwardRef, derivedProps } = this.props
return <WrappedComponent {...derivedProps} ref={forwardRef} />
}
}
PureWrapperRef.propTypes = {
derivedProps: propTypes.object,
forwardRef: propTypes.oneOfType([
propTypes.func,
propTypes.object
])
class PureWrapper extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.derivedProps !== this.props.derivedProps
}
PureWrapper = PureWrapperRef
} else {
class PureWrapperNoRef extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.derivedProps !== this.props.derivedProps
}

render() {
return <WrappedComponent {...this.props.derivedProps} />
componentDidUpdate(prevProps) {
if (prevProps.observedBits !== this.props.observedBits) {
this.props.setObservedBits(this.props.observedBits)
}
}
PureWrapperNoRef.propTypes = {
derivedProps: propTypes.object,

render() {
let {forwardRef, derivedProps} = this.props
return withRef
? <WrappedComponent {...derivedProps} ref={forwardRef}/>
: <WrappedComponent {...derivedProps}/>
}
PureWrapper = PureWrapperNoRef
}

PureWrapper.propTypes = {
observedProps: propTypes.number,
setObservedBits: propTypes.func,
derivedProps: propTypes.object,
forwardRef: propTypes.oneOfType([
propTypes.func,
propTypes.object
]),
}

const selectorFactoryOptions = {
Expand Down Expand Up @@ -151,30 +157,70 @@ export default function connectAdvanced(
)
this.generatedDerivedProps = this.makeDerivedPropsGenerator()
this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
this.setObservedBits = this.setObservedBits.bind(this)

this.state = {
observedBits: 0xFFFFFFFF
}
}

setObservedBits(bits) {
if (!connectOptions.observer && (bits || !selectorFactory)) {
this.setState(state => {
if (state.observedBits !== bits) {
return {
observedBits: 6 | bits
}
}
return null;
})
}
}

makeDerivedPropsGenerator() {
let lastProps
let lastState
let lastDerivedProps
let lastStore
let lastHashFunction
let sourceSelector
return (state, props, store) => {
let stateGetter
let observedBits
return (state, props, store, hashFunction) => {
if ((connectOptions.pure && lastProps === props) && (lastState === state)) {
return lastDerivedProps
}
if (store !== lastStore) {
lastStore = store
sourceSelector = selectorFactory(store.dispatch, selectorFactoryOptions)
}

if (hashFunction !== lastHashFunction) {
stateGetter = createGetter(state, key => {
observedBits = hashFunction(observedBits, key)
return lastState[key]
})
lastHashFunction = hashFunction
}
lastProps = props
lastState = state
const nextProps = sourceSelector(state, props)
if (lastDerivedProps === nextProps) {
return lastDerivedProps

observedBits = 0
const couldProxyState = typeof state === 'object' && !Array.isArray(state)
if (couldProxyState) {
const stateProxy = {}
Object.defineProperties(stateProxy, stateGetter);
lastDerivedProps = sourceSelector(stateProxy, props)
return {
derivedProps: lastDerivedProps,
observedBits
}
}
lastDerivedProps = sourceSelector(state, props)
return {
derivedProps: lastDerivedProps,
observedBits: 0xFFFFFFFF
}
lastDerivedProps = nextProps
return lastDerivedProps
}
}

Expand All @@ -185,14 +231,21 @@ export default function connectAdvanced(
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
const { state, store } = value
const { forwardRef, props } = this.props
let derivedProps = this.generatedDerivedProps(state, props, store)
const {state, store, hashFunction} = value
const {forwardRef, props} = this.props
let {derivedProps, observedBits} = this.generatedDerivedProps(state, props, store, hashFunction)
if (connectOptions.pure) {
return <PureWrapper derivedProps={derivedProps} forwardRef={forwardRef} />
return (
<PureWrapper
derivedProps={derivedProps}
observedBits={observedBits}
setObservedBits={this.setObservedBits}
forwardRef={forwardRef}
/>
)
}

return <WrappedComponent {...derivedProps} ref={forwardRef} />
return <WrappedComponent {...derivedProps} ref={forwardRef}/>
}

renderWrappedComponent(value) {
Expand All @@ -202,19 +255,25 @@ export default function connectAdvanced(
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
const { state, store } = value
let derivedProps = this.generatedDerivedProps(state, this.props, store)
const {state, store, hashFunction} = value
let {derivedProps, observedBits} = this.generatedDerivedProps(state, this.props, store, hashFunction)
if (connectOptions.pure) {
return <PureWrapper derivedProps={derivedProps} />
return (
<PureWrapper
derivedProps={derivedProps}
observedBits={observedBits}
setObservedBits={this.setObservedBits}
/>
)
}

return <WrappedComponent {...derivedProps} />
}

render() {
if (this.props.unstable_observedBits) {
if (this.state.observedBits) {
return (
<Consumer unstable_observedBits={this.props.unstable_observedBits}>
<Consumer unstable_observedBits={this.state.observedBits}>
{this.renderWrappedComponent}
</Consumer>
)
Expand Down Expand Up @@ -245,7 +304,7 @@ export default function connectAdvanced(
}

function forwardRef(props, ref) {
return <Connect props={props} forwardRef={ref} />
return <Connect props={props} forwardRef={ref}/>
}

const forwarded = React.forwardRef(forwardRef)
Expand Down

0 comments on commit b018b98

Please sign in to comment.