创建上下文
import React, {
createContext, useContext, useRef,
useMemo, forwardRef, useReducer
} from 'react';
import { shallowEqual } from 'react-redux';
const Ctx = createContext<any>({});
export const Provider = (props: any) => {
const ref = useRef<any>();
ref.current = props.reducers;
const [state, dispatch] = useReducer((state: any, action: any) => {
const { type, ...actionData } = action;
const reducer = ref.current[type];
const result = reducer(state, actionData);
if (!shallowEqual(result, state)) {
return result;
}
return state;
}, props.initialState);
const store = useMemo(() => {
return { state, dispatch };
}, [state]);
return <Ctx.Provider value={store}>{props.children}</Ctx.Provider>;
};
export const useCtx = () => {
return useContext(Ctx);
};
selector +connect
export const useSelector = (selector: any, props?: any) => {
const ref = useRef<any>();
ref.current = selector;
const { state } = useCtx();
return useMemo<any>(() => {
return ref.current(state, props);
}, [state, props]);
};
export const connect = (mapStateToProps?: any, mapDispatchToProps: any) => {
return (Comp: React.ComponentType) => {
return forwardRef((props, ref) => {
const dispatchRef = useRef<any>();
const ctx = useCtx();
dispatchRef.current = mapDispatchToProps(ctx.dispatch, props);
const state = useSelector(mapStateToProps, props);
return useMemo(() => {
const fullProps = {
...props,
...(state || {}),
...(dispatchRef.current || {})
};
return <Comp {...fullProps} ref={ref} />;
}, [props, ref, state]);
});
};
};
使用
components.jsx
import { useCtx, useSelector, connect } from './ctx';
export const AddBtn = () => {
const ctx = useCtx();
return (
<section>
<button
onClick={() => {
ctx.dispatch({ type: 'add', value: Math.random() });
}}
>
click
</button>
</section>
);
};
export const ShowValue = () => {
const ctx = useCtx();
return (
<section>
<span>{JSON.stringify(ctx.state)}</span>
</section>
);
};
let renderConter = 0;
export const EmptyListener = () => {
const ctx = useSelector();
console.log('log---------empty', ctx);
renderConter++;
return (
<section style={{ marginTop: 10 }}>
<ul>
<li>使用useSelector一定会触发渲染,因为内部对引用了ctx</li>
</ul>
<span>empty listener:{renderConter}</span>
</section>
);
};
export const SelectorComp = connect((state) => {
return state.value
})((props: any) => {
console.log('log---------SelectorComp', props);
return (
<section>
<span>SelectorComp:{renderConter}</span>
</section>
);
});
index
import { Provider } from './ctx';
import {
AddBtn, ShowValue, EmptyListener, SelectorComp
} from './components';
export default function CtxWithHooks() {
return (
<Provider
reducers={{
add: (state: any, data: any) => ({ ...state, value: data.value }),
}}
initialState={{ value: 1 }}
>
<AddBtn />
<ShowValue />
<EmptyListener />
<SelectorComp />
</Provider>
);
}