React useReducer:如何组合多个减速器?
React useReducer: How to combine multiple reducers?
我不是 Javascript 专家所以我想知道是否有人有 "elegant" 方法来组合多个 reducer 来创建一个全局状态(比如 Redux)。状态更新多个组件等时不影响性能的函数..
假设我有一个 store.js
import React, { createContext, useReducer } from "react";
import Rootreducer from "./Rootreducer"
export const StoreContext = createContext();
const initialState = {
....
};
export const StoreProvider = props => {
const [state, dispatch] = useReducer(Rootreducer, initialState);
return (
<StoreContext.Provider value={[state, dispatch]}>
{props.children}
<StoreContext.Provider>
);
};
Rootreducer.js
import Reducer1 from "./Reducer1"
import Reducer2 from "./Reducer2"
import Reducer3 from "./Reducer3"
import Reducer4 from "./Reducer4"
const rootReducer = combineReducers({
Reducer1,
Reducer2,
Reducer3,
Reducer4
})
export default rootReducer;
在您的 rootReducer.js
文件中,您可以使用 redux
中的 combineReducers
来组合多个减速器。传统的方式是:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({ name: nameReducer});
export default rootReducer;
您可以在创建商店时将 rootReducer
导入为:
import { combineReducers } from 'redux';
let store = createStore(rootReducer);
在使用 useReducer
钩子时,您可以将 rootReducer
传递给它:
const [state, dispatch] = useReducer(rootReducer, initialState);
希望这对你有用。
如果你只是想实现一个combine reducer的功能,而不需要任何第三方库,那么按照下面的方法来做。 (参考文献:Redux source/code)
工作代码在这里 https://codepen.io/rajeshpillai/pen/jOPWYzL?editors=0010
我创建了两个 reducer,一个 dateReducer 和另一个 counterReducer。我用它作为
const [state, dispatch] = useReducer(combineReducers({
计数器:counterReducer,
日期:dateReducer
}), 初始状态);
combineReducers 代码
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
用法:提取各自的状态
const { counter, date } = state;
注意:您可以根据需要添加更多类似 redux 的功能。
完整的工作代码(万一 codepen 挂了 :))
const {useReducer, useEffect} = React;
function dateReducer(state, action) {
switch(action.type) {
case "set_date":
return action.payload;
break;
default:
return state;
}
}
function counterReducer(state, action) {
console.log('cr:', state);
switch (action.type) {
case 'increment': {
return state + 1;
}
case 'decrement': {
return state - 1;
}
default:
return state;
}
}
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
const initialState = {
counter: 0,
date: new Date
};
function App() {
const [state, dispatch] = useReducer(combineReducers({
counter: counterReducer,
date: dateReducer
}), initialState);
console.log("state", state);
const { counter, date } = state;
return (
<div className="app">
<h3>Counter Reducer</h3>
<div className="counter">
<button onClick={() =>
dispatch({ type: 'increment'})}>+
</button>
<h2>{counter.toString()}</h2>
<button onClick={() =>
dispatch({ type: 'decrement'})}>-
</button>
</div>
<hr/>
<h3>Date Reducer</h3>
{date.toString()}
<button className="submit"
type="submit"
onClick={() =>
dispatch({ type: 'set_date', payload:new Date })}>
Set Date
</button>
</div>
);
}
const rootElement = document.querySelector("#root");
ReactDOM.render(<App />, rootElement);
注意:这是一个快速破解(仅用于学习和演示目的)
组合切片减速器(combineReducers
)
最常见的方法是让每个 reducer 管理自己的 属性(“切片”)状态:
const combineReducers = (slices) => (state, action) =>
Object.keys(slices).reduce( // use for..in loop, if you prefer it
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action),
}),
state
);
例子:
import a from "./Reducer1";
import b from "./Reducer2";
const initialState = { a: {}, b: {} }; // some state for props a, b
const rootReducer = combineReducers({ a, b });
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(rootReducer, initialState);
// Important(!): memoize array value. Else all context consumers update on *every* render
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
按顺序组合减速器
应用多个减速器in sequence on state with arbitrary shape, akin to reduce-reducers:
const reduceReducers = (...reducers) => (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);
例子:
const rootReducer2 = reduceReducers(a, b);
// rest like in first variant
合并多个 useReducer
钩子
您还可以组合来自多个 useReducer
的分派 and/or 状态,例如:
const combineDispatch = (...dispatches) => (action) =>
dispatches.forEach((dispatch) => dispatch(action));
例子:
const [s1, d1] = useReducer(a, {}); // some init state {}
const [s2, d2] = useReducer(b, {}); // some init state {}
// don't forget to memoize again
const combinedDispatch = React.useCallback(combineDispatch(d1, d2), [d1, d2]);
const combinedState = React.useMemo(() => ({ s1, s2, }), [s1, s2]);
// This example uses separate dispatch and state contexts for better render performance
<DispatchContext.Provider value={combinedDispatch}>
<StateContext.Provider value={combinedState}> {children} </StateContext.Provider>
</DispatchContext.Provider>;
总结
以上是最常见的变体。对于这些情况,还有像 use-combined-reducers
这样的库。最后,看看下面结合 combineReducers
和 reduceReducers
的示例:
const StoreContext = React.createContext();
const initialState = { a: 1, b: 1 };
// omit distinct action types for brevity
const plusOneReducer = (state, _action) => state + 1;
const timesTwoReducer = (state, _action) => state * 2;
const rootReducer = combineReducers({
a: reduceReducers(plusOneReducer, plusOneReducer), // aNew = aOld + 1 + 1
b: reduceReducers(timesTwoReducer, plusOneReducer) // bNew = bOld * 2 + 1
});
const StoreProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(rootReducer, initialState);
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
const Comp = () => {
const [globalState, globalDispatch] = React.useContext(StoreContext);
return (
<div>
<p>
a: {globalState.a}, b: {globalState.b}
</p>
<button onClick={globalDispatch}>Click me</button>
</div>
);
};
const App = () => <StoreProvider> <Comp /> </StoreProvider>
ReactDOM.render(<App />, document.getElementById("root"));
//
// helpers
//
function combineReducers(slices) {
return (state, action) =>
Object.keys(slices).reduce(
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action)
}),
state
)
}
function reduceReducers(...reducers){
return (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
我玩了一会儿,也在思考这个问题,因为我也必须处理它。
这可能不是最好的方法,但我只是将我的 reducer 定义为具有 key 的对象:reducer 函数 组合:
const counterRed = {
increment: (oldState, action) => ({
...oldState,
counter: oldState.counter + 1
}),
decrement: (oldState, action) => ({
...oldState,
counter: oldState.counter - 1
})
};
和
const dateRed = {
set_date: (oldState, action) => ({ ...oldState, date: action.payload })
};
我这样组合它们:
const reducer = (oldState, action) => {
const combinedReducers = { ...dateRed, ...counterRed };
let newState = null;
if (combinedReducers[action.type]) {
newState = combinedReducers[action.type](oldState, action);
}
if (newState) {
return { ...newState };
}
return oldState;
};
可以在这里看到一个工作示例:https://codesandbox.io/s/jovial-kowalevski-25pzf?file=/src/App.js
有一个名为 react combine reducer 的库专门用于将 reducer 与上下文结合起来 api。下面是代码示例
import { useReducer } from 'react';
import combineReducers from 'react-combine-reducers';
const initialIdentity = {
name: 'Harry'
}
const initialLocation = {
country: 'UK',
city: 'London'
}
const identityReducer = (state, action) => {
switch (action.type) {
case 'ACTION_A':
return { ...state, name: 'Puli' };
default: return state;
}
}
const locationReducer = (state, action) => {
switch (action.type) {
case 'ACTION_B':
return { ...state, city: 'Manchester' };
default: return state;
}
}
const [profileReducer, initialProfile] = combineReducers({
identity: [identityReducer, initialIdentity],
location: [locationReducer, initialLocation]
});
const [state, dispatch] = useReducer(profileReducer, initialProfile);
console.log(state);
// Outputs the following state:
// {
// identity: {
// name: "Harry"
// },
// location: {
// country: "UK",
// city: "London"
// }
// }
使用 useCombineReducers() 而不是使用 useReducer。可以根据您的要求更改此功能以接受多个参数
const inti ={ count:0, alpha:''}
export function reducer1(state, action) {
switch (action.type)
{
case 'increment':
return {...state , count: state.count + 1};
case 'decrement':
return {...state , count: state.count - 1};
default:
return {count:0};
} }
export function reducer2(state, action) {
switch (action.type) {
case 'add':
return {...state , alpha: state.alpha + action.payload };
case 'rem':
return {...state , alpha: state.alpha + action.payload};
default:
return {alpha:''};
}}
function useCombineReducers(reducer1,reducer2, init) {
const [state,setState] = useState(init);
function dispatch(action)
{
let ns = null;
if(action.type == 'add' || action.type=="rem")
{
ns = reducer2(state,action)
}
else
{
ns = reducer1(state,action)
}
setState(ns);
}
return [state, dispatch];}
function App() {
const [state,dispatch] = useCombineReducers(reducer1,reducer2,inti);
return (
<>
<Provider >
<Counter state ={state} dispatch={dispatch}></Counter>
<Alpha state ={state} dispatch={dispatch}></Alpha>
</Provider>
</>
); }
const Counter = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Count : {props.state.count}
<button onClick={()=> props.dispatch({type: 'increment'})}> + </button>
<button onClick={()=> props.dispatch({type: 'decrement'})}> - </button>
</div>
)} export default Counter
const Alpha = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Alpha : {props.state.alpha}
<button onClick={()=> props.dispatch({type: 'add',payload:'+'})}> + </button>
<button onClick={()=> props.dispatch({type: 'rem',payload:'-'})}> - </button>
</div>
)} export default Alpha
我不是 Javascript 专家所以我想知道是否有人有 "elegant" 方法来组合多个 reducer 来创建一个全局状态(比如 Redux)。状态更新多个组件等时不影响性能的函数..
假设我有一个 store.js
import React, { createContext, useReducer } from "react";
import Rootreducer from "./Rootreducer"
export const StoreContext = createContext();
const initialState = {
....
};
export const StoreProvider = props => {
const [state, dispatch] = useReducer(Rootreducer, initialState);
return (
<StoreContext.Provider value={[state, dispatch]}>
{props.children}
<StoreContext.Provider>
);
};
Rootreducer.js
import Reducer1 from "./Reducer1"
import Reducer2 from "./Reducer2"
import Reducer3 from "./Reducer3"
import Reducer4 from "./Reducer4"
const rootReducer = combineReducers({
Reducer1,
Reducer2,
Reducer3,
Reducer4
})
export default rootReducer;
在您的 rootReducer.js
文件中,您可以使用 redux
中的 combineReducers
来组合多个减速器。传统的方式是:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({ name: nameReducer});
export default rootReducer;
您可以在创建商店时将 rootReducer
导入为:
import { combineReducers } from 'redux';
let store = createStore(rootReducer);
在使用 useReducer
钩子时,您可以将 rootReducer
传递给它:
const [state, dispatch] = useReducer(rootReducer, initialState);
希望这对你有用。
如果你只是想实现一个combine reducer的功能,而不需要任何第三方库,那么按照下面的方法来做。 (参考文献:Redux source/code) 工作代码在这里 https://codepen.io/rajeshpillai/pen/jOPWYzL?editors=0010
我创建了两个 reducer,一个 dateReducer 和另一个 counterReducer。我用它作为
const [state, dispatch] = useReducer(combineReducers({ 计数器:counterReducer, 日期:dateReducer }), 初始状态);
combineReducers 代码
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
用法:提取各自的状态
const { counter, date } = state;
注意:您可以根据需要添加更多类似 redux 的功能。
完整的工作代码(万一 codepen 挂了 :))
const {useReducer, useEffect} = React;
function dateReducer(state, action) {
switch(action.type) {
case "set_date":
return action.payload;
break;
default:
return state;
}
}
function counterReducer(state, action) {
console.log('cr:', state);
switch (action.type) {
case 'increment': {
return state + 1;
}
case 'decrement': {
return state - 1;
}
default:
return state;
}
}
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (let key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
const initialState = {
counter: 0,
date: new Date
};
function App() {
const [state, dispatch] = useReducer(combineReducers({
counter: counterReducer,
date: dateReducer
}), initialState);
console.log("state", state);
const { counter, date } = state;
return (
<div className="app">
<h3>Counter Reducer</h3>
<div className="counter">
<button onClick={() =>
dispatch({ type: 'increment'})}>+
</button>
<h2>{counter.toString()}</h2>
<button onClick={() =>
dispatch({ type: 'decrement'})}>-
</button>
</div>
<hr/>
<h3>Date Reducer</h3>
{date.toString()}
<button className="submit"
type="submit"
onClick={() =>
dispatch({ type: 'set_date', payload:new Date })}>
Set Date
</button>
</div>
);
}
const rootElement = document.querySelector("#root");
ReactDOM.render(<App />, rootElement);
注意:这是一个快速破解(仅用于学习和演示目的)
组合切片减速器(combineReducers
)
最常见的方法是让每个 reducer 管理自己的 属性(“切片”)状态:
const combineReducers = (slices) => (state, action) =>
Object.keys(slices).reduce( // use for..in loop, if you prefer it
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action),
}),
state
);
例子:
import a from "./Reducer1";
import b from "./Reducer2";
const initialState = { a: {}, b: {} }; // some state for props a, b
const rootReducer = combineReducers({ a, b });
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(rootReducer, initialState);
// Important(!): memoize array value. Else all context consumers update on *every* render
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
按顺序组合减速器
应用多个减速器in sequence on state with arbitrary shape, akin to reduce-reducers:
const reduceReducers = (...reducers) => (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);
例子:
const rootReducer2 = reduceReducers(a, b);
// rest like in first variant
合并多个 useReducer
钩子
您还可以组合来自多个 useReducer
的分派 and/or 状态,例如:
const combineDispatch = (...dispatches) => (action) =>
dispatches.forEach((dispatch) => dispatch(action));
例子:
const [s1, d1] = useReducer(a, {}); // some init state {}
const [s2, d2] = useReducer(b, {}); // some init state {}
// don't forget to memoize again
const combinedDispatch = React.useCallback(combineDispatch(d1, d2), [d1, d2]);
const combinedState = React.useMemo(() => ({ s1, s2, }), [s1, s2]);
// This example uses separate dispatch and state contexts for better render performance
<DispatchContext.Provider value={combinedDispatch}>
<StateContext.Provider value={combinedState}> {children} </StateContext.Provider>
</DispatchContext.Provider>;
总结
以上是最常见的变体。对于这些情况,还有像 use-combined-reducers
这样的库。最后,看看下面结合 combineReducers
和 reduceReducers
的示例:
const StoreContext = React.createContext();
const initialState = { a: 1, b: 1 };
// omit distinct action types for brevity
const plusOneReducer = (state, _action) => state + 1;
const timesTwoReducer = (state, _action) => state * 2;
const rootReducer = combineReducers({
a: reduceReducers(plusOneReducer, plusOneReducer), // aNew = aOld + 1 + 1
b: reduceReducers(timesTwoReducer, plusOneReducer) // bNew = bOld * 2 + 1
});
const StoreProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(rootReducer, initialState);
const store = React.useMemo(() => [state, dispatch], [state]);
return (
<StoreContext.Provider value={store}> {children} </StoreContext.Provider>
);
};
const Comp = () => {
const [globalState, globalDispatch] = React.useContext(StoreContext);
return (
<div>
<p>
a: {globalState.a}, b: {globalState.b}
</p>
<button onClick={globalDispatch}>Click me</button>
</div>
);
};
const App = () => <StoreProvider> <Comp /> </StoreProvider>
ReactDOM.render(<App />, document.getElementById("root"));
//
// helpers
//
function combineReducers(slices) {
return (state, action) =>
Object.keys(slices).reduce(
(acc, prop) => ({
...acc,
[prop]: slices[prop](acc[prop], action)
}),
state
)
}
function reduceReducers(...reducers){
return (state, action) =>
reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
我玩了一会儿,也在思考这个问题,因为我也必须处理它。
这可能不是最好的方法,但我只是将我的 reducer 定义为具有 key 的对象:reducer 函数 组合:
const counterRed = {
increment: (oldState, action) => ({
...oldState,
counter: oldState.counter + 1
}),
decrement: (oldState, action) => ({
...oldState,
counter: oldState.counter - 1
})
};
和
const dateRed = {
set_date: (oldState, action) => ({ ...oldState, date: action.payload })
};
我这样组合它们:
const reducer = (oldState, action) => {
const combinedReducers = { ...dateRed, ...counterRed };
let newState = null;
if (combinedReducers[action.type]) {
newState = combinedReducers[action.type](oldState, action);
}
if (newState) {
return { ...newState };
}
return oldState;
};
可以在这里看到一个工作示例:https://codesandbox.io/s/jovial-kowalevski-25pzf?file=/src/App.js
有一个名为 react combine reducer 的库专门用于将 reducer 与上下文结合起来 api。下面是代码示例
import { useReducer } from 'react';
import combineReducers from 'react-combine-reducers';
const initialIdentity = {
name: 'Harry'
}
const initialLocation = {
country: 'UK',
city: 'London'
}
const identityReducer = (state, action) => {
switch (action.type) {
case 'ACTION_A':
return { ...state, name: 'Puli' };
default: return state;
}
}
const locationReducer = (state, action) => {
switch (action.type) {
case 'ACTION_B':
return { ...state, city: 'Manchester' };
default: return state;
}
}
const [profileReducer, initialProfile] = combineReducers({
identity: [identityReducer, initialIdentity],
location: [locationReducer, initialLocation]
});
const [state, dispatch] = useReducer(profileReducer, initialProfile);
console.log(state);
// Outputs the following state:
// {
// identity: {
// name: "Harry"
// },
// location: {
// country: "UK",
// city: "London"
// }
// }
使用 useCombineReducers() 而不是使用 useReducer。可以根据您的要求更改此功能以接受多个参数
const inti ={ count:0, alpha:''}
export function reducer1(state, action) {
switch (action.type)
{
case 'increment':
return {...state , count: state.count + 1};
case 'decrement':
return {...state , count: state.count - 1};
default:
return {count:0};
} }
export function reducer2(state, action) {
switch (action.type) {
case 'add':
return {...state , alpha: state.alpha + action.payload };
case 'rem':
return {...state , alpha: state.alpha + action.payload};
default:
return {alpha:''};
}}
function useCombineReducers(reducer1,reducer2, init) {
const [state,setState] = useState(init);
function dispatch(action)
{
let ns = null;
if(action.type == 'add' || action.type=="rem")
{
ns = reducer2(state,action)
}
else
{
ns = reducer1(state,action)
}
setState(ns);
}
return [state, dispatch];}
function App() {
const [state,dispatch] = useCombineReducers(reducer1,reducer2,inti);
return (
<>
<Provider >
<Counter state ={state} dispatch={dispatch}></Counter>
<Alpha state ={state} dispatch={dispatch}></Alpha>
</Provider>
</>
); }
const Counter = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Count : {props.state.count}
<button onClick={()=> props.dispatch({type: 'increment'})}> + </button>
<button onClick={()=> props.dispatch({type: 'decrement'})}> - </button>
</div>
)} export default Counter
const Alpha = (props) => {
return (
<div style ={{Border:'10px', width:'20px'}}>
Alpha : {props.state.alpha}
<button onClick={()=> props.dispatch({type: 'add',payload:'+'})}> + </button>
<button onClick={()=> props.dispatch({type: 'rem',payload:'-'})}> - </button>
</div>
)} export default Alpha