Skip to content

Commit 0a29990

Browse files
authored
Convert Context/Provider/Subscription to Typescript (#1742)
1 parent 66f1152 commit 0a29990

File tree

8 files changed

+99
-77
lines changed

8 files changed

+99
-77
lines changed

rollup.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import pkg from './package.json'
77

88
const env = process.env.NODE_ENV
99

10-
const extensions = ['.js', '.ts', '.json']
10+
const extensions = ['.js', '.ts', '.tsx', '.json']
1111

1212
const config = {
1313
input: 'src/index.js',

src/components/Context.js

-9
This file was deleted.

src/components/Context.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
import { Action, AnyAction, Store } from 'redux'
3+
import type { FixTypeLater } from '../types'
4+
import type Subscription from '../utils/Subscription'
5+
6+
export interface ReactReduxContextValue<SS = FixTypeLater, A extends Action = AnyAction> {
7+
store: Store<SS, A>
8+
subscription: Subscription
9+
}
10+
11+
export const ReactReduxContext = /*#__PURE__*/ React.createContext<ReactReduxContextValue | null>(null)
12+
13+
if (process.env.NODE_ENV !== 'production') {
14+
ReactReduxContext.displayName = 'ReactRedux'
15+
}
16+
17+
export default ReactReduxContext

src/components/Provider.js

-49
This file was deleted.

src/components/Provider.tsx

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { Context, ReactNode, useMemo } from 'react'
2+
import { ReactReduxContext, ReactReduxContextValue } from './Context'
3+
import Subscription from '../utils/Subscription'
4+
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
5+
import type { FixTypeLater } from '../types'
6+
import { Action, AnyAction, Store } from 'redux'
7+
8+
interface ProviderProps<A extends Action = AnyAction> {
9+
/**
10+
* The single Redux store in your application.
11+
*/
12+
store: Store<FixTypeLater, A>
13+
/**
14+
* Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used.
15+
* If this is used, generate own connect HOC by using connectAdvanced, supplying the same context provided to the
16+
* Provider. Initial value doesn't matter, as it is overwritten with the internal state of Provider.
17+
*/
18+
context?: Context<ReactReduxContextValue>
19+
children: ReactNode
20+
}
21+
22+
function Provider({ store, context, children }: ProviderProps) {
23+
const contextValue = useMemo(() => {
24+
const subscription = new Subscription(store)
25+
subscription.onStateChange = subscription.notifyNestedSubs
26+
return {
27+
store,
28+
subscription,
29+
}
30+
}, [store])
31+
32+
const previousState = useMemo(() => store.getState(), [store])
33+
34+
useIsomorphicLayoutEffect(() => {
35+
const { subscription } = contextValue
36+
subscription.trySubscribe()
37+
38+
if (previousState !== store.getState()) {
39+
subscription.notifyNestedSubs()
40+
}
41+
return () => {
42+
subscription.tryUnsubscribe()
43+
subscription.onStateChange = undefined
44+
}
45+
}, [contextValue, previousState])
46+
47+
const Context = context || ReactReduxContext
48+
49+
return <Context.Provider value={contextValue}>{children}</Context.Provider>
50+
}
51+
52+
export default Provider

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type FixTypeLater = any

src/utils/Subscription.js renamed to src/utils/Subscription.ts

+27-17
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import { getBatch } from './batch'
44
// well as nesting subscriptions of descendant components, so that we can ensure the
55
// ancestor components re-render before descendants
66

7-
const nullListeners = { notify() {} }
7+
type Listener = {
8+
callback: () => void
9+
next: Listener | null
10+
prev: Listener | null
11+
}
812

913
function createListenerCollection() {
1014
const batch = getBatch()
11-
let first = null
12-
let last = null
15+
let first: Listener | null = null
16+
let last: Listener | null = null
1317

1418
return {
1519
clear() {
@@ -37,10 +41,10 @@ function createListenerCollection() {
3741
return listeners
3842
},
3943

40-
subscribe(callback) {
44+
subscribe(callback: () => void) {
4145
let isSubscribed = true
4246

43-
let listener = (last = {
47+
let listener: Listener = (last = {
4448
callback,
4549
next: null,
4650
prev: last,
@@ -71,29 +75,35 @@ function createListenerCollection() {
7175
}
7276
}
7377

78+
type ListenerCollection = ReturnType<typeof createListenerCollection>
79+
7480
export default class Subscription {
75-
constructor(store, parentSub) {
81+
private store: any
82+
private parentSub?: Subscription
83+
private unsubscribe?: () => void
84+
private listeners?: ListenerCollection
85+
public onStateChange?: () => void
86+
87+
constructor(store: any, parentSub?: Subscription) {
7688
this.store = store
7789
this.parentSub = parentSub
78-
this.unsubscribe = null
79-
this.listeners = nullListeners
90+
this.unsubscribe = undefined
91+
this.listeners = undefined
8092

8193
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
8294
}
8395

84-
addNestedSub(listener) {
96+
addNestedSub(listener: () => void) {
8597
this.trySubscribe()
86-
return this.listeners.subscribe(listener)
98+
return this.listeners?.subscribe(listener)
8799
}
88100

89101
notifyNestedSubs() {
90-
this.listeners.notify()
102+
this.listeners?.notify()
91103
}
92104

93105
handleChangeWrapper() {
94-
if (this.onStateChange) {
95-
this.onStateChange()
96-
}
106+
this.onStateChange?.()
97107
}
98108

99109
isSubscribed() {
@@ -113,9 +123,9 @@ export default class Subscription {
113123
tryUnsubscribe() {
114124
if (this.unsubscribe) {
115125
this.unsubscribe()
116-
this.unsubscribe = null
117-
this.listeners.clear()
118-
this.listeners = nullListeners
126+
this.unsubscribe = undefined
127+
this.listeners?.clear()
128+
this.listeners = undefined
119129
}
120130
}
121131
}

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// "lib": [], /* Specify library files to be included in the compilation. */
1010
"allowJs": true, /* Allow javascript files to be compiled. */
1111
// "checkJs": true, /* Report errors in .js files. */
12-
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
12+
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
1313
"declaration": true, /* Generates corresponding '.d.ts' file. */
1414
"emitDeclarationOnly": true,
1515
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */

0 commit comments

Comments
 (0)