Context
coderljw 2024-10-13 大约 2 分钟
# 1. Context
- index.tsx
import {
createContext as reactCreateContext,
useEffect,
useContext as useReactContext,
useReducer,
useRef,
} from 'react'
import isEqual from 'react-fast-compare'
import { createReducer } from './reducer'
import type { InitialReducer } from './reducer'
export type CreateContextParams<S extends State = State, R extends Reducers<S> = Reducers<S>> = {
initialState?: S
reducers?: R
}
export const createContext = <S extends State, R extends Reducers<S>>({
initialState = {} as S,
reducers = {} as R,
}: CreateContextParams<S, R>) => {
const reducer = createReducer<S, R>(reducers)
type Dispatch = React.Dispatch<Parameters<typeof reducer>[1]>
const Context = reactCreateContext({} as { state: S; dispatch: Dispatch })
const useContext = () => useReactContext(Context)
const context =
<P extends Record<string, any> = Record<string, any>>(Component: React.FC<P>) =>
(props: P) => {
const [state, dispatch] = useReducer(
reducer as Reducer<S, InitialReducer<S>, S>,
initialState,
)
return (
<Context.Provider value={{ state, dispatch: dispatch as Dispatch }}>
<Component {...props} />
</Context.Provider>
)
}
const useBond = (dep: S) => {
const prevDep = useRef(dep)
const { dispatch } = useContext()
useEffect(() => {
if (!dep || typeof dep !== 'object') {
console.warn(
'The useBond dep has no value or non-object type; the expected value is an object of type State.',
)
return
}
dispatch({ type: 'state', payload: dep })
prevDep.current = dep
}, [isEqual(dep, prevDep.current)])
}
return { Context, context, useContext, useBond }
}
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
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
- reducer
import { produce } from 'immer'
export type InitialReducer<S extends State = State> = {
type: 'state'
payload: S
}
export const createReducer = <S extends State = State, R extends Reducers<S> = Reducers<S>>(
reducers = {} as R,
) => {
const initialReducers = {
state: (state: S, payload: S) => {
return produce(state, (draft) => {
Object.assign(draft, payload)
})
},
}
const externalReducers = Object.entries(reducers).reduce((acc, [type, reducer]) => {
return {
...acc,
[type]: (state: S, payload: Parameters<typeof reducer>[1]) => {
return produce(state, (draft) => {
reducer(draft as S, payload)
})
},
}
}, {} as R)
const mergeReducers = { ...initialReducers, ...externalReducers }
const reducer: Reducer<S, Action<S, typeof mergeReducers>> = (state, action) => {
// @ts-ignore
return mergeReducers[action.type]?.(state, action.payload)
}
return reducer
}
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
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
- types.d.ts
type State = Record<string, any>
type Reducer<S extends State = State, P = any, R = void> = (state: S, payload: P) => R
type Reducers<S extends State = State> = Record<string, Reducer<S, Parameters<Reducer>[1]>>
type ActionTypeMap<S extends State = State, R extends Reducers<S> = Reducers<S>> = {
[K in keyof R]: Parameters<R[K]>[1] extends undefined
? { type: K }
: { type: K; payload: Parameters<R[K]>[1] }
}
type Action<S extends State = State, R extends Reducers<S> = Reducers<S>> = ActionTypeMap<
S,
R
>[keyof ActionTypeMap<S, R>]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2. Zustand-Context
import React, { useEffect, useRef } from 'react'
import isEqual from 'react-fast-compare'
import { createStore, useStore as zUseStore } from 'zustand'
import type { StateCreator, StoreApi } from 'zustand'
export const createContext = <S extends Record<string, any>>(initializer: StateCreator<S>) => {
const Context = React.createContext<StoreApi<S>>({} as StoreApi<S>)
const context =
<P extends Record<string, any> = Record<string, any>>(Component: React.FC<P>) =>
(props: P) => {
const [store] = React.useState(() => createStore<S>(initializer))
return (
<Context.Provider value={store}>
<Component {...props} />
</Context.Provider>
)
}
const useStore = <U = S,>(selector?: (state: S) => U) => {
const store = React.useContext(Context)
return zUseStore(store, selector!)
}
const useBond = (dep: S) => {
const prevDep = useRef(dep)
const { setState } = React.useContext(Context)
useEffect(() => {
if (!dep || typeof dep !== 'object') {
console.warn(
'The useBond dep has no value or non-object type; the expected value is an object of type State.',
)
return
}
setState(dep)
prevDep.current = dep
}, [isEqual(dep, prevDep.current)])
}
return { Context, context, useStore, useBond }
}
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
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