From 99eea3ec9172189e115ce340a6104cb4a3c582c7 Mon Sep 17 00:00:00 2001 From: daishi Date: Fri, 21 Feb 2020 22:11:43 +0900 Subject: [PATCH 1/3] Experiment: Redux with useMutableSource --- __tests__/all_spec.js | 33 +++++----- package.json | 1 + src/redux-use-mutable-source/index.js | 92 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 src/redux-use-mutable-source/index.js diff --git a/__tests__/all_spec.js b/__tests__/all_spec.js index b07f491..93c37d9 100644 --- a/__tests__/all_spec.js +++ b/__tests__/all_spec.js @@ -3,22 +3,23 @@ const port = process.env.PORT || '8080'; const names = [ - 'react-redux', - 'reactive-react-redux', - 'react-tracked', - 'constate', - 'zustand', - 'react-sweet-state', - 'storeon', - 'react-hooks-global-state-1', - 'react-hooks-global-state-2', - 'use-context-selector', - 'mobx-react-lite', - 'use-subscription', - 'mobx-use-sub', - 'react-state', - 'simplux', - 'react-apollo', + // 'react-redux', + 'redux-use-mutable-source', + // 'reactive-react-redux', + // 'react-tracked', + // 'constate', + // 'zustand', + // 'react-sweet-state', + // 'storeon', + // 'react-hooks-global-state-1', + // 'react-hooks-global-state-2', + // 'use-context-selector', + // 'mobx-react-lite', + // 'use-subscription', + // 'mobx-use-sub', + // 'react-state', + // 'simplux', + // 'react-apollo', ]; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); diff --git a/package.json b/package.json index a82f836..86b3efa 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dev-server": "cross-env PORT=8080 webpack-dev-server", "http-server": "cross-env PORT=8080 http-server dist", "build:react-redux": "cross-env NAME=react-redux webpack", + "build:redux-use-mutable-source": "cross-env NAME=redux-use-mutable-source webpack", "build:reactive-react-redux": "cross-env NAME=reactive-react-redux webpack", "build:react-tracked": "cross-env NAME=react-tracked webpack", "build:constate": "cross-env NAME=constate webpack", diff --git a/src/redux-use-mutable-source/index.js b/src/redux-use-mutable-source/index.js new file mode 100644 index 0000000..5531a32 --- /dev/null +++ b/src/redux-use-mutable-source/index.js @@ -0,0 +1,92 @@ +import React, { + useTransition, + createContext, + useCallback, + useContext, + useLayoutEffect, + useMemo, + useRef, + createMutableSource, + useMutableSource, +} from 'react'; +import { createStore } from 'redux'; + +import { + syncBlock, + useRegisterIncrementDispatcher, + reducer, + ids, + useCheckTearing, +} from '../common'; + +const StoreContext = createContext(); +const subscribe = (store, callback) => store.subscribe(callback); +const Provider = ({ store, children }) => { + const contextValue = useMemo(() => ({ + source: createMutableSource(store, () => store.getState()), + dispatch: store.dispatch, + }), [store]); + return ( + + {children} + + ); +}; +const useDispatch = () => useContext(StoreContext).dispatch; +const useSelector = (selector) => { + const selectorRef = useRef(selector); + useLayoutEffect(() => { + selectorRef.current = selector; + }); + const getSnapshot = useCallback((store) => selectorRef.current(store.getState()), []); + const { source } = useContext(StoreContext); + return useMutableSource(source, getSnapshot, subscribe); +}; + +const store = createStore(reducer); + +const Counter = React.memo(() => { + const count = useSelector((state) => state.count); + syncBlock(); + return
{count}
; +}, () => true); + +const Main = () => { + const dispatch = useDispatch(); + const count = useSelector((state) => state.count); + useCheckTearing(); + useRegisterIncrementDispatcher(React.useCallback(() => { + dispatch({ type: 'increment' }); + }, [dispatch])); + const [localCount, localIncrement] = React.useReducer((c) => c + 1, 0); + const normalIncrement = () => { + dispatch({ type: 'increment' }); + }; + const [startTransition, isPending] = useTransition(); + const transitionIncrement = () => { + startTransition(() => { + dispatch({ type: 'increment' }); + }); + }; + return ( +
+ + + {isPending && 'Pending...'} +

Shared Count

+ {ids.map((id) => )} +
{count}
+

Local Count

+ {localCount} + +
+ ); +}; + +const App = () => ( + +
+ +); + +export default App; From 53f42617c33e039ee2b32aadf339d0537bc17f18 Mon Sep 17 00:00:00 2001 From: daishi Date: Sun, 23 Feb 2020 08:08:11 +0900 Subject: [PATCH 2/3] experimental redux binding with storeState --- src/redux-use-mutable-source/index.js | 36 ++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/redux-use-mutable-source/index.js b/src/redux-use-mutable-source/index.js index 5531a32..aceaff8 100644 --- a/src/redux-use-mutable-source/index.js +++ b/src/redux-use-mutable-source/index.js @@ -3,12 +3,15 @@ import React, { createContext, useCallback, useContext, + useEffect, useLayoutEffect, useMemo, useRef, + useState, createMutableSource, useMutableSource, } from 'react'; +import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'; import { createStore } from 'redux'; import { @@ -20,10 +23,34 @@ import { } from '../common'; const StoreContext = createContext(); -const subscribe = (store, callback) => store.subscribe(callback); +const subscribe = ({ subscribersRef }, callback) => { + const subscribers = subscribersRef.current; + subscribers.push(callback); + return () => { + const index = subscribers.indexOf(callback); + subscribers.splice(index, 1); + }; +}; const Provider = ({ store, children }) => { + const [storeState, setStoreState] = useState(store.getState()); + useEffect(() => { + const callback = () => { + setStoreState(store.getState()); + }; + const unsubscribe = store.subscribe(callback); + callback(); + return unsubscribe; + }, [store]); + const storeStateRef = useRef(storeState); + const subscribersRef = useRef([]); + useEffect(() => { + batchedUpdates(() => { + storeStateRef.current = storeState; + subscribersRef.current.forEach((callback) => callback()); + }); + }); const contextValue = useMemo(() => ({ - source: createMutableSource(store, () => store.getState()), + source: createMutableSource({ storeStateRef, subscribersRef }, () => storeStateRef.current), dispatch: store.dispatch, }), [store]); return ( @@ -38,8 +65,11 @@ const useSelector = (selector) => { useLayoutEffect(() => { selectorRef.current = selector; }); - const getSnapshot = useCallback((store) => selectorRef.current(store.getState()), []); const { source } = useContext(StoreContext); + const getSnapshot = useCallback( + ({ storeStateRef }) => selectorRef.current(storeStateRef.current), + [], + ); return useMutableSource(source, getSnapshot, subscribe); }; From b349bc0f7acca0b05a26f2578c2cc63d1adfa6bb Mon Sep 17 00:00:00 2001 From: daishi Date: Sun, 23 Feb 2020 10:58:02 +0900 Subject: [PATCH 3/3] Revert "experimental redux binding with storeState" This reverts commit 53f42617c33e039ee2b32aadf339d0537bc17f18. --- src/redux-use-mutable-source/index.js | 36 +++------------------------ 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/redux-use-mutable-source/index.js b/src/redux-use-mutable-source/index.js index aceaff8..5531a32 100644 --- a/src/redux-use-mutable-source/index.js +++ b/src/redux-use-mutable-source/index.js @@ -3,15 +3,12 @@ import React, { createContext, useCallback, useContext, - useEffect, useLayoutEffect, useMemo, useRef, - useState, createMutableSource, useMutableSource, } from 'react'; -import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'; import { createStore } from 'redux'; import { @@ -23,34 +20,10 @@ import { } from '../common'; const StoreContext = createContext(); -const subscribe = ({ subscribersRef }, callback) => { - const subscribers = subscribersRef.current; - subscribers.push(callback); - return () => { - const index = subscribers.indexOf(callback); - subscribers.splice(index, 1); - }; -}; +const subscribe = (store, callback) => store.subscribe(callback); const Provider = ({ store, children }) => { - const [storeState, setStoreState] = useState(store.getState()); - useEffect(() => { - const callback = () => { - setStoreState(store.getState()); - }; - const unsubscribe = store.subscribe(callback); - callback(); - return unsubscribe; - }, [store]); - const storeStateRef = useRef(storeState); - const subscribersRef = useRef([]); - useEffect(() => { - batchedUpdates(() => { - storeStateRef.current = storeState; - subscribersRef.current.forEach((callback) => callback()); - }); - }); const contextValue = useMemo(() => ({ - source: createMutableSource({ storeStateRef, subscribersRef }, () => storeStateRef.current), + source: createMutableSource(store, () => store.getState()), dispatch: store.dispatch, }), [store]); return ( @@ -65,11 +38,8 @@ const useSelector = (selector) => { useLayoutEffect(() => { selectorRef.current = selector; }); + const getSnapshot = useCallback((store) => selectorRef.current(store.getState()), []); const { source } = useContext(StoreContext); - const getSnapshot = useCallback( - ({ storeStateRef }) => selectorRef.current(storeStateRef.current), - [], - ); return useMutableSource(source, getSnapshot, subscribe); };